Skip to content

mod_http2: fix use-after-free of response and trailer bucket data - #846

Closed
arshsmith1 wants to merge 1 commit into
apache:trunkfrom
arshsmith1:h2-stream-response-uaf
Closed

arshsmith1 wants to merge 1 commit into
apache:trunkfrom
arshsmith1:h2-stream-response-uaf

Conversation

@arshsmith1

Copy link
Copy Markdown

In the AP_HAS_RESPONSE_BUCKETS build, stream_do_response() and stream_do_trailers() in modules/http2/h2_stream.c keep a pointer into a RESPONSE/HEADERS metadata bucket (resp = b->data) and then call apr_bucket_destroy(b) before using it. That struct is allocated from the bucket allocator and freed by the bucket destructor (apr_bucket_free() once the shared refcount reaches 0, see server/headers_bucket.c), so the following reads of resp->status, resp->notes, resp->headers (and headers->headers for trailers) read freed memory. stream_do_response() also stores the freed pointer in stream->response, which is dereferenced later from stream_data_cb() (stream->response->status).

The bucket arrives in out_buffer as a fresh clone produced by h2_beam_receive() (ap_bucket_response_clone) with refcount 1, so the destroy really frees it; the session keeps allocating buckets from the same per-connection allocator, the freed slot gets handed back, and the response is corrupted.

The struct contents (headers/notes/reason) already live in stream->pool, so copying the small struct there with apr_pmemdup() before the destroy keeps it valid for the life of the stream. This mirrors the clone-before-destroy pattern h2_c2_filter.c already uses for the headers it consumes. The non-AP_HAS_RESPONSE_BUCKETS build is unaffected, since there resp is the pool-allocated h2_headers.

I could not turn this into a crash reproducible under a supported configuration (the freed slot is frequently not reused within the window), so it is offered as a correctness fix rather than a reported vulnerability. I checked the lifetime with a small program driving the real APR bucket allocator: after apr_bucket_destroy() the freed ap_bucket_response-shaped slot is returned by the next same-size apr_bucket_alloc() and overwritten, so the retained pointer reads corrupted data; with the apr_pmemdup() copy the value survives.


  • This PR does not report or address a security vulnerability
    (see SECURITY.md and
    https://www.apache.org/security/#reporting-a-vulnerability).
    I was unable to reproduce a crash under a supported configuration, so
    this is submitted as a memory-safety correctness fix; please redirect it
    if you would rather handle it privately.
  • Code follows the httpd style guide.
  • New log messages (level debug or higher) use an empty APLOGNO() tag;
    numbers are assigned by a committer at merge time (see docs/log-message-tags/README).
    (No new log messages in this change.)
  • If the change is user-visible, a changes-entries/*.txt file is included,
    following the template in README.CHANGES (not needed otherwise).
  • Test cases based on pyhttpd are included where appropriate, in the
    test/modules/xxx directory matching the modules/xxx module source,
    or test/modules/core for core server changes. On Unix, verify these
    pass locally with e.g. make check-pytest PYTEST_DIRS=test/modules/xxx.
    A deterministic pyhttpd test is impractical for this latent use-after-free
    (it only manifests when the freed slot is reused), so I verified it with a
    standalone reproducer against the real APR bucket allocator instead.

stream_do_response() and stream_do_trailers() read the RESPONSE/HEADERS
bucket's data after apr_bucket_destroy() has freed it, and keep the
response pointer in stream->response for the life of the stream. Copy
the data into the stream pool before destroying the bucket, as
h2_c2_filter.c already does for the headers it consumes.
@notroj

notroj commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator

Duplicate of #708 - let's discuss there.

@notroj notroj closed this Oct 14, 2026
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