Skip to content

fix: align generator contracts - #17

Merged
berisher merged 2 commits into
mainfrom
fix/generator-contracts
Mar 31, 2026
Merged

fix: align generator contracts#17
berisher merged 2 commits into
mainfrom
fix/generator-contracts

Conversation

@berisher

Copy link
Copy Markdown
Collaborator

Description

Align the generator, CLI, and Python bindings around one consistent contract
for determinism, opcode budgeting, and size-limited generation.

This change makes repeated generation calls reset cleanly, derives distinct
deterministic per-sample seeds in batch mode, normalizes and bounds opcode
ranges, treats max_opcodes as a total emitted-opcode budget, makes persistent
ID opcodes opt-in, and exposes a real max-size generation path to the Python
API so PickleMutator no longer truncates finished pickles into invalid output.

Related Issue

Generator range, size, and determinism remediation from Hunter campaign
pickle-fuzzer-patch-test:
F-0006, F-0022, F-0023, F-0024, F-0025, F-0034, F-0035,
F-0038, F-0046, F-0047, F-0048, F-0077, F-0078, F-0079,
F-0080, F-0081, F-0092, F-0095, F-0097

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Performance improvement
  • Code refactoring
  • Test addition or update

Changes Made

  • Updated the Rust generator contract so:
    • generate() and generate_from_arbitrary() reset state before each run
    • opcode ranges are normalized and capped to a sane upper bound
    • total-opcode budgeting includes cleanup and STOP, with impossible budgets
      rejected instead of silently exceeded
    • with_buffer_size() becomes a real max-size generation control
    • PERSID/BINPERSID are disabled by default and require explicit opt-in
  • Updated the CLI so:
    • seeded batch mode derives a deterministic per-sample seed instead of
      duplicating the same pickle into every output slot
    • --mutators bitflip output.pkl no longer swallows the output path
    • persistent IDs have an explicit --allow-persistent-ids flag
  • Updated the Python bindings so:
    • set_opcode_range() preserves seed and other generator configuration
    • generate() and generate_from_bytes() accept max_size
    • the Python stub matches the new binding signatures
  • Updated PickleMutator so it asks the Rust generator to honor max_size
    up front and falls back to a minimal valid pickle on generation failure when
    one fits in the requested budget.
  • Updated README usage/docs to reflect the new CLI and Python behavior.

Testing Performed

Describe the tests you ran to verify your changes:

  • Unit tests added/updated
  • Integration tests added/updated
  • Manual testing performed
  • All existing tests pass (cargo test)

Test commands:

cargo fmt
uv run python -m py_compile python/pickle_fuzzer/fuzzer.py python/tests/test_generation.py python/tests/test_fuzzer.py
uv run maturin develop
uv run pytest python/tests
cargo test
git diff --check

Checklist

Before submitting this PR, please ensure:

  • Code follows project style guidelines (cargo fmt)
  • No clippy warnings (cargo clippy -- -D warnings)
  • All tests pass (cargo test)
  • CHANGELOG.md updated (if applicable)
  • No breaking changes (or documented if necessary)
  • Commit messages follow conventional commit format

Performance Impact

If this PR affects performance:

  • Benchmarks run and results documented
  • No significant performance regression
  • Performance improvement quantified

Benchmark results:

N/A

Screenshots/Examples

Not applicable.

Additional Notes

The new Python max_size path intentionally prefers returning an error or a
minimal valid fallback over truncating a completed pickle stream. That keeps the
structure-aware contract intact for Atheris users instead of silently feeding
raw entropy or chopped bytecode back into the target parser.

Breaking Changes

Generator.generate() and Generator.generate_from_bytes() in Python now
accept max_size, the CLI exposes --allow-persistent-ids, and impossible
total-opcode budgets now return errors instead of silently exceeding the
declared maximum.

@berisher
berisher merged commit 48b580b into main Mar 31, 2026
24 checks passed
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.

1 participant