Skip to content

fix: Free pre-existing allocation in s2n_alloc to prevent memory leak… - #5990

Open
alexw91 wants to merge 1 commit into
aws:mainfrom
alexw91:fix-alloc-memleak
Open

fix: Free pre-existing allocation in s2n_alloc to prevent memory leak…#5990
alexw91 wants to merge 1 commit into
aws:mainfrom
alexw91:fix-alloc-memleak

Conversation

@alexw91

@alexw91 alexw91 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Goal

Free pre-existing allocations in s2n_alloc to prevent memory leaks when called on an already-allocated blob.

Why

s2n_alloc() zeroes the blob struct (including b->allocated) before calling s2n_realloc(). Since s2n_realloc only frees the previous buffer when b->allocated is non-zero, the previous b->data pointer is silently orphaned. This is reachable via QUIC transport parameters during HelloRetryRequest, s2n_server_key_share_recv, s2n_client_psk_recv, and s2n_kem_recv_public_key.

How

Check b->allocated before zeroing and call s2n_free() if a pre-existing allocation exists. This is a 4-line fix in utils/s2n_mem.c. The header comment in utils/s2n_mem.h and the doxygen comment in utils/s2n_mem.c are updated to reflect the new semantics (the old docs warned that calling s2n_alloc on an allocated blob would leak).

Callouts

  • The fix changes documented behavior: s2n_alloc was explicitly documented as leaking on re-use ("calling s2n_alloc on a blob that already has memory allocated will leak memory"). Callers that relied on this were already buggy, so this is safe.
  • s2n_realloc remains the preferred API for growing an existing blob since it can reuse the buffer without a new allocation.

Testing

Tests are added to the existing s2n_mem_test.c using counting malloc/free callbacks:

  • A second s2n_alloc on the same blob frees the first allocation (malloc=2, free=1 after second alloc)
  • 10 repeated calls produce exactly 10 mallocs and 10 frees (no accumulation)
  • An empty blob (allocated == 0) does not trigger a spurious free

All new assertions fail without the fix and pass with it.

Related

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@alexw91
alexw91 force-pushed the fix-alloc-memleak branch 2 times, most recently from 14fa2af to f1c0dbc Compare July 16, 2026 22:53
@alexw91
alexw91 requested review from boquan-fang and jmayclin and removed request for boquan-fang August 10, 2026 20:27
@alexw91
alexw91 force-pushed the fix-alloc-memleak branch 2 times, most recently from d0b44a3 to ec636c6 Compare August 10, 2026 22:50
Comment thread utils/s2n_mem.h
* s2n_alloc allocates a new buffer of the requested size. If the blob
* already holds an allocation, it is freed first.
*
* s2n_realloc is preferred when growing an existing blob, as it may

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙋 Okay, maybe this is a stupid question, but why don't we just delete s2n_alloc and switch everything to use s2n_realloc?

@alexw91 alexw91 Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s2n_realloc requires the blob to already be growable (b->growable is explicitly set). s2n_realloc rejects non-growable blobs with S2N_ERR_RESIZE_STATIC_BLOB.

s2n_alloc zeroes the blob first (which makes it pass the growable check), so it works on blobs in any state, including ones previously set up with s2n_blob_init() pointing at stack memory.

… s2n_alloc() zeroed the blob struct (including b->allocated) before calling s2n_realloc(). Since s2n_realloc only frees the previous buffer when b->allocated is non-zero, the previous b->data pointer was silently orphaned on every call to s2n_alloc on an already- allocated blob. This was reachable via QUIC transport parameters during HRR, s2n_server_key_share_recv, s2n_client_psk_recv, and s2n_kem_recv_public_key. Fix: Check b->allocated before zeroing and call s2n_free() if a pre-existing allocation exists.
@alexw91
alexw91 force-pushed the fix-alloc-memleak branch from ec636c6 to 312310a Compare August 14, 2026 21:44
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