Omniscia Nexera Audit
PurchaseDiscountGenesisIdsMixerStorage Manual Review Findings
PurchaseDiscountGenesisIdsMixerStorage Manual Review Findings
PDI-01M: Inexistent Restriction of Selectors
| Type | Severity | Location |
|---|---|---|
| Logical Fault | ![]() | PurchaseDiscountGenesisIdsMixerStorage.sol: • I-1: L170, L172 • I-2: L192, L194 |
Description:
The discount application and price per fraction evaluation selectors can be arbitrarily set in the configuration of the purchase discount facet, potentially allowing malicious access to different internally-invoked functions of the tokenizer's Diamond.
Impact:
As the encodedData and selector of a particular discount application or price evaluation are arbitrarily specified user data, it is possible for a malicious campaign creator to introduce a backdoor to their campaign to gain access to functions they otherwise would not be able to invoke, especially via the PurchaseDiscountGenesisIdsMixerStorage::getPricePerFractionForGenesisId pathway which restricts only two arguments.
Example:
131function checkValidityAndStoreSelectors(132 Layout storage l,133 uint256 length,134 uint256 currentId,135 bytes4[] memory setAndCheckSelectors,136 bytes4[] memory applyDiscountSelectors,137 bytes4[] memory getPriceSelectors,138 bytes[] memory setAndCheckData139) private {140 for (uint256 i; i < length; ) {141 bytes4 setAndCheckSelector_ = setAndCheckSelectors[i];142
143 if (!l.isSetAndCheckSelectorSupported[setAndCheckSelector_]) revert UnsupportedSelector();144
145 bytes memory callDataInput = abi.encodeWithSelector(setAndCheckSelector_, setAndCheckData[i]);146 (bool success, bytes memory result) = address(this).call(callDataInput);147
148 if (!success) {149 assembly ("memory-safe") {150 revert(add(result, 32), mload(result))151 }152 }153
154 l.isDiscountModelForId[currentId][applyDiscountSelectors[i]] = true;155 l.isGetPriceModelForId[currentId][getPriceSelectors[i]] = true;156
157 unchecked {158 i += 1;159 }160 }161}162
163function applyDiscount(164 Layout storage l,165 uint256 campaignId,166 uint256 amountOfFractions,167 address account,168 bytes calldata operationData169) internal returns (uint256, uint256) {170 (bytes4 selector, bytes memory encodedData) = abi.decode(operationData, (bytes4, bytes));171
172 if (!l.isDiscountModelForId[campaignId][selector]) revert UnsupportedDiscountModel();173
174 bytes memory callDataInput = abi.encodeWithSelector(selector, campaignId, amountOfFractions, account, encodedData);175 (bool success, bytes memory returnData) = address(this).call(callDataInput);176
177 if (!success) {178 assembly ("memory-safe") {179 revert(add(returnData, 32), mload(returnData))180 }181 }182
183 return abi.decode(returnData, (uint256, uint256));184}Recommendation:
We advise the PurchaseDiscountGenesisIdsMixerStorage::checkValidityAndStoreSelectors function to restrict the applyDiscountSelectors and getPriceSelectors that are authorized in the system, disallowing corruption of a particular campaign via unauthorized access of internally-invokeable functions.
Alleviation (d682057ecb0e254069773d64f32c068cedb71e2a):
The code has been updated to restrict the applyDiscountSelector_ and getPriceSelector_ values to specific implementations within the PurchaseDiscountGenesisIdsMixerStorage::checkValidityAndStoreSelectors function, preventing the backdoor invocation pathways described within this exhibit.
