Skip to content

feat(edge_cov): add CmpLog-style comparison operand tracking#397

Draft
grandizzy wants to merge 1 commit intoparadigmxyz:mainfrom
grandizzy:feat/cmplog-tracking
Draft

feat(edge_cov): add CmpLog-style comparison operand tracking#397
grandizzy wants to merge 1 commit intoparadigmxyz:mainfrom
grandizzy:feat/cmplog-tracking

Conversation

@grandizzy
Copy link

Summary

Add comparison operand logging to EdgeCovInspector for CmpLog-style guided fuzzing. This captures operands from EQ, LT, GT, SLT, SGT, and ISZERO opcodes to help fuzzers solve comparison constraints.

Motivation

Coverage-guided fuzzers struggle with tight constraints like a == 61 * b because they sample parameters independently. By capturing the actual comparison operands at runtime, fuzzers can:

  • Add observed values to their dictionary
  • Synthesize related values (e.g., divide by common multipliers)
  • Generate inputs that satisfy magic byte/number comparisons

This is inspired by AFL++'s CmpLog feature.

Changes

  • Add CmpOperands struct to store comparison operand pairs with PC
  • Add cmp_log field to EdgeCovInspector
  • Add get_cmp_log() and into_parts() accessor methods
  • Modify reset() to clear cmp_log alongside hitcount
  • Add store_cmp() and do_cmp_step() for capturing comparison operands
  • Track comparison opcodes (EQ, LT, GT, SLT, SGT, ISZERO) in step() inspector method

Testing

Added 4 unit tests:

  • test_cmp_log_basic - Verifies CmpLog captures comparison operands from a real contract
  • test_cmp_log_into_parts - Tests the into_parts() method
  • test_cmp_log_reset - Verifies reset() clears both hitcount and cmp_log
  • test_cmp_operands_struct - Tests CmpOperands struct creation, Default, Clone

Usage

let inspector = EdgeCovInspector::new();
// ... run EVM with inspector ...

// Get comparison operands for dictionary enrichment
let cmp_log = inspector.get_cmp_log();
for cmp in cmp_log {
    println!("Comparison at PC {}: {} vs {}", cmp.pc, cmp.op1, cmp.op2);
}

// Or consume both hitcount and cmp_log
let (hitcount, cmp_log) = inspector.into_parts();

Add comparison operand logging to EdgeCovInspector for CmpLog-style
guided fuzzing. This captures operands from EQ, LT, GT, SLT, SGT, and
ISZERO opcodes to help fuzzers solve comparison constraints.

Changes:
- Add CmpOperands struct to store comparison operand pairs with PC
- Add cmp_log field to EdgeCovInspector
- Add get_cmp_log() and into_parts() accessor methods
- Modify reset() to clear cmp_log alongside hitcount
- Add store_cmp() and do_cmp_step() for capturing comparison operands
- Track comparison opcodes in step() inspector method

This enables coverage-guided fuzzers like Foundry to extract comparison
values and synthesize inputs that satisfy tight constraints like
`a == 61 * b` by observing the actual operand values at runtime.

Inspired by AFL++'s CmpLog feature for solving magic byte comparisons.
@grandizzy
Copy link
Author

@0xalpharush could you please have a look at this PR, it helped quite a lot for breaking new invariants in maze (need to make use of it in foundry). Thanks!

@0xalpharush
Copy link
Contributor

0xalpharush commented Jan 22, 2026

This make sense. Looking at LibAFL, it does seem they try to determine if the value is constant or variable as well as if the comparison is the loop counter.
https://github.com/AFLplusplus/LibAFL/blob/main/crates/libafl/src/observers/cmp.rs#L50-L62

It's also worth noting the usage should likely be able to track how the the comparison logs change between the original and mutated input as that is used to inform mutations. That is, if modifying the input does not change a comparison, it means the fuzzer input isn't directly read and either relies on some computation or state. In practice, I suspsect cmplog for EVM would be most useful when a slice of the calldata is directly read in a comparison and we can replace the right portion of bytes to solve the branch
https://github.com/AFLplusplus/LibAFL/blob/main/crates/libafl_targets/src/cmps/observers/aflpp.rs#L173-L201
https://github.com/AFLplusplus/LibAFL/blob/main/crates/libafl/src/mutators/token_mutations.rs

I wonder if some of the stack dictionary stuff in Foundry can use this logic as well. Would appreciate a ping when the integration is made as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments