Assembly Analysis
Overview
Remediation Guide: Assembly Analysis Remediation
Inline assembly bypasses every safety feature Solidity provides: overflow checks, memory bounds, return-value handling, and the free memory pointer discipline. The assembly analysis detector inspects code regions characteristic of hand-written assembly and reports eight distinct finding types, each with its own severity:
| Finding | Severity |
|---|---|
Dangerous opcode (selfdestruct, delegatecall) in assembly |
Critical |
| Arbitrary jump destination | Critical |
| Unchecked return value from a low-level call | High |
| Missing success check after an external call | High |
| Direct storage write without validation | High |
| Unsafe memory access (unvalidated bounds) | Medium |
| Return data used without a size check | Medium |
| Unchecked arithmetic (no overflow guard) | Medium |
The detector’s default severity is high; individual findings carry the severity from the table above.
Why This Is an Issue
Assembly is where compiler protections end. A delegatecall in an assembly block executes foreign code in the caller’s storage context — the pattern behind the 2017 Parity wallet incidents. An arbitrary jump whose destination derives from calldata lets an attacker redirect control flow past access checks. Unchecked arithmetic in assembly silently wraps even on Solidity 0.8+, because checked math does not apply inside assembly { }.
Less severe but still costly: calls whose success flag is dropped fail silently, and returndatacopy without a returndatasize bound can read garbage or revert unexpectedly.
How to Resolve
Re-add the checks the compiler would have inserted, and prefer Solidity over assembly when the gas saving is marginal.
// Before: success flag dropped, no returndata bound
function forward(address target, bytes memory data) external {
assembly {
pop(call(gas(), target, 0, add(data, 0x20), mload(data), 0, 0))
}
}
// After: success checked, returndata bounded
function forward(address target, bytes memory data) external returns (bytes memory out) {
assembly {
let ok := call(gas(), target, 0, add(data, 0x20), mload(data), 0, 0)
if iszero(ok) { revert(0, 0) }
out := mload(0x40)
let size := returndatasize()
mstore(out, size)
returndatacopy(add(out, 0x20), 0, size)
mstore(0x40, add(add(out, 0x20), size))
}
}
Examples
Vulnerable Code
contract Unsafe {
function drainOnCommand(address target) external {
assembly {
// Critical: selfdestruct reachable from an unprotected external function
selfdestruct(target)
}
}
}
Fixed Code
contract Safer {
address immutable owner = msg.sender;
function shutdown(address payable target) external {
require(msg.sender == owner, "not owner");
// Prefer an explicit sweep over selfdestruct
target.transfer(address(this).balance);
}
}
Sample Sigvex Output
{
"detector_id": "assembly-analysis",
"severity": "critical",
"confidence": 0.45,
"description": "Dangerous opcode in assembly block: DELEGATECALL at offset 0x9e executes with a target that is not access-controlled.",
"location": { "function": "forward(address,bytes)", "offset": 158 }
}
Detection Methodology
The detector works on decompiled bytecode, classifying instruction patterns characteristic of assembly blocks: raw call sequences whose success flag is never consumed, storage writes with unvalidated keys, jumps whose destination is data-dependent, and arithmetic with no guard branch. Findings are structural pattern matches, so they start at a conservative confidence base. Confidence is then reduced — not removed — when a mitigating context is discovered: access control guarding the assembly path, or a delegatecall inside a recognized proxy/UUPS upgrade function where it is the expected mechanism. Audited library code (OpenZeppelin, Solmate, Solady) is similarly discounted.
Limitations
- Confidence is pattern-based; assembly that is correct but unusual (custom memory allocators, packed encoders) can be flagged at reduced confidence.
- Upgrade-flow suppression is keyed to known upgrade function names (
upgradeTo,setImplementation, and similar); a proxy with a nonstandard entry point is reported at full confidence. - Initializer functions are deliberately not suppressed — a
delegatecallinside an initializer is a real risk, not expected behavior.
Related Detectors
- Unsafe Delegatecall — delegatecall with mutable, attacker-influenced targets
- Arbitrary Storage — writes to attacker-controlled storage slots
- Integer Overflow — arithmetic safety outside assembly