litebox_shim_linux: fix race in prepare_for_exit child TID write - #1155
Open
Leon Schuermann (lschuermann) wants to merge 1 commit into
Open
Conversation
This fixes a bug in `prepare_for_exit` in the LiteBox Linux shim. When a
thread exits, it may indicate this by writing its child TID to a
location in user memory. However, prior to this, `detach_from_process`
is run, which decrements `nr_threads`. This is a signal to other tasks
that this task no longer holds any references to process memory, and
thus that the memory is ready to be deallocated.
In the case of, e.g., an `exec` system call, this in turn introduces a
"confused deputy" style write where the LiteBox kernel will either
attempt to write to some unmapped page (caught by the exception handlers
and silently turned into a no-op), or write into and corrupt the new
process' memory.
The Race Condition
------------------
+----+------------------------------+------------------------------+
| # | A (main / exec-ing thread) | B (worker / dying thread) |
+----+------------------------------+------------------------------+
| 1 | pthread_create(), passing | |
| | CLONE_CHILD_CLEARTID = | |
| | &pd->tid | |
+----+------------------------------+------------------------------+
| 2 | | starts running |
| | | - clear_child_tid = addr |
| | | inside B's stack |
| | | - nr_threads -> 2 |
+----+------------------------------+------------------------------+
| 3 | | recv(fd, buf, ...), blocks |
+----+------------------------------+------------------------------+
| 4 | execve("/new/program") | blocked |
+----+------------------------------+------------------------------+
| 5 | kill_other_threads() | blocked |
| | - sets is_exiting, | |
| | interrupts B | |
| | - waits nr_threads == 1 | |
+----+------------------------------+------------------------------+
| 6 | waiting | recv returns EINTR |
| | | - drops Task -> |
| | | prepare_for_exit() |
+----+------------------------------+------------------------------+
| 7 | waiting | detach_from_process() |
| | | - nr_threads 2 -> 1, |
| | | wakes A |
| | | - DECLARED DONE, but |
| | | guest write pending |
+----+------------------------------+------------------------------+
| 8 | kill_other_threads() | |
| | RETURNS | |
| | - process assumed | |
| | quiesced | |
+----+------------------------------+------------------------------+
| 9 | | descheduled |
| | | - still owes write to |
| | | clear_child_tid |
+----+------------------------------+------------------------------+
| 10 | release_memory() UNMAP | descheduled |
| | - destroys all old | |
| | mappings, incl. B's | |
| | stack | |
| | - ranges immediately | |
| | reusable, no | |
| | quarantine | |
+----+------------------------------+------------------------------+
| 11 | load_program() REMAP | descheduled |
| | - new ELF/stack/heap | |
| | from same pool | |
| | - may land on the | |
| | freed range | |
+----+------------------------------+------------------------------+
| 12 | new program runs | clear_child_tid |
| | | .write_at_offset(0, 0) |
| | | - confused-deputy write |
| | | into new image |
+----+------------------------------+------------------------------+
| 13 | new program runs | sys_futex(Wake), |
| | | wake_robust_list() |
| | | - more accesses via |
| | | old-image addresses |
+----+------------------------------+------------------------------+
The Fix
-------
Don't decrement the `nr_threads` counter prior to completing any writes
to that process' address space.
`memcpy_fallible` provides fault safety, but not lifetime safety.
I used Claude Opus 5 to inspect the codebase for this class of bug, help
me verify that it can be triggered, and validate the fix.
Leon Schuermann (lschuermann)
force-pushed
the
dev/litebox-shim-linux-thread-detach-race
branch
from
August 10, 2026 18:23
ef98547 to
93fa9eb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This fixes a bug in
prepare_for_exitin the LiteBox Linux shim. When a thread exits, it may indicate this by writing its child TID to a location in user memory. However, prior to this,detach_from_processis run, which decrementsnr_threads. This is a signal for other tasks that this task no longer holds any references to process memory, and thus is ready to be deallocated. In the case of, e.g., anexecsystem call, this in turn introduces a "confused deputy" style write where the LiteBox kernel will either attempt to write to some unmapped pages (caught by the exception handlers and silently turned into a no-op), or writes into and corrupts the new process' memory.The Race Condition
pthread_create(), passingCLONE_CHILD_CLEARTID = &pd->tidclear_child_tid= address inside B's stacknr_threadsincrement to 2recv(fd, buf, ...), blocksexecve("/new/program")kill_other_threads()is_exiting, interrupts Bnr_threads == 1recvreturnsEINTRTask, dropsThreadState, runsprepare_for_exit()detach_from_process()nr_threads2 → 1, wakes Akill_other_threads()returnsclear_child_tidrelease_memory(), UNMAPload_program(), REMAPclear_child_tid.write_at_offset(0, 0)sys_futex(Wake),wake_robust_list()The Fix
Don't decrement the
nr_threadscounter prior to completing any writes to that process' address space.memcpy_fallibleprovides fault safety, but not lifetime safety. I used Claude Opus 5 inspect the codebase for this class of bug, and help me verify that it can be triggered, as well as validate the fix.