Fix Verification
Rigorously verify that a reported vulnerability has been eliminated without introducing regressions or new issues. This skill ensures fixes are complete, tested, and safe to deploy.
When to Use
- •Reviewing security patches before deployment
- •Validating bug fix implementations
- •Confirming audit finding remediations
- •Re-testing after fix iterations
1. The Verification Loop
Step 1: Reproduce the Bug
Create a test scenario that demonstrates the vulnerability:
json
// scenarios/exploit_before_fix.scen.json
{
"name": "Demonstrate vulnerability - should fail before fix",
"steps": [
{
"step": "scCall",
"comment": "Attacker exploits the vulnerability",
"tx": {
"from": "address:attacker",
"to": "sc:vulnerable_contract",
"function": "vulnerable_endpoint",
"arguments": ["...exploit_payload..."],
"gasLimit": "5,000,000"
},
"expect": {
"status": "0",
"message": "*"
}
}
]
}
Step 2: Apply the Fix
Review the code modification that addresses the vulnerability.
Step 3: Verify Fix Effectiveness
Run the exploit scenario - it MUST now fail (or behave correctly):
bash
# The exploit scenario should now pass (exploit blocked) sc-meta test --scenario scenarios/exploit_before_fix.scen.json
Step 4: Run Regression Suite
ALL existing tests must still pass:
bash
# Full test suite sc-meta test # Or with cargo cargo test
2. Common Fix Failures
Partial Fix
The fix addresses one path but misses variants:
rust
// VULNERABILITY: Missing amount validation
#[endpoint]
fn deposit(&self) {
let amount = self.call_value().egld_value();
// No check for amount > 0
}
// PARTIAL FIX: Only fixed deposit, not transfer
#[endpoint]
fn deposit(&self) {
let amount = self.call_value().egld_value();
require!(amount > 0, "Amount must be positive"); // Fixed!
}
#[endpoint]
fn transfer(&self, amount: BigUint) {
// Still missing amount > 0 check! <- VARIANT NOT FIXED
}
Verification: Use multiversx-variant-analysis to find all similar code paths.
Moved Bug (Fix Creates New Issue)
The fix prevents the original exploit but introduces a new vulnerability:
rust
// VULNERABILITY: Reentrancy
#[endpoint]
fn withdraw(&self) {
let balance = self.balance().get();
self.send_egld(&caller, &balance); // External call before state update
self.balance().clear();
}
// BAD FIX: Prevents reentrancy but creates DoS
#[endpoint]
fn withdraw(&self) {
self.locked().set(true); // Lock added
let balance = self.balance().get();
self.send_egld(&caller, &balance);
self.balance().clear();
// Missing: self.locked().set(false); <- LOCK NEVER RELEASED!
}
// CORRECT FIX: Checks-Effects-Interactions pattern
#[endpoint]
fn withdraw(&self) {
let balance = self.balance().get();
self.balance().clear(); // State update BEFORE external call
self.send_egld(&caller, &balance);
}
Incomplete Validation
The fix adds validation but with incorrect conditions:
rust
// VULNERABILITY: Integer overflow
let total = amount1 + amount2; // Can overflow
// INCOMPLETE FIX: Checks one but not both
require!(amount1 < MAX_AMOUNT, "Amount1 too large");
let total = amount1 + amount2; // Still overflows if amount2 is large!
// CORRECT FIX: Checked arithmetic
let total = amount1.checked_add(&amount2)
.unwrap_or_else(|| sc_panic!("Overflow"));
3. Verification Checklist
Code Review
- • Fix addresses the root cause, not just symptoms
- • All code paths with similar patterns are fixed (variant analysis)
- • No new vulnerabilities introduced by the fix
- • Fix follows MultiversX best practices
Testing
- • Exploit scenario created that fails on vulnerable code
- • Exploit scenario passes (blocked) on fixed code
- • All existing tests pass (no regressions)
- • Edge cases tested (boundary values, empty inputs, max values)
Documentation
- • Fix commit clearly describes the vulnerability
- • Test scenario documents the attack vector
- • Any behavioral changes documented
4. Test Scenario Template
json
{
"name": "Verify fix for [VULNERABILITY_ID]",
"comment": "This scenario verifies that [DESCRIPTION] is properly fixed",
"steps": [
{
"step": "setState",
"comment": "Setup vulnerable state",
"accounts": {
"address:attacker": { "nonce": "0", "balance": "1000" },
"sc:contract": { "code": "file:output/contract.wasm" }
}
},
{
"step": "scCall",
"comment": "Attempt exploit - should fail after fix",
"tx": {
"from": "address:attacker",
"to": "sc:contract",
"function": "vulnerable_function",
"arguments": ["exploit_input"]
},
"expect": {
"status": "4",
"message": "str:Expected error message"
}
},
{
"step": "checkState",
"comment": "Verify state unchanged (exploit blocked)",
"accounts": {
"sc:contract": {
"storage": {
"str:sensitive_value": "original_value"
}
}
}
}
]
}
5. Deliverable: Verification Report
markdown
# Fix Verification Report ## Vulnerability Reference - **ID**: [CVE/Internal ID] - **Severity**: [Critical/High/Medium/Low] - **Description**: [Brief description] ## Fix Details - **Commit**: [git commit hash] - **Files Changed**: [list of files] - **Approach**: [Description of fix approach] ## Verification Results ### Exploit Reproduction - [ ] Exploit scenario created: `scenarios/[name].scen.json` - [ ] Scenario fails on vulnerable code (commit: [hash]) - [ ] Scenario passes on fixed code (commit: [hash]) ### Regression Testing - [ ] All existing tests pass - [ ] No new warnings from `cargo clippy` - [ ] Gas costs within acceptable range ### Variant Analysis - [ ] Searched for similar patterns using `multiversx-variant-analysis` - [ ] All variants addressed: [list or "none found"] ## Conclusion **Status**: [VERIFIED / NEEDS WORK / REJECTED] **Notes**: [Any additional observations] **Signed**: [Reviewer name, date]
6. Red Flags During Verification
- •Fix is overly complex for the issue
- •Fix changes unrelated code
- •No test added for the specific vulnerability
- •Fix relies on external assumptions
- •Gas cost increased significantly
- •Access control modified without clear justification