multiversx-variant-analysis
Variant Analysis
Multiply the value of a single vulnerability finding by systematically locating similar issues elsewhere in the codebase. Once you find one bug, this skill helps you find all its "cousins."
When to Use
- After discovering an initial vulnerability
- During comprehensive security audits
- When creating detection patterns for CI/CD
- Before claiming a bug class is fully remediated
- When assessing the extent of a vulnerability pattern
1. The Variant Analysis Process
From Finding to Pattern
1. Find Initial Bug → Specific vulnerability instance
2. Abstract Pattern → What makes this a bug?
3. Create Search → Grep/Semgrep queries
4. Find Variants → All similar occurrences
5. Verify Each → Confirm true positives
6. Report All → Document the bug class
Example Transformation
Initial Bug:
// Found: Missing payment validation in deposit()
#[payable("*")]
fn deposit(&self) {
let payment = self.call_value().single_esdt();
self.balances().update(|b| *b += payment.amount);
// Bug: No token ID validation!
}
Abstract Pattern:
#[payable("*")]endpoint- Uses
call_value()to get payment - Does NOT validate
token_identifier
Search Query:
# Find all payable endpoints
grep -n "#\[payable" src/*.rs
# Then check each for token validation
grep -A 20 "#\[payable" src/*.rs | grep -v "token_identifier"
2. Common MultiversX Variant Patterns
Pattern: Missing Payment Validation
Initial Finding: One endpoint accepts payment but doesn't validate the token.
Variant Search:
# Find all payable endpoints
grep -rn "#\[payable" src/
# Check for missing token validation
# Look for call_value() without subsequent token_identifier check
grep -A 30 "#\[payable" src/*.rs > payable_endpoints.txt
# Manually review each for token_identifier validation
Semgrep Rule:
rules:
- id: mvx-payable-no-token-check
patterns:
- pattern: |
#[payable("*")]
$ANNOTATIONS
fn $FUNC(&self, $...PARAMS) {
$...BODY
}
- pattern-not: |
#[payable("*")]
$ANNOTATIONS
fn $FUNC(&self, $...PARAMS) {
<... token_identifier ...>
}
Pattern: Unbounded Iteration
Initial Finding:
One function iterates over a VecMapper without bounds.
Variant Search:
# Find all .iter() calls on storage mappers
grep -rn "\.iter()" src/
# Find all for loops over storage
grep -rn "for.*in.*self\." src/
Checklist for Each:
- Is iteration bounded?
- Can a user grow the collection?
- Is there pagination?
Pattern: Callback State Assumptions
Initial Finding: One callback doesn't handle the error case.
Variant Search:
# Find all callbacks
grep -rn "#\[callback\]" src/
# Check for proper result handling
grep -A 20 "#\[callback\]" src/*.rs | grep -c "ManagedAsyncCallResult"
All Callbacks Need:
#[callback]
fn any_callback(&self, #[call_result] result: ManagedAsyncCallResult<T>) {
match result {
ManagedAsyncCallResult::Ok(_) => { /* success */ },
ManagedAsyncCallResult::Err(_) => { /* handle failure! */ }
}
}
Pattern: Missing Access Control
Initial Finding:
One admin function lacks #[only_owner].
Variant Search:
# Find functions that modify admin-like storage
grep -rn "admin\|owner\|config\|fee" src/ | grep "\.set("
# Cross-reference with access control
grep -B 10 "admin.*\.set\|config.*\.set" src/*.rs | grep -v "only_owner"
Pattern: Arithmetic Without Checks
Initial Finding:
One calculation uses raw + instead of checked_add.
Variant Search:
# Find all arithmetic operations
grep -rn " + \| - \| \* " src/*.rs
# Exclude test files and comments
grep -rn " + \| - \| \* " src/*.rs | grep -v "test\|//"
3. Systematic Variant Hunting
Step 1: Characterize the Bug
Answer these questions:
- What is the vulnerable code pattern?
- What makes it exploitable?
- What would a fix look like?
Step 2: Create Detection Queries
Grep-based:
# Pattern: [specific code pattern]
grep -rn "[pattern]" src/
# Negative pattern (should be present but isn't)
grep -L "[expected_pattern]" src/*.rs
Semgrep-based:
# See multiversx-semgrep-creator skill for details
rules:
- id: variant-pattern
patterns:
- pattern: <vulnerable pattern>
- pattern-not: <fixed pattern>
Step 3: Triage Results
For each potential variant:
| Result | Classification | Action |
|---|---|---|
| Clearly vulnerable | True Positive | Report |
| Needs context | Investigate | Manual review |
| Has mitigation | False Positive | Document why |
| Different pattern | Not a variant | Skip |
Step 4: Document Findings
## Variant Analysis: [Bug Class Name]
### Initial Finding
- Location: [file:line]
- Description: [what's wrong]
### Pattern Description
[Abstract description of what makes this a bug]
### Search Method
```bash
[grep/semgrep commands used]
Variants Found
| Location | Status | Notes |
|---|---|---|
| file1.rs:23 | Confirmed | Same pattern |
| file2.rs:45 | Confirmed | Slight variation |
| file3.rs:67 | FP | Has validation elsewhere |
Remediation
[How to fix all instances]
## 4. Automation for Future Prevention
### Convert to CI/CD Check
After finding variants, create automated detection:
```yaml
# .github/workflows/security.yml
- name: Check for vulnerability patterns
run: |
# Run semgrep with custom rules
semgrep --config rules/mvx-security.yaml src/
# Grep-based checks
if grep -rn "unsafe_pattern" src/; then
echo "Found potential vulnerability"
exit 1
fi
Create Semgrep Rule
See multiversx-semgrep-creator skill:
rules:
- id: mvx-[bug-class]-[id]
languages: [rust]
message: "[Description of bug class]"
severity: ERROR
patterns:
- pattern: <vulnerable pattern>
5. Variant Analysis Checklist
After finding any bug:
- Abstract the pattern (what makes it a bug?)
- Create search queries (grep, semgrep)
- Search entire codebase
- Triage each result (TP/FP/needs investigation)
- Verify true positives are exploitable
- Document all variants
- Create prevention rule for CI/CD
- Recommend fix for all instances
6. Common Variant Categories
Input Validation Variants
- Missing in one endpoint → Check ALL endpoints
- Missing for one parameter → Check ALL parameters
Access Control Variants
- Missing on one admin function → Check ALL admin functions
- Inconsistent role checks → Audit entire role system
State Management Variants
- Reentrancy in one function → Check ALL external calls
- Missing callback handling → Check ALL callbacks
Arithmetic Variants
- Overflow in one calculation → Check ALL math operations
- Precision loss in one formula → Check ALL division operations
7. Reporting Multiple Variants
Consolidated Report
When multiple variants exist, consolidate:
# Bug Class: [Name]
## Summary
Found [N] instances of [bug description] across the codebase.
## Root Cause
[Why this pattern is vulnerable]
## Instances
### Instance 1 (file1.rs:23)
[Details]
### Instance 2 (file2.rs:45)
[Details]
...
## Recommended Fix
[Generic fix pattern]
```rust
// Before (vulnerable)
[vulnerable code]
// After (fixed)
[fixed code]
Prevention
[How to prevent this class of bugs in the future]
### Severity Aggregation
| Individual Severity | Count | Aggregate Severity |
|---------------------|-------|-------------------|
| Critical | 3+ | Critical |
| High | 5+ | Critical |
| Medium | 10+ | High |
| Low | Any | Low |
## 8. Example: Complete Variant Analysis
**Initial Bug: Missing amount validation in stake()**
```rust
// Found in stake.rs:45
#[payable("EGLD")]
fn stake(&self) {
let payment = self.call_value().egld_value();
// Bug: No check for amount > 0
self.staked().update(|s| *s += payment.clone_value());
}
Pattern: Missing amount > 0 check on payable endpoint
Search:
grep -rn "#\[payable" src/ | cut -d: -f1 | sort -u | while read file; do
echo "=== $file ==="
grep -A 30 "#\[payable" "$file" | head -40
done > payable_review.txt
Variants Found:
stake.rs:45- stake() - CONFIRMEDstake.rs:78- add_stake() - CONFIRMEDrewards.rs:23- deposit_rewards() - CONFIRMEDfees.rs:12- pay_fee() - FALSE POSITIVE (has check on line 15)
Fix Applied to All:
#[payable("EGLD")]
fn stake(&self) {
let payment = self.call_value().egld_value();
require!(payment.clone_value() > 0, "Amount must be positive");
self.staked().update(|s| *s += payment.clone_value());
}
CI Rule Created: rules/mvx-amount-validation.yaml