Skip to content

fix use-after-free of the new block in SingleIOBuf::assign#3397

Open
ubeddulla wants to merge 1 commit into
apache:masterfrom
ubeddulla:single-iobuf-assign-uaf
Open

fix use-after-free of the new block in SingleIOBuf::assign#3397
ubeddulla wants to merge 1 commit into
apache:masterfrom
ubeddulla:single-iobuf-assign-uaf

Conversation

@ubeddulla

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number:

Problem Summary:
SingleIOBuf::assign() takes the concatenating path when the message spans more than one BlockRef of the source IOBuf, and that path frees the block it is about to write into.

  1. alloc_block_by_size(msg_size) stores the block it returns in the member _cur_block, and for a freshly created block that member holds the only reference.
  2. reset() then runs on the next line and releases _cur_block. For a message bigger than the default block size it calls dec_ref(), nshared goes 1 to 0 and the block is freed; for a smaller one the block goes back into the TLS pool where another owner can take it.
  3. The rest of the function still writes msg_size bytes into b, bumps b->size, stores b in _cur_ref and inc_ref()s it. _cur_ref keeps the dangling pointer, so the later reset() or the destructor dec-refs it a second time.

ASAN on a source IOBuf of two 5000/6000 byte blocks with msg_size = 11000:

ERROR: AddressSanitizer: heap-use-after-free on address 0x626000000118
READ of size 8 at 0x626000000118 thread T0
    #0 butil::SingleIOBuf::assign(butil::IOBuf const&, unsigned int) single_iobuf.cpp:230
freed by thread T0 here:
    #1 butil::IOBuf::Block::dec_ref() iobuf_inl.h:544
    #2 butil::SingleIOBuf::reset() single_iobuf.cpp:193
    #3 butil::SingleIOBuf::assign(butil::IOBuf const&, unsigned int) single_iobuf.cpp:229
previously allocated by thread T0 here:
    #2 butil::SingleIOBuf::alloc_block_by_size(unsigned int) single_iobuf.cpp:126

What is changed and the side effects?

Changed:
Drop only the stale _cur_ref reference instead of calling reset(). alloc_block_by_size() already releases the old _cur_block when it cannot be reused, so the reference that had to go here is the one to the previously assigned data. The single-BlockRef fast path above and assign_user_data() are unaffected, they already release before acquiring. reallocate_downward() orders its dec_ref() after the copy and is fine as is.

single_iobuf_assign_large_multi_block in iobuf_unittest.cpp covers it: it reports the trace above on the current tree and passes with the change, and the other 59 IOBufTest cases still pass under ASAN.

Side effects:

  • Performance effects: none. Keeping _cur_block lets a repeated assign() reuse the same block, which the old code could not do since it released it every time.

  • Breaking backward compatibility: none.


Check List:

Signed-off-by: ubeddulla khan <ubed@bugqore.com>

Copilot AI left a comment

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.

Pull request overview

Fixes a use-after-free in butil::SingleIOBuf::assign() when the input IOBuf spans multiple BlockRefs, by avoiding reset() after alloc_block_by_size() has just set _cur_block to the newly allocated destination block.

Changes:

  • Update SingleIOBuf::assign() to drop only the previously assigned _cur_ref reference instead of calling reset() (which could release _cur_block and invalidate the destination block).
  • Add a regression unit test covering multi-block concatenation with a message larger than the default block size, including re-assigning on an already-assigned SingleIOBuf.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/butil/single_iobuf.cpp Prevents freeing/recycling the freshly allocated destination block during multi-block assign() concatenation.
test/iobuf_unittest.cpp Adds a regression test that reproduces the prior UAF scenario and verifies correctness across repeated assign().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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