msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: NodeRegistry.registerNodeFor() no replay protection and expiration  Won't Fix
Severity: medium

Description:
An owner can register a node with the signer not being the owner by calling `registerNodeFor`. The owner submits a message signed for the owner including the properties of the node including the url.\\nThe signed data does not include the `registryID` nor the NodeRegistry's address and can therefore be used by the owner to submit the same node to multiple registries or chains without the signers consent.\\nThe signed data does not expire and can be re-used by the owner indefinitely to submit the same node again to future contracts or the same contract after the node has been removed.\\nArguments are not validated in the external function (also see issue 6.17)\\n```\\nbytes32 tempHash = keccak256(\\n    abi.encodePacked(\\n        \\_url,\\n        \\_props,\\n        \\_timeout,\\n        \\_weight,\\n        msg.sender\\n    )\\n);\\n```\\n

Impact:

Recommended mitigation:
Include `registryID` and an expiration timestamp that is checked in the contract with the signed data. Validate function arguments.
Vulnerability: BlockhashRegistry - recreateBlockheaders allows invalid parent hashes for intermediary blocks
Severity: medium

Description:
It is assumed that a blockhash of `0x00` is invalid, but the method accepts intermediary parent hashes extracted from blockheaders that are zero when establishing the trust chain.\\n`recreateBlockheaders` relies on `reCalculateBlockheaders` to correctly establish a chain of trust from the provided list of `_blockheaders` to a valid blockhash stored in the contract. However, `reCalculateBlockheaders` fails to raise an exception in case `getParentAndBlockhash` returns a blockhash of `0x00`. Subsequently it will skip over invalid blockhashes and continue to establish the trust chain without raising an error.\\nThis may allow an attacker with enough hashing power to store a blockheader hash that is actually invalid on the real chain but accepted within this smart contract. This may even only be done temporarily to overwrite an existing hash for a short period of time (see https://github.com/ConsenSys/slockit-in3-audit-2019-09/issues/24).\\n```\\nfor (uint i = 0; i < \\_blockheaders.length; i++) {\\n    (calcParent, calcBlockhash) = getParentAndBlockhash(\\_blockheaders[i]);\\n    if (calcBlockhash != currentBlockhash) {\\n        return 0x0;\\n    }\\n    currentBlockhash = calcParent;\\n}\\n```\\n

Impact:

Recommended mitigation:
Stop processing the array of `_blockheaders` immediately if a blockheader is invalid.
Vulnerability: BlockhashRegistry - recreateBlockheaders succeeds and emits an event even though no blockheaders have been provided
Severity: medium

Description:
The method is used to re-create blockhashes from a list of rlp-encoded `_blockheaders`. However, the method never checks if `_blockheaders` actually contains items. The result is, that the method will unnecessarily store the same value that is already in the `blockhashMapping` at the same location and wrongly log `LogBlockhashAdded` even though nothing has been added nor changed.\\nassume `_blockheaders` is empty and the registry already knows the blockhash of `_blockNumber`\\n```\\nfunction recreateBlockheaders(uint \\_blockNumber, bytes[] memory \\_blockheaders) public {\\n\\n    bytes32 currentBlockhash = blockhashMapping[\\_blockNumber];\\n    require(currentBlockhash != 0x0, "parentBlock is not available");\\n\\n    bytes32 calculatedHash = reCalculateBlockheaders(\\_blockheaders, currentBlockhash);\\n    require(calculatedHash != 0x0, "invalid headers");\\n```\\n\\nAn attempt is made to re-calculate the hash of an empty `_blockheaders` array (also passing the `currentBlockhash` from the registry)\\n```\\nbytes32 calculatedHash = reCalculateBlockheaders(\\_blockheaders, currentBlockhash);\\n```\\n\\nThe following loop in `reCalculateBlockheaders` is skipped and the `currentBlockhash` is returned.\\n```\\nfunction reCalculateBlockheaders(bytes[] memory \\_blockheaders, bytes32 \\_bHash) public pure returns (bytes32 bhash) {\\n\\n    bytes32 currentBlockhash = \\_bHash;\\n    bytes32 calcParent = 0x0;\\n    bytes32 calcBlockhash = 0x0;\\n\\n    /// save to use for up to 200 blocks, exponential increase of gas-usage afterwards\\n    for (uint i = 0; i < \\_blockheaders.length; i++) {\\n        (calcParent, calcBlockhash) = getParentAndBlockhash(\\_blockheaders[i]);\\n        if (calcBlockhash != currentBlockhash) {\\n            return 0x0;\\n        }\\n        currentBlockhash = calcParent;\\n    }\\n\\n    return currentBlockhash;\\n```\\n\\nThe assertion does not fire, the `bnr` to store the `calculatedHash` is the same as the one initially provided to the method as an argument.. Nothing has changed but an event is emitted.\\n```\\n    /// we should never fail this assert, as this would mean that we were able to recreate a invalid blockchain\\n    assert(\\_blockNumber > \\_blockheaders.length);\\n    uint bnr = \\_blockNumber - \\_blockheaders.length;\\n    blockhashMapping[bnr] = calculatedHash;\\n    emit LogBlockhashAdded(bnr, calculatedHash);\\n}\\n```\\n

Impact:

Recommended mitigation:
The method is crucial for the system to work correctly and must be tightly controlled by input validation. It should not be allowed to overwrite an existing value in the contract (issue 6.29) or emit an event even though nothing has happened. Therefore validate that user provided input is within safe bounds. In this case, that at least one `_blockheader` has been provided. Validate that `_blockNumber` is less than `block.number` and do not expect that parts of the code will throw and safe the contract from exploitation.
Vulnerability: NodeRegistry.updateNode replaces signer with owner and emits inconsistent events
Severity: medium

Description:
When the `owner` calls `updateNode()` function providing a new `url` for the node, the `signer` of the `url` is replaced by `msg.sender` which in this case is the `owner` of the node. Note that new URL can resolve to the same URL as before (See https://github.com/ConsenSys/slockit-in3-audit-2019-09/issues/36).\\n```\\nif (newURl != keccak256(bytes(node.url))) {\\n\\n    // deleting the old entry\\n    delete urlIndex[keccak256(bytes(node.url))];\\n\\n    // make sure the new url is not already in use\\n    require(!urlIndex[newURl].used, "url is already in use");\\n\\n    UrlInformation memory ui;\\n    ui.used = true;\\n    ui.signer = msg.sender;\\n    urlIndex[newURl] = ui;\\n    node.url = \\_url;\\n}\\n```\\n\\nFurthermore, the method emits a `LogNodeRegistered` event when the node structure is updated. However, the event will always emit `msg.sender` as the signer even though that might not be true. For example, if the `url` does not change, the signer can still be another account that was previously registered with `registerNodeFor` and is not necessarily the `owner`.\\n```\\nemit LogNodeRegistered(\\n    node.url,\\n    \\_props,\\n    msg.sender,\\n    node.deposit\\n);\\n```\\n\\n```\\nevent LogNodeRegistered(string url, uint props, address signer, uint deposit);\\n```\\n

Impact:

Recommended mitigation:
The `updateNode()` function gets the `signer` as an input used to reference the node structure and this `signer` should be set for the `UrlInformation`.\\n```\\nfunction updateNode(\\n        address \\_signer,\\n        string calldata \\_url,\\n        uint64 \\_props,\\n        uint64 \\_timeout,\\n        uint64 \\_weight\\n    )\\n```\\n\\nThe method should actually only allow to change node properties when `owner==signer` otherwise `updateNode` is bypassing the strict requirements enforced with `registerNodeFor` where e.g. the `url` needs to be signed by the signer in order to register it.\\nThe emitted event should always emit `node.signer` instead of `msg.signer` which can be wrong.\\nThe method should emit its own distinct event `LogNodeUpdated` for audit purposes and to be able to distinguish new node registrations from node structure updates. This might also require software changes to client/node implementations to listen for node updates.
Vulnerability: NodeRegistry - In3Node memory n is never used
Severity: low

Description:
NodeRegistry `In3Node memory n` is never used inside the modifier `onlyActiveState`.\\n```\\nmodifier onlyActiveState(address \\_signer) {\\n\\n    SignerInformation memory si = signerIndex[\\_signer];\\n    require(si.stage == Stages.Active, "address is not an in3-signer");\\n\\n    In3Node memory n = nodes[si.index];\\n    assert(nodes[si.index].signer == \\_signer);\\n    \\_;\\n}\\n```\\n

Impact:

Recommended mitigation:
Use `n` in the assertion to access the node signer `assert(n.signer == _signer);` or directly access it from storage and avoid copying the struct.
Vulnerability: NodeRegistry - removeNode unnecessarily casts the nodeIndex to uint64 potentially truncating its value
Severity: low

Description:
`removeNode` removes a node from the Nodes array. This is done by copying the last node of the array to the `_nodeIndex` of the node that is to be removed. Finally the node array size is decreased.\\nA Node's index is also referenced in the `SignerInformation` struct. This index needs to be adjusted when removing a node from the array as the last node is copied to the index of the node that is to be removed.\\nWhen adjusting the Node's index in the `SignerInformation` struct `removeNode` casts the index to `uint64`. This is both unnecessary as the struct defines the index as `uint` and theoretically dangerous if a node at an index greater than `uint64_max` is removed. The resulting `SignerInformation` index will be truncated to `uint64` leading to an inconsistency in the contract.\\n```\\nstruct SignerInformation {\\n    uint64 lockedTime;                  /// timestamp until the deposit of an in3-node can not be withdrawn after the node was removed\\n    address owner;                      /// the owner of the node\\n\\n    Stages stage;                       /// state of the address\\n\\n    uint depositAmount;                 /// amount of deposit to be locked, used only after a node had been removed\\n\\n    uint index;                         /// current index-position of the node in the node-array\\n}\\n```\\n\\n```\\n// move the last entry to the removed one.\\nIn3Node memory m = nodes[length - 1];\\nnodes[\\_nodeIndex] = m;\\n\\nSignerInformation storage si = signerIndex[m.signer];\\nsi.index = uint64(\\_nodeIndex);\\nnodes.length--;\\n```\\n

Impact:

Recommended mitigation:
Resolution\\nFixed as per recommendation https://git.slock.it/in3/in3-contracts/commit/6c35dd422e27eec1b1d2f70e328268014cadb515.\\nDo not cast and therefore truncate the index.
Vulnerability: BlockhashRegistry- assembly code can be optimized
Severity: low

Description:
The following code can be optimized by removing `mload` and mstore:\\n```\\nrequire(first > 0xf7, "invalid offset");\\nuint8 offset = first - 0xf7 + 2;\\n\\n/// we are using assembly because it's the most efficent way to access the parent blockhash within the rlp-encoded blockheader\\n// solium-disable-next-line security/no-inline-assembly\\nassembly { // solhint-disable-line no-inline-assembly\\n    // mstore to get the memory pointer of the blockheader to 0x20\\n    mstore(0x20, \\_blockheader)\\n\\n    // we load the pointer we just stored\\n    // then we add 0x20 (32 bytes) to get to the start of the blockheader\\n    // then we add the offset we calculated\\n    // and load it to the parentHash variable\\n    parentHash :=mload(\\n        add(\\n            add(\\n                mload(0x20), 0x20\\n            ), offset)\\n    )\\n}\\n```\\n

Impact:

Recommended mitigation:
```\\nassembly { // solhint-disable-line no-inline-assembly\\n            // mstore to get the memory pointer of the blockheader to 0x20\\n            //mstore(0x20, \\_blockheader) //@audit should assign 0x20ptr to variable first and use it.\\n\\n            // we load the pointer we just stored\\n            // then we add 0x20 (32 bytes) to get to the start of the blockheader\\n            // then we add the offset we calculated\\n            // and load it to the parentHash variable\\n            parentHash :=mload(\\n                add(\\n                    add(\\n                        \\_blockheader, 0x20\\n                    ), offset)\\n            )\\n        }\\n```\\n
Vulnerability: BlockhashRegistry - Existing blockhashes can be overwritten
Severity: low

Description:
Last 256 blocks, that are available in the EVM environment, are stored in `BlockhashRegistry` by calling `snapshot()` or `saveBlockNumber(uint _blockNumber)` functions. Older blocks are recreated by calling `recreateBlockheaders`.\\nThe methods will overwrite existing blockhashes.\\n```\\nfunction saveBlockNumber(uint \\_blockNumber) public {\\n\\n    bytes32 bHash = blockhash(\\_blockNumber);\\n\\n    require(bHash != 0x0, "block not available");\\n\\n    blockhashMapping[\\_blockNumber] = bHash;\\n    emit LogBlockhashAdded(\\_blockNumber, bHash);\\n}\\n```\\n\\n```\\nblockhashMapping[bnr] = calculatedHash;\\n```\\n

Impact:

Recommended mitigation:
Resolution\\nAddressed with 80bb6ecf and 17d450cf by checking if blockhash exists and changing the `assert` to `require`.\\nIf a block is already saved in the smart contract, it can be checked and a SSTORE can be prevented to save gas. Require that blocknumber hash is not stored.\\n```\\nrequire(blockhashMapping[\\_blockNumber] == 0x0, "block already saved");\\n```\\n
Vulnerability: An account that confirms a transaction via AssetProxyOwner can indefinitely block that transaction
Severity: high

Description:
When a transaction reaches the required number of confirmations in `confirmTransaction()`, its confirmation time is recorded:\\n```\\n/// @dev Allows an owner to confirm a transaction.\\n/// @param transactionId Transaction ID.\\nfunction confirmTransaction(uint256 transactionId)\\n    public\\n    ownerExists(msg.sender)\\n    transactionExists(transactionId)\\n    notConfirmed(transactionId, msg.sender)\\n    notFullyConfirmed(transactionId)\\n{\\n    confirmations[transactionId][msg.sender] = true;\\n    emit Confirmation(msg.sender, transactionId);\\n    if (isConfirmed(transactionId)) {\\n        \\_setConfirmationTime(transactionId, block.timestamp);\\n    }\\n}\\n```\\n\\nBefore the time lock has elapsed and the transaction is executed, any of the owners that originally confirmed the transaction can revoke their confirmation via revokeConfirmation():\\n```\\n/// @dev Allows an owner to revoke a confirmation for a transaction.\\n/// @param transactionId Transaction ID.\\nfunction revokeConfirmation(uint256 transactionId)\\n    public\\n    ownerExists(msg.sender)\\n    confirmed(transactionId, msg.sender)\\n    notExecuted(transactionId)\\n{\\n    confirmations[transactionId][msg.sender] = false;\\n    emit Revocation(msg.sender, transactionId);\\n}\\n```\\n\\nImmediately after, that owner can call `confirmTransaction()` again, which will reset the confirmation time and thus the time lock.\\nThis is especially troubling in the case of a single compromised key, but it's also an issue for disagreement among owners, where any m of the n owners should be able to execute transactions but could be blocked.\\nMitigations\\nOnly an owner can do this, and that owner has to be part of the group that originally confirmed the transaction. This means the malicious owner may have to front run the others to make sure they're in that initial confirmation set.\\nEven once a malicious owner is in position to execute this perpetual delay, they need to call `revokeConfirmation()` and `confirmTransaction()` again each time. Another owner can attempt to front the attacker and execute their own `confirmTransaction()` immediately after the `revokeConfirmation()` to regain control.

Impact:

Recommended mitigation:
There are several ways to address this, but to best preserve the original `MultiSigWallet` semantics, once a transaction has reached the required number of confirmations, it should be impossible to revoke confirmations. In the original implementation, this is enforced by immediately executing the transaction when the final confirmation is received.
Vulnerability: Reentrancy in executeTransaction()  Won't Fix
Severity: medium

Description:
In `MixinTransactions`, `executeTransaction()` and `batchExecuteTransactions()` do not have the `nonReentrant` modifier. Because of that, it is possible to execute nested transactions or call these functions during other reentrancy attacks on the exchange. The reason behind that decision is to be able to call functions with `nonReentrant` modifier as delegated transactions.\\nNested transactions are partially prevented with a separate check that does not allow transaction execution if the exchange is currently in somebody else's context:\\n```\\n// Prevent `executeTransaction` from being called when context is already set\\naddress currentContextAddress\\_ = currentContextAddress;\\nif (currentContextAddress\\_ != address(0)) {\\n    LibRichErrors.rrevert(LibExchangeRichErrors.TransactionInvalidContextError(\\n        transactionHash,\\n        currentContextAddress\\_\\n    ));\\n}\\n```\\n\\nThis check still leaves some possibility of reentrancy. Allowing that behavior is dangerous and may create possible attack vectors in the future.

Impact:

Recommended mitigation:
Add a new modifier to `executeTransaction()` and `batchExecuteTransactions()` which is similar to `nonReentrant` but uses different storage slot.
Vulnerability: “Poison” order that consumes gas can block market trades  Won't Fix
Severity: medium

Description:
The market buy/sell functions gather a list of orders together for the same asset and try to fill them in order until a target amount has been traded.\\nThese functions use `MixinWrapperFunctions._fillOrderNoThrow()` to attempt to fill each order but ignore failures. This way, if one order is unfillable for some reason, the overall market order can still succeed by filling other orders.\\nOrders can still force `_fillOrderNoThrow()` to revert by using an external contract for signature validation and having that contract consume all available gas.\\nThis makes it possible to advertise a “poison” order for a low price that will block all market orders from succeeding. It's reasonable to assume that off-chain order books will automatically include the best prices when constructing market orders, so this attack would likely be quite effective. Note that such an attack costs the attacker nothing because all they need is an on-chain contract that consumers all available gas (maybe via an assert). This makes it a very appealing attack vector for, e.g., an order book that wants to temporarily disable a competitor.\\nDetails\\n`_fillOrderNoThrow()` forwards all available gas when filling the order:\\n```\\n// ABI encode calldata for `fillOrder`\\nbytes memory fillOrderCalldata = abi.encodeWithSelector(\\n    IExchangeCore(address(0)).fillOrder.selector,\\n    order,\\n    takerAssetFillAmount,\\n    signature\\n);\\n\\n(bool didSucceed, bytes memory returnData) = address(this).delegatecall(fillOrderCalldata);\\n```\\n\\nSimilarly, when the `Exchange` attempts to fill an order that requires external signature validation (Wallet, `Validator`, or `EIP1271Wallet` signature types), it forwards all available gas:\\n```\\n(bool didSucceed, bytes memory returnData) = verifyingContractAddress.staticcall(callData);\\n```\\n\\nIf the verifying contract consumes all available gas, it can force the overall transaction to revert.\\nPedantic Note\\nTechnically, it's impossible to consume all remaining gas when called by another contract because the EVM holds back a small amount, but even at the block gas limit, the amount held back would be insufficient to complete the transaction.

Impact:

Recommended mitigation:
Constrain the gas that is forwarded during signature validation. This can be constrained either as a part of the signature or as a parameter provided by the taker.
Vulnerability: Front running in matchOrders()  Won't Fix
Severity: medium

Description:
Calls to `matchOrders()` are made to extract profit from the price difference between two opposite orders: left and right.\\n```\\nfunction matchOrders(\\n    LibOrder.Order memory leftOrder,\\n    LibOrder.Order memory rightOrder,\\n    bytes memory leftSignature,\\n    bytes memory rightSignature\\n)\\n```\\n\\nThe caller only pays protocol and transaction fees, so it's almost always profitable to front run every call to `matchOrders()`. That would lead to gas auctions and would make `matchOrders()` difficult to use.

Impact:

Recommended mitigation:
Consider adding a commit-reveal scheme to `matchOrders()` to stop front running altogether.
Vulnerability: The Exchange owner should not be able to call executeTransaction or batchExecuteTransaction  Won't Fix
Severity: medium

Description:
If the owner calls either of these functions, the resulting `delegatecall` can pass `onlyOwner` modifiers even if the transaction signer is not the owner. This is because, regardless of the `contextAddress` set through `_executeTransaction`, the `onlyOwner` modifier checks `msg.sender`.\\n`_executeTransaction` sets the context address to the signer address, which is not `msg.sender` in this case:\\n```\\n// Set the current transaction signer\\naddress signerAddress = transaction.signerAddress;\\n\\_setCurrentContextAddressIfRequired(signerAddress, signerAddress);\\n```\\n\\nThe resulting `delegatecall` could target an admin function like this one:\\n```\\n/// @dev Registers an asset proxy to its asset proxy id.\\n/// Once an asset proxy is registered, it cannot be unregistered.\\n/// @param assetProxy Address of new asset proxy to register.\\nfunction registerAssetProxy(address assetProxy)\\n    external\\n    onlyOwner\\n{\\n    // Ensure that no asset proxy exists with current id.\\n    bytes4 assetProxyId = IAssetProxy(assetProxy).getProxyId();\\n    address currentAssetProxy = \\_assetProxies[assetProxyId];\\n    if (currentAssetProxy != address(0)) {\\n        LibRichErrors.rrevert(LibExchangeRichErrors.AssetProxyExistsError(\\n            assetProxyId,\\n            currentAssetProxy\\n        ));\\n    }\\n  \\n    // Add asset proxy and log registration.\\n    \\_assetProxies[assetProxyId] = assetProxy;\\n    emit AssetProxyRegistered(\\n        assetProxyId,\\n        assetProxy\\n    );\\n}\\n```\\n\\nThe `onlyOwner` modifier does not check the context address, but checks msg.sender:\\n```\\nfunction \\_assertSenderIsOwner()\\n    internal\\n    view\\n{\\n    if (msg.sender != owner) {\\n        LibRichErrors.rrevert(LibOwnableRichErrors.OnlyOwnerError(\\n            msg.sender,\\n            owner\\n        ));\\n    }\\n}\\n```\\n

Impact:

Recommended mitigation:
Add a check to `_executeTransaction` that prevents the owner from calling this function.