Skip to content

Read the batched remote free ring's length word without domesticating it - #877

Merged
Matthew Parkinson (mjp41) merged 1 commit into
microsoft:mainfrom
sblessing:fix-remote-ring-domestication
Sep 8, 2026
Merged

Read the batched remote free ring's length word without domesticating it#877
Matthew Parkinson (mjp41) merged 1 commit into
microsoft:mainfrom
sblessing:fix-remote-ring-domestication

Conversation

@sblessing

Copy link
Copy Markdown
Contributor

Fixes #876.

Problem

BatchedRemoteMessage::mk_from_freelist_builder() closes the free ring by storing a bit-packed (displacement << MAX_CAPACITY_BITS) + length word in the ring's next field. On the receiving side, open_free_ring() and ring_size() read that word back through freelist::Object::T::read_next(), which passes the decoded value through the config's domesticator as if it were a pointer.

A domesticator that does its job and rejects anything outside the heap (FixedRangeConfig::capptr_domesticate() returns nullptr for out-of-range addresses) turns the word into 0. Every incoming ring then decodes as length 0, the slab's needed count is never decremented, and every batched remote deallocation is leaked. With FixedRangeConfig a fixed region is exhausted by remote frees alone even though the program frees everything it allocates. The default StandardConfig has an identity domesticator, which is why the mainstream configuration was unaffected.

Fix

  • Add freelist::Object::T::read_next_raw(), which undoes the free-list encoding of the next field but does not domesticate the result. Per Nathaniel Wesley Filardo (@nwf)'s review comment on the issue, read_next() is now defined as domesticate(read_next_raw(...)), so the difference between the two is the single domestication call.
  • open_free_ring() and ring_size() read the ring word with read_next_raw(). The pointer derived from the displacement is still domesticated separately, as before.

Test

src/test/func/fixed_region_remote_dealloc adds the reproducer from the issue to the test harness: allocator A allocates 64-byte objects from a 32 MiB FixedRangeConfig region, allocator B frees them and flushes, for twice as many rounds as would be needed to exhaust the region if the remote frees were lost. On the unpatched tree it aborts in round 8144 (the whole region leaked); with the fix it completes.

Existing fixed_region, fixed_region_alloc and domestication tests still pass in both the fast and check flavours. domesticate_count in the domestication test drops from 5 to 4, since the ring word is no longer domesticated; that test does not assert on the count.

@sblessing

Copy link
Copy Markdown
Contributor Author

@microsoft-github-policy-service agree

@mjp41 Matthew Parkinson (mjp41) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, Nathaniel Wesley Filardo (@nwf) are you happy with this.

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.

LGTM. Thanks again!

BatchedRemoteMessage::mk_from_freelist_builder() closes the free ring by
storing a bit-packed (displacement, length) word, not a pointer, in the
ring's next field.  open_free_ring() and ring_size() read it back through
freelist::Object::T::read_next(), which passes the decoded value through
the config's domesticator.  A domesticator that rejects addresses outside
the heap (FixedRangeConfig::capptr_domesticate() returns nullptr for
them) turns the word into 0, so every incoming ring decodes as length 0
and every batched remote deallocation is leaked.  With FixedRangeConfig
the fixed region is exhausted by remote frees alone even though the
program frees everything it allocates.

Add freelist::Object::T::read_next_raw(), which undoes the free-list
encoding but does not domesticate, and define read_next() in terms of it
so the difference between the two is the single domestication call.  Use
read_next_raw() for the ring word; the pointer derived from its
displacement is still domesticated separately, as before.

Add the reproducer as func/fixed_region_remote_dealloc: one allocator
allocates from a FixedRangeConfig region, another frees remotely and
flushes, for more rounds than the region could survive if the frees were
lost.  It aborts on the unpatched tree and completes with this change.

Fixes microsoft#876.
@mjp41
Matthew Parkinson (mjp41) merged commit 511e91a into microsoft:main Sep 8, 2026
197 checks passed
@sblessing
Sebastian Blessing (sblessing) deleted the fix-remote-ring-domestication branch September 8, 2026 05:46
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.

FixedRangeConfig loses every batched remote deallocation: open_free_ring() runs the ring's bit-packed length through capptr_domesticate()

3 participants