Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -597,3 +597,60 @@ jobs:
# growth scenario grows under MUTEX and cleanly skips under LOCKFREE.
"$bin" --scenario all --threads 4 --iterations 20000 --repeats 3
echo "OK — all bench scenarios ran to completion under ${{ matrix.mode }}."

# ---------------------------------------------------------------------------
# ROADMAP §9.3 — coverage-guided fuzzing (ADR-0044). Builds the libFuzzer
# target under ASan/UBSan (Clang / POSIX only — libFuzzer is a Clang runtime),
# replays the seed corpus as a regression gate, then fuzzes for a bounded time
# on every PR. A crash fails the job and the offending input is uploaded as a
# reproducer for the bug ledger (ADR-0039).
# ---------------------------------------------------------------------------
fuzz:
name: fuzz / libFuzzer (asan+ubsan)
runs-on: ubuntu-24.04
steps:
- name: Check out
uses: actions/checkout@v6

- name: Install CMake and Ninja
uses: lukka/get-cmake@latest

- name: Configure (fuzz preset, Clang)
shell: bash
run: |
export CC=clang CXX=clang++
cmake --preset fuzz

- name: Build the fuzz target
shell: bash
run: cmake --build build/fuzz --target pool_fuzz

- name: Replay the seed corpus (regression gate)
shell: bash
run: |
set -euo pipefail
bin="build/fuzz/src/test/cpp/it/d4np/memorypool/pool_fuzz"
corpus="src/test/cpp/it/d4np/memorypool/pool_fuzz_corpus"
# Replaying named files runs each once and exits; a crash is a finding.
"$bin" "$corpus"/seed_*
echo "OK — seed corpus replayed clean."

- name: Fuzz for a bounded time
shell: bash
run: |
set -euo pipefail
bin="build/fuzz/src/test/cpp/it/d4np/memorypool/pool_fuzz"
corpus="src/test/cpp/it/d4np/memorypool/pool_fuzz_corpus"
"$bin" -max_total_time=60 -timeout=25 -rss_limit_mb=4096 "$corpus"
echo "OK — libFuzzer ran to the time budget with no crash."

- name: Upload any crash reproducers
if: failure()
uses: actions/upload-artifact@v7
with:
name: fuzz-crashes
path: |
crash-*
timeout-*
oom-*
if-no-files-found: ignore
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@ dated version block (`## [X.Y.Z] — YYYY-MM-DD`) when a release PR closes a mil

### Added

- **Coverage-guided fuzzing harness for the pool surface.** A new
[`pool_fuzz.cpp`](src/test/cpp/it/d4np/memorypool/pool_fuzz.cpp) drives randomized
`alloc` / `free(valid)` / `free(NULL)` / `free(foreign)` sequences — over both fixed and
dynamic-growth pools — through a stateful opcode interpreter, asserting the no-alias,
per-block-canary, foreign/NULL-no-op ([ADR-0012](docs/adr/0012-foreign-pointer-and-out-of-range-pointer-policy.md)),
and `InstrumentedPool` accounting invariants against a shadow oracle so a corruption becomes
a saved reproducer. One engine-agnostic source yields the Clang-only libFuzzer target
`pool_fuzz` (opt-in `PBR_MEMORY_POOL_BUILD_FUZZERS`; a `fuzz` preset builds it under
`-fsanitize=fuzzer,address,undefined`, and a dedicated CI job replays the seed corpus and
fuzzes for a bounded time on every PR) and an always-built standalone `pool_fuzz_replay` that
makes the seed corpus a portable regression gate everywhere — including MSVC, where libFuzzer
is unavailable. Test-only and additive; the release build and the benchmark numbers are
untouched. [ADR-0044](docs/adr/0044-coverage-guided-fuzzing-harness.md). Closes #108.
- **Opt-in debug hardening — freed-block poisoning, a guard word, and free-list
safe-linking.** A compile-time knob `PBR_MEMORY_POOL_HARDENING` (OFF by default; a `harden`
CMake preset turns it on) hardens the intrusive free list against the classic
Expand Down
14 changes: 14 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,19 @@ if(PBR_MEMORY_POOL_BUILD_BENCHMARKS)
add_subdirectory(src/bench/cpp/it/d4np/memorypool)
endif()

# ---------------------------------------------------------------------------
# Coverage-guided fuzzing (ADR-0044). When ON, the test tree adds the
# libFuzzer target `pool_fuzz` (Clang only — libFuzzer is a Clang runtime),
# built with -fsanitize=fuzzer,address,undefined and run by a dedicated CI
# job. OFF by default so it never perturbs the release build or the ADR-0014
# benchmark methodology. The portable `pool_fuzz_replay` target (which replays
# the seed corpus as a regression gate) is built with the normal test suite
# regardless of this option; only the coverage-guided engine is gated here.
# Requires PBR_MEMORY_POOL_BUILD_TESTS, since the target lives in the test tree.
# ---------------------------------------------------------------------------
option(PBR_MEMORY_POOL_BUILD_FUZZERS
"Build the libFuzzer coverage-guided fuzz target (Clang only, ADR-0044)" OFF)

# ---------------------------------------------------------------------------
# Configuration summary (debug aid; not output in -DCMAKE_MESSAGE_LOG_LEVEL=ERROR).
# ---------------------------------------------------------------------------
Expand All @@ -285,5 +298,6 @@ message(STATUS " C++ standard : C++${CMAKE_CXX_STANDARD} (extensions ${C
message(STATUS " Build type : ${CMAKE_BUILD_TYPE}${CMAKE_CONFIGURATION_TYPES}")
message(STATUS " Tests : ${PBR_MEMORY_POOL_BUILD_TESTS}")
message(STATUS " Benchmarks : ${PBR_MEMORY_POOL_BUILD_BENCHMARKS}")
message(STATUS " Fuzzers : ${PBR_MEMORY_POOL_BUILD_FUZZERS}")
message(STATUS " Install rules : ${PBR_MEMORY_POOL_INSTALL}")
message(STATUS "")
22 changes: 21 additions & 1 deletion CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,24 @@
"PBR_MEMORY_POOL_HARDENING": "ON"
}
},
{
"name": "fuzz",
"inherits": "debug",
"displayName": "Fuzzing (libFuzzer + ASan/UBSan)",
"description": "Debug + the coverage-guided libFuzzer target pool_fuzz, with ASan/UBSan over the whole build. Clang / POSIX only — libFuzzer is a Clang runtime (ADR-0044).",
"cacheVariables": {
"PBR_MEMORY_POOL_BUILD_FUZZERS": "ON",
"CMAKE_C_FLAGS": "-fsanitize=fuzzer-no-link,address,undefined -fno-omit-frame-pointer -g",
"CMAKE_CXX_FLAGS": "-fsanitize=fuzzer-no-link,address,undefined -fno-omit-frame-pointer -g",
"CMAKE_EXE_LINKER_FLAGS": "-fsanitize=address,undefined",
"CMAKE_SHARED_LINKER_FLAGS": "-fsanitize=address,undefined"
},
"condition": {
"type": "notEquals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
}
},
{
"name": "bench",
"displayName": "Benchmark",
Expand All @@ -114,6 +132,7 @@
{"name": "ubsan", "configurePreset": "ubsan"},
{"name": "tsan", "configurePreset": "tsan"},
{"name": "harden", "configurePreset": "harden"},
{"name": "fuzz", "configurePreset": "fuzz"},
{"name": "bench", "configurePreset": "bench"}
],
"testPresets": [
Expand All @@ -122,6 +141,7 @@
{"name": "asan", "configurePreset": "asan", "output": {"outputOnFailure": true}, "execution": {"jobs": 0}},
{"name": "ubsan", "configurePreset": "ubsan", "output": {"outputOnFailure": true}, "execution": {"jobs": 0}},
{"name": "tsan", "configurePreset": "tsan", "output": {"outputOnFailure": true}, "execution": {"jobs": 0}},
{"name": "harden", "configurePreset": "harden", "output": {"outputOnFailure": true}, "execution": {"jobs": 0}}
{"name": "harden", "configurePreset": "harden", "output": {"outputOnFailure": true}, "execution": {"jobs": 0}},
{"name": "fuzz", "configurePreset": "fuzz", "output": {"outputOnFailure": true}, "execution": {"jobs": 0}}
]
}
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ Goal: a coherent, post-`v1.0.0` wave of **additive, ABI-compatible** capabilitie

- [x] 9.1 **`std::pmr::memory_resource` adapter** (`PoolMemoryResource`) — the "door left open" in [ADR-0018](docs/adr/0018-stl-allocator-adapter.md): a `std::pmr::memory_resource` subclass binding one `Pool` so any `std::pmr`-aware container can draw from it through `std::pmr::polymorphic_allocator`, without the `PoolAllocator<T>` per-type rebind. Deterministic `(bytes, alignment)` routing to the bound pool — over-sized / over-aligned requests delegate to a configurable upstream resource, and exhaustion of a pool-eligible request throws `std::bad_alloc` rather than falling back (preserving the deterministic deallocate routing) — with `is_equal` by `(pool, upstream)` identity, gated behind `PBR_MEMORY_POOL_HAS_PMR` where `<memory_resource>` is available. Header-only, additive, ABI-compatible. Decided in [ADR-0042](docs/adr/0042-pmr-memory-resource-adapter.md); implemented in [`pool_memory_resource.hpp`](src/main/cpp/it/d4np/memorypool/pool_memory_resource.hpp) with [`pool_memory_resource_test.cpp`](src/test/cpp/it/d4np/memorypool/pool_memory_resource_test.cpp) (issue #107).
- [x] 9.2 **Opt-in debug hardening** — freed-block poisoning, canaries, and free-list safe-linking (which also yields double-free detection); zero cost when the gate is off (issue #109). Decided in [ADR-0043](docs/adr/0043-opt-in-debug-hardening.md); implemented in [`memory_pool.cpp`](src/main/cpp/it/d4np/memorypool/memory_pool.cpp) behind the compile-time `PBR_MEMORY_POOL_HARDENING` knob (a `harden` CMake preset), with the swappable violation-handler surface in [`pool_hardening.hpp`](src/main/cpp/it/d4np/memorypool/pool_hardening.hpp) and [`pool_hardening_test.cpp`](src/test/cpp/it/d4np/memorypool/pool_hardening_test.cpp) (CTest `pool_hardening`). The "canary" is realized as one trailing **guard word** living in *added* slot stride — so the user-visible `block_size` and the ADR-0009 alignment guarantee are unchanged and the default build is byte-for-byte unchanged (the mechanism is fully compiled out). Poisoning (`0xDE`) catches use-after-free on the next allocation, the guard word catches a write past `block_size` and a double-free, and glibc-style safe-linking (`ptr XOR (slot_addr >> 12)`) protects the in-band next-link. A `harden` CI matrix cell builds and tests the hardened configuration on each Tier-1 platform. Works with fixed and dynamic pools across all three thread-safety policies.
- [ ] 9.3 **Coverage-guided fuzzing harness** for the pool surface — a libFuzzer target under `src/fuzz/`, time-boxed in CI (issue #108).
- [x] 9.3 **Coverage-guided fuzzing harness** for the pool surface — a libFuzzer target, time-boxed in CI (issue #108). Decided in [ADR-0044](docs/adr/0044-coverage-guided-fuzzing-harness.md); implemented in [`pool_fuzz.cpp`](src/test/cpp/it/d4np/memorypool/pool_fuzz.cpp) as a stateful opcode interpreter with a shadow oracle that asserts the no-alias, canary-intact, foreign/NULL-no-op ([ADR-0012](docs/adr/0012-foreign-pointer-and-out-of-range-pointer-policy.md)) and `InstrumentedPool` accounting invariants across fixed and dynamic pools. One engine-agnostic source yields both the Clang-only libFuzzer target `pool_fuzz` (opt-in `PBR_MEMORY_POOL_BUILD_FUZZERS`, a `fuzz` preset under `-fsanitize=fuzzer,address,undefined`) and the always-built standalone [`pool_fuzz_replay`](src/test/cpp/it/d4np/memorypool/pool_fuzz_corpus/) target that replays the seed corpus as a portable regression gate (CTest `pool_fuzz_replay`) on every platform, including MSVC. A dedicated `fuzz` CI job replays the corpus and fuzzes for a bounded time on every PR; a crash is filed in the bug ledger ([ADR-0039](docs/adr/0039-bug-ledger-and-triage-protocol.md)). Test-only and additive — the release build and the benchmark numbers are untouched.
- [ ] 9.4 **Benchmark extension** — external allocator baselines (jemalloc / tcmalloc) and p99 tail-latency reporting (issue #111).

---
Expand Down
Loading
Loading