Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

_validatePositionList() positionIdList can forgery #463

Closed
c4-bot-10 opened this issue Apr 22, 2024 · 4 comments
Closed

_validatePositionList() positionIdList can forgery #463

c4-bot-10 opened this issue Apr 22, 2024 · 4 comments
Labels
2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working downgraded by judge Judge downgraded the risk level of this issue duplicate-498 🤖_178_group AI based duplicate group recommendation satisfactory satisfies C4 submission criteria; eligible for awards

Comments

@c4-bot-10
Copy link
Contributor

Lines of code

https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/PanopticPool.sol#L1367

Vulnerability details

Vulnerability details

_validatePositionList(positionIdList) is called from multiple places and is primarily used to validate the legality of positionIdList.

The main logic involves iterating through tokenIds and performing XOR operations, then comparing the result with s_positionsHash[account].

    function _validatePositionList(
        address account,
        TokenId[] calldata positionIdList,
        uint256 offset
    ) internal view {
        uint256 pLength;
        uint256 currentHash = s_positionsHash[account];

        unchecked {
            pLength = positionIdList.length - offset;
        }
        // note that if pLength == 0 even if a user has existing position(s) the below will fail b/c the fingerprints will mismatch
        // Check that position hash (the fingerprint of option positions) matches the one stored for the '_account'
        uint256 fingerprintIncomingList;

        for (uint256 i = 0; i < pLength; ) {
            fingerprintIncomingList = PanopticMath.updatePositionsHash(
                fingerprintIncomingList,
                positionIdList[i],
                ADD
            );
            unchecked {
                ++i;
            }
        }

        // revert if fingerprint for provided '_positionIdList' does not match the one stored for the '_account'
        if (fingerprintIncomingList != currentHash) revert Errors.InputListFail();
    }
    function updatePositionsHash(
        uint256 existingHash,
        TokenId tokenId,
        bool addFlag
    ) internal pure returns (uint256) {
        // add the XOR`ed hash of the single option position `tokenId` to the `existingHash`
        // @dev 0 ^ x = x

        unchecked {
            // update hash by taking the XOR of the new tokenId
            uint248 updatedHash = uint248(existingHash) ^
                (uint248(uint256(keccak256(abi.encode(tokenId)))));
            // increment the top 8 bit if addflag=true, decrement otherwise
            return
                addFlag
@>                  ? uint256(updatedHash) + (((existingHash >> 248) + 1) << 248)
                    : uint256(updatedHash) + (((existingHash >> 248) - 1) << 248);
        }
    }

The main issue in the code is with (((existingHash >> 248) + 1) << 248). The first uint8 represents the number of tokenIds, and overflow is ignored.

Additionally,when XOR calculation, the known formula is X^Y^Y=X.

This means that calculating the hash for positionIdList[1,2,3] and positionIdList[1,2,3,4,4,4,4......] (256 4 ) will result in the same hash.

Proof of Concept

The following code demonstrates that inserting any 256 tokenIds will yield the same hash.

contract CounterTest is Test {

    function updatePositionsHash(
      uint256 existingHash,
      uint256 tokenId,
      bool addFlag
  ) internal pure returns (uint256) {

      unchecked {
          // update hash by taking the XOR of the new tokenId
          uint248 updatedHash = uint248(existingHash) ^
              (uint248(uint256(keccak256(abi.encode(tokenId)))));
          // increment the top 8 bit if addflag=true, decrement otherwise
          return
              addFlag
                  ? uint256(updatedHash) + (((existingHash >> 248) + 1) << 248)
                  : uint256(updatedHash) + (((existingHash >> 248) - 1) << 248);
      }
  }

  function test() external {
    uint256 existingHash = 0;
    uint256 u1 = updatePositionsHash(0,1,true);
    uint256 u2 = updatePositionsHash(u1,2,true);
    uint256 u3 = updatePositionsHash(u2,3,true);
    uint256 newU3=u3;
    for(uint256 i;i< 256;i++){
      newU3 = updatePositionsHash(newU3,789,true);
    }
    console.log(newU3 == u3);
  }
}
$ forge test -vvv

Running 1 test for test/Counter.t.sol:CounterTest
[PASS] test() (gas: 105578)
Logs:
  true

Impact

If a seller has a tokenId premium > required collateral, they can maliciously increase positionIdList by adding any 256*N number of tokenIds, passing the validation in _validateSolvency(), and thus illegally opening a position.

Recommended Mitigation

    function updatePositionsHash(
        uint256 existingHash,
        TokenId tokenId,
        bool addFlag
    ) internal pure returns (uint256) {
        // add the XOR`ed hash of the single option position `tokenId` to the `existingHash`
        // @dev 0 ^ x = x

        unchecked {
            // update hash by taking the XOR of the new tokenId
            uint248 updatedHash = uint248(existingHash) ^
                (uint248(uint256(keccak256(abi.encode(tokenId)))));
            // increment the top 8 bit if addflag=true, decrement otherwise
+           require((existingHash >> 248)<255,"invald");
            return
                addFlag
                    ? uint256(updatedHash) + (((existingHash >> 248) + 1) << 248)
                    : uint256(updatedHash) + (((existingHash >> 248) - 1) << 248);
        }
    }

Assessed type

Context

@c4-bot-10 c4-bot-10 added 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working labels Apr 22, 2024
c4-bot-2 added a commit that referenced this issue Apr 22, 2024
@c4-bot-13 c4-bot-13 added the 🤖_178_group AI based duplicate group recommendation label Apr 22, 2024
@c4-judge
Copy link
Contributor

Picodes marked the issue as duplicate of #498

@c4-judge c4-judge added duplicate-498 3 (High Risk) Assets can be stolen/lost/compromised directly upgraded by judge Original issue severity upgraded from QA/Gas by judge and removed 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value labels Apr 25, 2024
@c4-judge
Copy link
Contributor

Picodes changed the severity to 3 (High Risk)

@c4-judge
Copy link
Contributor

Picodes marked the issue as satisfactory

@c4-judge c4-judge added the satisfactory satisfies C4 submission criteria; eligible for awards label Apr 30, 2024
@c4-judge c4-judge removed 3 (High Risk) Assets can be stolen/lost/compromised directly upgraded by judge Original issue severity upgraded from QA/Gas by judge labels May 9, 2024
@c4-judge
Copy link
Contributor

c4-judge commented May 9, 2024

Picodes changed the severity to 2 (Med Risk)

@c4-judge c4-judge added 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value downgraded by judge Judge downgraded the risk level of this issue labels May 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working downgraded by judge Judge downgraded the risk level of this issue duplicate-498 🤖_178_group AI based duplicate group recommendation satisfactory satisfies C4 submission criteria; eligible for awards
Projects
None yet
Development

No branches or pull requests

3 participants