Table of Contents
Summary
- Type
- DeFi
- Timeline
- From 2024-12-18
- To 2024-12-20
- Languages
- Solidity
- Total Issues
- 11 (11 resolved)
- Critical Severity Issues
- 0 (0 resolved)
- High Severity Issues
- 1 (1 resolved)
- Medium Severity Issues
- 1 (1 resolved)
- Low Severity Issues
- 5 (5 resolved)
- Notes & Additional Information
- 4 (4 resolved)
- Client Reported Issues
- 0 (0 resolved)
Scope
We performed an incremental audit on the forta-network/forta-firewall-contracts repository between commits c42acc8
and 09feff1
.
In scope were the following files:
src/
├── AttesterWallet.sol
├── CheckpointExecutor.sol
├── ExternalFirewall.sol
├── Firewall.sol
├── FirewallPermissions.sol
├── FirewallRouter.sol
├── SecurityValidator.sol
├── TrustedAttesters.sol
└── interfaces
├── FirewallDependencies.sol
├── IAttesterWallet.sol
├── ICheckpointHook.sol
├── IExternalFirewall.sol
├── IFirewall.sol
├── ISecurityValidator.sol
└── ITrustedAttesters.sol
System Overview
This diff-audit focused on the primary changes introduced by the Forta team:
- Addition of the
AttesterWallet
Contract: An ERC-20 upgradeable contract with extra functionality. It maintains native currency balances, called FORTAGAS, per user transaction origin and spends them when an attester stores an attestation on behalf of such an origin. - Creation of the
FirewallRouter
Contract: Introduced to enhance modularity and upgradeability for firewall management. It enables the firewall to be upgraded, allowing integrator contracts to point to theFirewallRouter
instead of a specific firewall instance. - Creation of the
TrustedAttesters
Contract: A centralized registry for managing trusted attesters. It improves security by employing role-based access control mechanisms, ensuring only authorized attesters can interact with the system. - Modification to the
SecurityValidator
Contract: ThestoreAttestationForOrigin
function has been added, allowing the storage of attestations tied to specific origins. This feature simplifies attestation handling and adds flexibility to the system. - Deletion of the
AttestationForwarder
Contract: This contract has been removed. - Upgrade of OpenZeppelin Contracts Library: The OpenZeppelin library has been upgraded to version 5.1.0 and now utilizes the
TransientSlot
library instead ofStorageSlot
for managing transient storage.
Privileged Roles
Two new contracts introduce or extend privileged roles:
DEFAULT_ADMIN_ROLE
: Can grant and revoke roles such as Attester Managers and upgrade theSecurityValidator
address on theAttesterWallet
contract.ATTESTER_MANAGER_ROLE
: Can grant and revoke theTRUSTED_ATTESTER
role withinTrustedAttesters
.TRUSTED_ATTESTER_ROLE
: Can store attestations for abeneficiary
and get reimbursed for an arbitrarychargeAmount
from an arbitrarychargeAccount
.
Security Model and Trust Assumptions
- Trusted attesters are assumed not to misbehave by spending arbitrary users' FORTAGAS to store attestations.
- Trusted attesters are assumed not to spend more FORTAGAS than necessary to store attestations.
- Trusted attesters are assumed not to use attestations signed by an arbitrary account. This is currently not enforced, as it is pending confirmation that only trusted attesters can sign them when storing.
- The owner of the
TrustedAttesters
contract effectively holds the trusted attester role, as the contract is upgradable. The owner must retain the trusted attester role to avoid reversion of the store attestation call to the security validator contract.
High Severity
Overflow in quantize
Function Can Cause a DoS
The quantize
function contains a vulnerability caused by an intermediate overflow during sequential operations in the return statement:
return ((n >> offset) << offset) + (2 ** offset) - 1;
When offset = 256
(calculated as 8 * Math.log256(n)
for sufficiently large n
), the operation 2 ** offset
results in an overflow since 2^256
exceeds the maximum value of a uint256
. This overflow affects the intermediate result of the addition:
((n >> offset) << offset) + (2 ** offset)
Because Solidity performs this addition first, the overflow in the intermediate result causes the function to revert when it encounters an overflow. The subtraction - 1
is never reached, as the overflow prevents further execution.
Consider refactoring the formula so that the full second term (2 ** offset - 1)
is calculated first, in order to prevent the overflow. Additionally, consider adding fuzz tests to your test suite in order to detect edge cases in this quantize
calculation.
Update: Resolved in pull request #47.
Medium Severity
Applying Comparison to Raw Signed Integers Causes Unexpected Results
The _secureExecution
function in the Firewall
contract has a vulnerability that arises when the byteRange
extracted from calldata represents a signed integer. In this case, the data is cast to a uint256
, resulting in a loss of the original signedness.
If the byteRange
represents a negative integer, its two's complement representation is treated as a large positive unsigned value when converted to uint256
. This can lead to unexpected behavior during threshold checks in _checkpointActivated
, such as ref >= checkpoint.threshold
. Negative values, once interpreted as large unsigned integers, could either incorrectly satisfy or fail the comparison, bypassing the intended validation logic.
Consider properly documenting the behavior of calldata extraction and casting, including its implications on signedness, to ensure the firewall can reliably protect functions handling various data types.
Update: Resolved in pull request #48.
Low Severity
Missing Docstrings
Throughout the codebase, multiple instances of missing docstrings were identified:
- In
AttesterWallet.sol
, theinitialize
function - In
FirewallRouter.sol
, thefirewall
state variable - In
FirewallRouter.sol
, theupdateFirewall
function
Consider thoroughly documenting all functions (and their parameters) that are part of any contract's public API. Functions implementing sensitive functionality, even if not public, should be clearly documented as well. When writing docstrings, consider following the Ethereum Natural Specification Format (NatSpec).
Update: Resolved in pull request #49.
Missing Zero Address Checks
When assigning an address from a user-provided parameter, it is crucial to ensure the address is not set to zero. Setting an address to zero is problematic because it has special burn/renounce semantics. This action should be handled by a separate function to prevent accidental loss of access during value or ownership transfers.
Throughout the codebase, there are multiple instances where assignment operations are missing a zero address check:
- The
beneficiary
and_securityValidator
assignment operations within the contractAttesterWallet
. - The
origin
assignment operation within the contractSecurityValidator
.
Consider adding a zero address check before assigning a state variable.
Update: Resolved in pull request #50.
Incomplete Docstrings
Throughout the codebase, multiple instances of incomplete docstrings were identified:
- In
AttesterWallet.sol
, thewithdraw
function parametersamount
andbeneficiary
are not documented. - In
AttesterWallet.sol
, thewithdrawAll
function parameterbeneficiary
is not documented. - In
AttesterWallet.sol
, thestoreAttestationForOrigin
function parameterschargeAccount
andchargeAmount
are not documented. - In
CheckpointExecutor.sol
, theattestedCall
function does not have all return values documented. - In
ExternalFirewall.sol
, bothexecuteCheckpoint
functions ([1], [2]) parametercaller
are not documented. - In
Firewall.sol
, the functionsgetAttesterControllerId
,getCheckpoint
andattestedCall
do not have all return values documented. - In
FirewallRouter.sol
, bothexecuteCheckpoint
functions ([1], [2] parametercaller
are not documented. - In
SecurityValidator.sol
, the functionsgetCurrentAttester
,hashAttestation
,executeCheckpoint
andexecutionHashFrom
do not have all return values documented. - In
TrustedAttesters.sol
, theisTrustedAttester
function does not have all return values documented.
Consider thoroughly documenting all functions/events (and their parameters or return values) that are part of a contract's public API. When writing docstrings, consider following the Ethereum Natural Specification Format (NatSpec).
Update: Resolved in pull request #51.
Lack of Input Validation
Within AttesterWallet
, all functions should ensure that amounts are not zero.
Consider implementing input validations in functions where parameters must be confined within specific boundaries. Furthermore, ensure that variables used across different functions are checked against the same boundaries to maintain consistency and integrity.
Update: Resolved in pull request #52.
Inability to Modify Firewall Access After Contract Deployment
The FirewallRouter
contract inherits from FirewallPermissions
, which provides an internal _updateFirewallAccess
function to update the firewallAccess
variable. While the firewallAccess
is updated in the constructor, there is no explicit function in FirewallRouter
to invoke this internal function, making it impossible to update the firewallAccess
variable after contract deployment.
Consider implementing a restricted function to allow updates to firewallAccess
after deployment in order to improve flexibility.
Update: Resolved in pull request #53.
Notes & Additional Information
Lack of License Specification
The following instances were found where no license or a wrong one was specified:
TrustedAttesters.sol
contractITrustedAttesters.sol
interfaceSecurityValidator.sol
contract
The use of // SPDX-License-Identifier: UNLICENSED
in the code indicates that no explicit license has been specified. This has significant implications:
- Restricted Usage by Default: Without a license, others are not granted permission to copy, modify, or distribute the code. Copyright laws automatically apply, protecting the code, and any use without permission could be considered infringement.
- Enforceability Concerns: Even though the code is legally protected, the absence of a clear license might make it harder to enforce these rights. Some users may mistakenly believe that the code is open for use due to the lack of a license statement.
- No Attribution Requirements: Since no license is provided, there are no conditions like attribution requirements. However, others are not permitted to use the code at all without explicit permission from the author.
To ensure control over how your code is used, consider specifying a license and enforcing consistency on the license specified across the rest of the contracts. A license provides legal protection by establishing clear usage terms, which helps prevent misuse and encourages collaboration.
Update: Resolved in pull request #54.
Use Custom Errors
Since Solidity version 0.8.4, custom errors provide a cleaner and more cost-efficient way to explain to users why an operation failed.
Throughout the codebase, instances of revert and/or require messages were found. For instance:
- In the
AttesterWallet.sol
file, therequire
statement with the message "sender is not a trusted attester". This error is also inconsistent with the same one raised on theSecurityValidator
contract. Consider emitting the same custom error instead. - In the
Firewall.sol
file, therequire
statement with the message "refStart is larger than refEnd". - In the
FirewallPermissions.sol
file, therequire
statement with the message "caller is not firewall admin". - In the
FirewallPermissions.sol
file, therequire
statement with the message "caller is not checkpoint manager". - In the
FirewallPermissions.sol
file, therequire
statement with the message "caller is not logic upgrader". - In the
FirewallPermissions.sol
file, therequire
statement with the message "caller is not checkpoint executor". - In the
FirewallPermissions.sol
file, therequire
statement with the message "new firewall access contract cannot be zero address".
For conciseness and gas savings, consider replacing require and revert messages with custom errors.
Update: Resolved in pull request #55.
State Variable Visibility Not Explicitly Declared
Within SecurityValidator.sol, the trustedAttesters
state variable lacks an explicitly declared visibility.
For improved code clarity, consider always explicitly declaring the visibility of state variables, even when the default visibility matches the intended visibility.
Update: Resolved in pull request #56.
Unused Named Return Variables
Named return variables are a way to declare variables that are meant to be used within a function's body for the purpose of being returned as that function's output. They are an alternative to explicit in-line return
statements.
Within Firewall.sol
, multiple instances of unused named return variables were identified:
- The
validator
return variable of thegetFirewallConfig
function - The
checkpointHook
return variable of thegetFirewallConfig
function - The
attesterControllerId
return variable of thegetFirewallConfig
function
Consider either using or removing any unused named return variables.
Update: Resolved in pull request #57.
Conclusion
Throughout the audit process, a few issues were identified, leading to recommendations aimed at improving code consistency, readability, and gas efficiency.
In addition to these recommendations, the codebase would benefit from a strengthened test suite, including fuzz tests to identify critical vulnerabilities, as well as an expansion of unit tests to achieve a test coverage rate above 95%. This is essential for adhering to the highest standards of security and reliability, thereby significantly enhancing code safety.
The Forta team's approach to the audit process was highly commendable. Their prompt responses, willingness to engage in discussions about the findings, and dedication to improving their product's security were particularly noteworthy. Despite the issues identified, the potential of the Forta Firewall remains strong, with its capability to significantly enhance DeFi security.