Penetration Test Report (Consensys): SimpleMultisigWallet Audit

Simple MultiSig Wallet Audit
1 Summary
1.1 Key Observations/Recommendations
1.2 Audit Dashboard
1.3 Audit Goals
1.4 System Overview
2 Issue Overview
3 Issue Detail
3.1 Signed data should include the sender
3.2 Signed data should include the gas limit
3.3 Failed transactions should not be replayable
3.4 A reasonable threshold should be required
3.5 Consider whitelisting function calls to standard ERC20/ERC223 functions
3.6 Use the latest Solidity compiler
3.7 Passing multiple arguments to keccak256 is deprecated
3.8 The execute function can be external rather than public
4 Threat Model
4.1 Overview
4.2 Threat Analysis
5 Tool based analysis
5.1 Mythril
5.2 Solhint
5.3 Surya
6 Test Coverage Measurement
Appendix 1 ‑ File Hashes
Appendix 2 ‑ Severity
A.2.1 ‑ Minor
A.2.2 ‑ Medium
A.2.3 ‑ Major
A.2.4 ‑ Critical
Appendix 3 ‑ Disclosure
1 Summary
ConsenSys Diligence conducted a security audit for the contract. The contract will at first be used to store and
SimpleMultiSig
transfer ether. It may be used to store and transfer tokens or other assets in the future.
Number of issues per severity
1 0 0 0
3 4 0 0
1.1 Key Observations/Recommendations
Simple contract design: The contract design is excellent. The simplicity of the contract means there's a very small attack
surface.
Clear code: The code is well written and commented.
The signatures used with this wallet lock down key parameters of a transaction, such as the recipient and payload, but they leave the
following parameters unconstrained:
the initiator of the transaction
the gas limit supplied
the timing of the transaction
These free parameters are the primary surface area for an attacker. The recommended changes detailed in section 3 mostly relate to
reducing the attack surface area by constraining these remaining parameters. In some cases, reducing the surface area also
reduces functionality. For a wallet that will control assets of considerable value, we believe that tradeoff is worthwhile.
1.2 Audit Dashboard
Audit Details
Project Name: Simple MultiSig Wallet
Auditors: Steve Marx, Dean Pierce
Internal Reviewer: J. Maurelian
Languages: Solidity
Date: 2018‑09‑11 to 2018‑09‑24
1.3 Audit Goals
The focus of the audit was to verify that the smart contract system is secure, resilient and working according to its specifications. The
audit activities can be grouped in the following three categories:
Security: Identifying security related issues within each contract and within the system of contracts.
Sound Architecture: Evaluation of the architecture of this system through the lens of established smart contract best practices and
general software best practices.
Code Correctness and Quality: A full review of the contract source code. The primary areas of focus include:
Correctness
Readability
Sections of code with high complexity
Improving scalability
Quantity and quality of test coverage
1.4 System Overview
Scope
The scope of this audit was the single file .
SimpleMultiSig-v1.0.4
Design
The contract is an m‑of‑n multisig wallet that uses minimal usable state. Signatures are gathered offline and
SimpleMultiSig
submitted together to execute a transaction.
2 Issue Overview
The following table contains all the issues discovered during the audit. The issues are ordered based on their severity. More detailed
description on the levels of severity can be found in Appendix 2. The table also contains the GitHub status of any discovered issue.
Chapter Issue Title Issue Status Severity
3.1 Signed data should include the sender
3.2 Signed data should include the gas limit
3.3 Failed transactions should not be replayable
3.4 A reasonable threshold should be required
3.5 Consider whitelisting function calls to standard ERC20/ERC223 functions
3.6 Use the latest Solidity compiler
3.7 Passing multiple arguments to keccak256 is deprecated
3.8 The execute function can be external rather than public
3 Issue Detail
3.1 Signed data should include the sender
Severity Status Link Remediation Comment
This was fixed in https://github.com/christianlundkvist/simple‑
issues/32
multisig/commit/179a6834e80a39f5f709a6a92107bec2673aa89e.
Description
can be called by anyone in possession of valid signatures. This opens up a front‑running opportunity where an
execute() threshold
attacker observes a desired call to and sends their own transaction with the same signatures. This changes the
execute() tx.origin
for the transaction, which may affect the result.
Furthermore, locking down the sender to a trusted (by the owners) party could mitigate cross‑chain replay attacks by not allowing an
attacker to submit the transaction on the other chain.
Example
Below is a contrived example of a target contract that an attacker could manipulate by front‑running with their own transaction:
function() public payable {
// Behavior differs according to tx.origin.
if (tx.origin == owner) {
// Bad attempt at a refund?
tx.origin.transfer(msg.value);
} else {
// Track how much ether has been deposited
balance += msg.value;
}
}
Remediation
Add to the signed data.
msg.sender
3.2 Signed data should include the gas limit
Severity Status Link Remediation Comment
This is considered mitigated by adding the to the signature in
executor
https://github.com/christianlundkvist/simple‑
issues/31 multisig/commit/179a6834e80a39f5f709a6a92107bec2673aa89e. The gas limit was also
included, but there is no check for sufficient gas, so this only prevents issues involving
providing too much gas.
Description
can be called by anyone in possession of valid signatures. The signatures do not specify how much gas will be
execute() threshold
forwarded to the destination address. This allows an attacker, by front‑running a legitimate call, to make the same call with a different
amount of gas.
If the executed call succeeds but behaves differently based on the amount of gas provided, an attacker could use this technique to
affect the outcome.
Example
Below is a contrived example of a target contract that fails to transfer funds if insufficient gas is provided. An attacker maliciously
lowering the provided gas could result in locked ether or simply a denial of service attack against the wallet owners.
function withdraw(address destination) external {
uint256 amount = balance;
balance = 0;
// Silently fails with insufficient gas
destination.call.value(amount)();
}
Remediation
Add the gas limit as a parameter to and include that parameter in the signature.
execute()
3.3 Failed transactions should not be replayable
Severity Status Link Remediation Comment
The project team considers this unnecessary due to the addition of an in the
executor
issues/30 signed data in https://github.com/christianlundkvist/simple‑
multisig/commit/179a6834e80a39f5f709a6a92107bec2673aa89e.
Description
If a call to with proper signatures fails because the resulting does, the is not incremented. Until the is
execute() call nonce nonce
used for a different transaction, anyone can attempt that original transaction again. If the transaction has different behavior in the
future (e.g. because the destination contract was destroyed or a crowdsale moved to a different phase), this may be an opportunity for
an attacker to make mischief.
Example
Below is a contrived example of a target contract that reveals this vulnerability. The initial call to with attached ether will fail,
reverts
but if the contract is later ed, replaying that transaction would result in lost ether, as there would be no code to revert
selfdestruct
the transaction.
function reverts() external payable {
revert();
}
function kill() external {
require(msg.sender == owner);
selfdestruct(msg.sender);
}
Remediation
We suggest two changes to address this vulnerability:
1. As suggested in other issues, specify the transaction sender in the signature. This means that only the sender can retry a failed
transaction. Depending on the trust model and security of the sender, this may be sufficient.
2. Increment the nonce even when the fails. (Remove .) Note that this should only