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
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,22 @@ jobs:
- { os: ubuntu-24.04, compiler: clang, preset: release }
- { os: ubuntu-24.04, compiler: clang, preset: asan }
- { os: ubuntu-24.04, compiler: clang, preset: ubsan }
# Opt-in debug hardening (ADR-0043) — cross-platform (no sanitizer
# flags); builds the hardened configuration and runs its detection
# tests, the "clean in both configurations" acceptance for #109.
- { os: ubuntu-24.04, compiler: gcc, preset: harden }
- { os: ubuntu-24.04, compiler: clang, preset: harden }
# Windows x86_64 — sanitizer presets are POSIX-only.
- { os: windows-2022, compiler: msvc, preset: debug }
- { os: windows-2022, compiler: msvc, preset: release }
# MSVC has no ASan in this matrix, so hardening is the memory-safety net here.
- { os: windows-2022, compiler: msvc, preset: harden }
# macOS arm64
- { os: macos-14, compiler: apple-clang, preset: debug }
- { os: macos-14, compiler: apple-clang, preset: release }
- { os: macos-14, compiler: apple-clang, preset: asan }
- { os: macos-14, compiler: apple-clang, preset: ubsan }
- { os: macos-14, compiler: apple-clang, preset: harden }
steps:
- name: Check out the source tree
uses: actions/checkout@v6
Expand Down
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,24 @@ dated version block (`## [X.Y.Z] — YYYY-MM-DD`) when a release PR closes a mil

### Added

- **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
use-after-free / pointer-corruption primitives an in-band next-link exposes: freed blocks are
**poisoned** (`0xDE`) and verified on the next allocation (use-after-free), a per-slot
trailing **guard word** detects a contiguous write past `block_size` (buffer overflow) and a
repeated free (double-free — the [ADR-0012](docs/adr/0012-foreign-pointer-and-out-of-range-pointer-policy.md)
gap), and the next-link is stored with glibc-style **safe-linking**
(`ptr XOR (slot_addr >> 12)`) so a leaked/overwritten link is neither usable nor silently
followed. On detection a swappable `HardeningViolationHandler`
([`pool_hardening.hpp`](src/main/cpp/it/d4np/memorypool/pool_hardening.hpp)) fires — the
default prints a diagnostic and `abort()`s. The guard lives in *added* slot stride, so the
user-visible `block_size` and the [ADR-0009](docs/adr/0009-free-list-layout-block-size-constraints-and-alignment-guarantee.md)
alignment guarantee are unchanged, and the default build is byte-for-byte and cycle-for-cycle
unchanged (the knob is fully compiled out). Works with fixed and dynamic pools across all
three thread-safety policies. Purely additive and ABI-compatible; a hardened build is
deliberately not layout-compatible with a non-hardened one (never mix configurations).
[ADR-0043](docs/adr/0043-opt-in-debug-hardening.md). Closes #109.
- **`std::pmr::memory_resource` adapter — `PoolMemoryResource`.** A new header-only Adapter
([`pool_memory_resource.hpp`](src/main/cpp/it/d4np/memorypool/pool_memory_resource.hpp))
binds one `Pool` behind the runtime `std::pmr::memory_resource` interface, so a single
Expand Down
15 changes: 15 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,21 @@ if(PBR_MEMORY_POOL_ENABLE_DIAGNOSTICS)
target_compile_definitions(pbr_memory_pool PUBLIC PBR_MEMORY_POOL_DIAGNOSTICS=1)
endif()

# ---------------------------------------------------------------------------
# Opt-in debug hardening (ADR-0043). When ON, the free list gains freed-block
# poisoning, a per-slot guard word (buffer-overflow + double-free detection),
# and next-pointer safe-linking (see pool_hardening.hpp). OFF by default — a
# release build is byte-for-byte unchanged. Defined PUBLIC because a hardened
# build changes the on-disk free-list encoding AND the physical slot stride, so
# the library and every consumer / test linking it must agree; never mix a
# hardened and a non-hardened build.
# ---------------------------------------------------------------------------
option(PBR_MEMORY_POOL_HARDENING
"Enable opt-in debug hardening: poisoning, guard word, free-list safe-linking (ADR-0043)" OFF)
if(PBR_MEMORY_POOL_HARDENING)
target_compile_definitions(pbr_memory_pool PUBLIC PBR_MEMORY_POOL_HARDENING=1)
endif()

# ---------------------------------------------------------------------------
# Thread-safety policy (ADR-0020). Selects how the implementation
# synchronizes the free-list head, fixed library-wide at build time. The
Expand Down
13 changes: 12 additions & 1 deletion CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@
"rhs": "Windows"
}
},
{
"name": "harden",
"inherits": "debug",
"displayName": "Debug + Hardening",
"description": "Debug + opt-in debug hardening: freed-block poisoning, per-slot guard word (overflow + double-free), and free-list safe-linking (ADR-0043). Cross-platform (no sanitizer flags).",
"cacheVariables": {
"PBR_MEMORY_POOL_HARDENING": "ON"
}
},
{
"name": "bench",
"displayName": "Benchmark",
Expand All @@ -104,13 +113,15 @@
{"name": "asan", "configurePreset": "asan"},
{"name": "ubsan", "configurePreset": "ubsan"},
{"name": "tsan", "configurePreset": "tsan"},
{"name": "harden", "configurePreset": "harden"},
{"name": "bench", "configurePreset": "bench"}
],
"testPresets": [
{"name": "debug", "configurePreset": "debug", "output": {"outputOnFailure": true}, "execution": {"jobs": 0}},
{"name": "release", "configurePreset": "release", "output": {"outputOnFailure": true}, "execution": {"jobs": 0}},
{"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": "tsan", "configurePreset": "tsan", "output": {"outputOnFailure": true}, "execution": {"jobs": 0}},
{"name": "harden", "configurePreset": "harden", "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 @@ -137,7 +137,7 @@ Goal: *post-`v1.0.0`*, stand up a **modular, professional documentation-translat
Goal: a coherent, post-`v1.0.0` wave of **additive, ABI-compatible** capabilities on top of the frozen public surface — richer standard-library interop, opt-in memory-safety hardening, a fuzzing harness, and a broader benchmark baseline. Every item is additive, so the milestone targets `v1.2.0` (SemVer `MINOR`); each ships independently under the §6.1 one-PR-at-a-time rule, and closing the milestone is the `v1.2.0` bump ([ADR-0037](docs/adr/0037-new-feature-roadmap-placement.md)). Each item comes from a tracking issue.

- [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).
- [ ] 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).
- [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).
- [ ] 9.4 **Benchmark extension** — external allocator baselines (jemalloc / tcmalloc) and p99 tail-latency reporting (issue #111).

Expand Down
2 changes: 2 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ This is a single-maintainer reference project, so timelines are **best-effort**,

In scope: memory-safety and correctness defects in the library's own code reachable through its public API or documented build options — e.g. out-of-bounds access, use-after-free, double-free, leaks, data races in the `MUTEX` / `LOCKFREE` policies, or integer-overflow in size computations.

For defense-in-depth and bug-finding, the library ships an **opt-in debug-hardening build** (compile-time knob `PBR_MEMORY_POOL_HARDENING`, OFF by default; [ADR-0043](docs/adr/0043-opt-in-debug-hardening.md)) that turns use-after-free, buffer-overflow-past-`block_size`, and double-free into deterministic, loud failures via freed-block poisoning, a per-slot guard word, and glibc-style free-list safe-linking. It complements the sanitizer matrix (and works where ASan does not, e.g. MSVC); it is a debugging aid, not a substitute for the sanitizers, and a hardened build is intentionally not layout-compatible with a default one.

Out of scope: misuse that the documentation explicitly calls undefined behaviour (e.g. pairing storage and object-lifetime verbs incorrectly, or a moved-from wrapper used after move), issues in a consumer's own code, and vulnerabilities in third-party toolchains. The default single-threaded build is intentionally not thread-safe (spec §2.4) — concurrent use of a `NONE`-policy pool is not a vulnerability.

## See also
Expand Down
Loading
Loading