-
Notifications
You must be signed in to change notification settings - Fork 2
Description
SPBEAMHaltSpell.sol - Security Audit Report
High Severity Vulnerabilities
1. Missing Access Control Verification in Emergency Actions
Severity: High
Description:
The _emergencyActions() function relies entirely on inherited access control from DssEmergencySpell without any explicit verification. While the function is marked as internal and override, there's no guarantee that the parent contract implements proper authorization checks. If DssEmergencySpell doesn't properly restrict who can trigger emergency actions, any unauthorized party could potentially halt the SPBEAM module, causing a denial of service to the protocol.
Impact:
Unauthorized parties could halt critical SPBEAM functionality, disrupting the MakerDAO stability parameter module and potentially affecting protocol operations.
Attack Scenario:
- Attacker calls the public/external function in DssEmergencySpell
- This triggers
_emergencyActions()in SPBEAMHaltSpell - SPBEAM gets halted without authorization
- Protocol functionality is disrupted
Affected Code:
function _emergencyActions() internal override {
spbeamMom.halt(address(spbeam));
emit Halt();
}Proposed Fix:
function _emergencyActions() internal override {
// Add explicit verification that caller is authorized
require(msg.sender == authorizedGovernance, "Unauthorized");
// Or verify through parent contract
require(isAuthorized(msg.sender), "Not authorized to execute emergency spell");
spbeamMom.halt(address(spbeam));
emit Halt();
}2. Unvalidated External Contract Addresses from Registry
Severity: High
Description:
The contract retrieves critical addresses for spbeamMom and spbeam from _log registry at deployment time without any validation. If the _log registry is compromised, returns zero addresses, or returns addresses of malicious contracts, the spell could either fail silently or interact with unintended contracts. The immutable nature of these addresses means any issues cannot be corrected post-deployment.
Impact:
Contract could be deployed with invalid addresses leading to permanent failure of emergency mechanisms, or worse, interaction with malicious contracts that could exploit the emergency spell privileges.
Attack Scenarios:
Scenario 1: Zero Address
- If
_log.getAddress("SPBEAM_MOM")returnsaddress(0) - Contract deploys successfully
_emergencyActions()will revert when callingaddress(0).halt()- Emergency spell becomes permanently unusable
Scenario 2: Malicious Contract
- Attacker compromises
_logbefore deployment _log.getAddress("SPBEAM_MOM")returns attacker's contract- Emergency spell calls
attacker.halt() - Attacker can execute arbitrary logic
Affected Code:
SPBEAMMomLike public immutable spbeamMom = SPBEAMMomLike(_log.getAddress("SPBEAM_MOM"));
SPBEAMLike public immutable spbeam = SPBEAMLike(_log.getAddress("MCD_SPBEAM"));Proposed Fix:
constructor() {
address _spbeamMom = _log.getAddress("SPBEAM_MOM");
address _spbeam = _log.getAddress("MCD_SPBEAM");
require(_spbeamMom != address(0), "Invalid SPBEAM_MOM address");
require(_spbeam != address(0), "Invalid SPBEAM address");
// Optionally verify contract code hash
require(_spbeamMom.code.length > 0, "SPBEAM_MOM not a contract");
require(_spbeam.code.length > 0, "SPBEAM not a contract");
spbeamMom = SPBEAMMomLike(_spbeamMom);
spbeam = SPBEAMLike(_spbeam);
}Medium Severity Vulnerabilities
3. Silent Failure Masking in done() Function
Severity: Medium
Description:
The done() function returns true for all error cases including when SPBEAMMom is not a ward, when calls revert, or when targeting non-SPBEAM contracts. This design makes it impossible to distinguish between 'successfully halted', 'cannot halt', and 'error' states. External systems relying on this function could incorrectly assume the emergency action succeeded when it actually failed.
Impact:
Governance systems or monitoring tools could incorrectly believe an emergency halt succeeded when it failed, leaving the protocol vulnerable during critical incidents.
Attack Scenario:
- Emergency situation occurs requiring SPBEAM halt
- Governance executes SPBEAMHaltSpell
_emergencyActions()fails because SPBEAMMom lost ward privileges- External monitoring calls
done()which returnstrue - System believes SPBEAM is halted when it's still active
- Vulnerability continues to be exploited
Affected Code:
function done() external view returns (bool) {
try spbeam.wards(address(spbeamMom)) returns (uint256 ward) {
if (ward == 0) {
return true; // Returns true even though halt would fail
}
} catch {
return true; // Returns true on error
}
try spbeam.bad() returns (uint256 bad) {
return bad == 1;
} catch {
return true; // Returns true on error
}
}Proposed Fix:
enum SpellStatus {
NOT_EXECUTED,
EXECUTED_SUCCESS,
CANNOT_EXECUTE,
ERROR
}
function status() external view returns (SpellStatus) {
try spbeam.wards(address(spbeamMom)) returns (uint256 ward) {
if (ward == 0) {
return SpellStatus.CANNOT_EXECUTE;
}
try spbeam.bad() returns (uint256 bad) {
return bad == 1 ? SpellStatus.EXECUTED_SUCCESS : SpellStatus.NOT_EXECUTED;
} catch {
return SpellStatus.ERROR;
}
} catch {
return SpellStatus.ERROR;
}
}4. No Verification of SPBEAMMom Ward Status Before Execution
Severity: Medium
Description:
The _emergencyActions() function doesn't verify that SPBEAMMom has ward privileges on SPBEAM before attempting to halt. If SPBEAMMom has been de-authorized (ward removed) from SPBEAM, the halt call will revert, causing the entire emergency spell to fail. This could happen if governance removes SPBEAMMom's privileges or if there's a configuration error.
Impact:
Emergency spell could fail at a critical moment when SPBEAM needs to be halted, leaving the protocol exposed to ongoing attacks or issues.
Failure Scenario:
- SPBEAMMom is initially a ward of SPBEAM
- Governance removes SPBEAMMom ward status for maintenance
- Emergency occurs requiring SPBEAM halt
- Emergency spell is executed
spbeamMom.halt()reverts because SPBEAMMom is not authorized- Emergency response fails
Affected Code:
function _emergencyActions() internal override {
spbeamMom.halt(address(spbeam)); // No check if spbeamMom is authorized
emit Halt();
}Proposed Fix:
function _emergencyActions() internal override {
// Verify authorization before attempting halt
require(spbeam.wards(address(spbeamMom)) == 1, "SPBEAMMom not authorized");
spbeamMom.halt(address(spbeam));
emit Halt();
}
// Or implement a canExecute() check
function canExecute() public view returns (bool) {
try spbeam.wards(address(spbeamMom)) returns (uint256 ward) {
return ward == 1;
} catch {
return false;
}
}Low Severity Vulnerabilities
5. Potential Reentrancy Through External halt() Call
Severity: Low
Description:
While the _emergencyActions() function makes an external call to spbeamMom.halt() before emitting the Halt event, there's no reentrancy protection. If SPBEAMMom is compromised or upgraded to a malicious implementation, it could reenter the spell contract during the halt() call. Although the current implementation has limited attack surface due to being internal and having no state changes after the call, future modifications or parent contract implementations could introduce vulnerabilities.
Impact:
If SPBEAMMom is malicious or compromised, it could potentially reenter the contract, though current impact is limited due to the function being internal and having no state modifications.
Attack Scenario:
// If SPBEAMMom is malicious:
contract MaliciousSPBEAMMom {
function halt(address target) external {
// Attempt to reenter the spell contract
// Current impact limited, but could exploit parent contract
ISpell(msg.sender).someFunction();
}
}Affected Code:
function _emergencyActions() internal override {
spbeamMom.halt(address(spbeam)); // External call without reentrancy guard
emit Halt(); // Event after external call
}Proposed Fix:
// Add reentrancy protection if parent doesn't provide it
modifier nonReentrant() {
require(!locked, "Reentrant call");
locked = true;
_;
locked = false;
}
function _emergencyActions() internal override nonReentrant {
spbeamMom.halt(address(spbeam));
emit Halt();
}6. Missing Event Parameters for Audit Trail
Severity: Low
Description:
The Halt event doesn't include any parameters such as the halted contract address, the caller, or timestamp. This makes it difficult to track which SPBEAM instance was halted, who initiated it, and when, reducing the effectiveness of monitoring and incident response.
Impact:
Reduced observability and difficulty in tracking emergency actions, making incident response and forensics more challenging.
Issues Without Proper Event Parameters:
Without proper event parameters, monitoring systems cannot:
- Identify which SPBEAM was halted if multiple exist
- Track who initiated the emergency action
- Correlate halt events with specific incidents
- Build proper audit trails for compliance
Affected Code:
event Halt(); // No parameters
function _emergencyActions() internal override {
spbeamMom.halt(address(spbeam));
emit Halt(); // Missing context
}Proposed Fix:
event Halt(
address indexed spbeamAddress,
address indexed initiator,
uint256 timestamp
);
function _emergencyActions() internal override {
spbeamMom.halt(address(spbeam));
emit Halt(address(spbeam), msg.sender, block.timestamp);
}Gas Optimization
7. Gas Inefficiency in done() Function Logic
Severity: Gas Optimization
Description:
The done() function uses nested try-catch blocks that could be optimized. The function makes two separate external calls even when the first one indicates the spell cannot execute. Additionally, checking ward status when it's 0 still proceeds to check bad() status unnecessarily.
Impact:
Unnecessary gas consumption for view function calls, especially when called frequently by monitoring systems.
Scenario:
Current implementation always makes 2 external calls even when the first indicates failure. For frequent monitoring calls, this doubles gas costs unnecessarily.
Affected Code:
function done() external view returns (bool) {
try spbeam.wards(address(spbeamMom)) returns (uint256 ward) {
if (ward == 0) {
return true;
}
} catch {
return true;
}
try spbeam.bad() returns (uint256 bad) {
return bad == 1;
} catch {
return true;
}
}Proposed Fix:
function done() external view returns (bool) {
try spbeam.wards(address(spbeamMom)) returns (uint256 ward) {
if (ward == 0) {
return true; // Early return, no need for second call
}
// Only check bad() if ward check passes
try spbeam.bad() returns (uint256 bad) {
return bad == 1;
} catch {
return true;
}
} catch {
return true;
}
}Summary
Total Vulnerabilities Found: 7
- High Severity: 2
- Medium Severity: 2
- Low Severity: 2
- Gas Optimization: 1