Skip to content

tests/functional: show sanitizer reports when a test fails - #81

Closed
joshheinrichs-shopify wants to merge 3 commits into
mainfrom
ci-dump-sanitizer-logs
Closed

joshheinrichs-shopify wants to merge 3 commits into
mainfrom
ci-dump-sanitizer-logs

Conversation

@joshheinrichs-shopify

Copy link
Copy Markdown
Contributor

Motivation

main's sanitizer and coverage legs fail in binary-cache with nix serve being Aborted during the truncated-NAR resume test — silently, because ASan writes its report to log_path and nothing shows it. This makes onError dump those reports, so the sanitizer leg of this PR should tell us what the actual finding is.

Context

One-line harness change, no effect on passing tests. The underlying nix serve abort is new since the v3.23.1 sync and only reproduces under clang+ASan on Linux (macOS and the remote builder are unavailable to me right now), hence getting the report via CI.

The functional tests run with `log_path` in ASAN_OPTIONS so that
sanitizer output doesn't break tests that compare stderr. The flip side
is that when a sanitizer aborts a process, the test log shows nothing
but an "Aborted" from bash: the report is in a file in the build
directory that CI never sees. Dump any such reports from `onError`.
The sanitizer runtimes print raw addresses unless llvm-symbolizer is on
PATH, which makes a report like the heap-use-after-free that aborts
`nix serve` in the binary-cache test nearly useless. Put llvm-symbolizer
in the environment of every instrumented component.
`GCTrackedStackAllocator` stored a pointer to the owning
`SinkToSource`/`SourceToSink`'s `stackCookie` and dereferenced it when
the coroutine's stack was deallocated, on the assumption that the owner
outlives the coroutine. With nested coroutines that assumption fails:
a decompressor's `sourceToSink` coroutine feeds a `sinkToSource` sink,
so the outer coroutine yields from the inner coroutine's stack. When the
outer `SinkToSource` is destroyed while suspended, boost unwinds the
stack it yielded from -- the inner one -- and that unwinding destroys
the outer stack's frames, among them the `unique_ptr` owning the inner
`SourceToSink`. The inner coroutine's own stack is only deallocated
afterwards, when its fiber exits, and its allocator then reads and
writes the cookie through a pointer into the freed `SourceToSink`:

    ERROR: AddressSanitizer: heap-use-after-free
    #0 GCTrackedStackAllocator::deallocate(...) serialise.cc:410
    ...
    #12 sinkToSource(...)::SinkToSource::~SinkToSource()
    #16 CmdServe::handleRequest(...)::State::~State
    #19 MHD_destroy_response

`nix serve` hits this whenever a NAR download is cut short, which is
exactly what tests/functional/binary-cache.sh does to test resuming, so
the sanitizer legs of CI failed there with a silent abort. Without ASan
it is a use-after-free that happens to go unnoticed.

Keep the cookie in a cell shared between the owner and the allocator
(whose copy in the fiber record lives exactly as long as the stack), so
either can go first.
@joshheinrichs-shopify

Copy link
Copy Markdown
Contributor Author

Superseded: the fix landed on main as 499125b by itself; the two CI-only commits here were debugging aids.

@joshheinrichs-shopify
joshheinrichs-shopify deleted the ci-dump-sanitizer-logs branch October 10, 2026 04:23
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