A bug found by our static analysis tool
Severity: High
File: litebox_shim_linux/src/syscalls/mm.rs
/// Per-process collection of ELF patching state, keyed by fd number.
pub(crate) type ElfPatchCache = BTreeMap<i32, ElfPatchState>;
The documentation states the flaw. do_close_and_replace vacates the fd slot
under the descriptor-store guard, drops it, and only then runs the subsystem
teardown that calls finalize_elf_patch(raw_fd). Between those two points the
number is free and the next open receives it.
Two consequences follow:
fn init_elf_patch_state(&self, fd: i32, mapped_addr: usize, file_offset: usize) {
// Quick check: skip if already initialized.
if self.global.elf_patch_cache.lock().contains_key(&fd) {
return;
}
- The new file inherits the closed file's state — its
trampoline_addr,
pre_patched flag and cursor — because the key is already present.
- The pending
finalize_elf_patch then deletes that state, and if
trampoline_mapped was set it munmaps state.trampoline_addr, an address
belonging to a mapping the closer never owned.
Evidence. syscalls::mm::tests::patch_state_is_not_inherited_by_a_recycled_fd:
assertion `left != right` failed: fd 3 now belongs to /other, but the patch cache
still keys /patched's state to it
left: Some(3735879680) // 0xdead0000, the marker seeded for /patched
right: Some(3735879680)
The test seeds the cache entry directly rather than driving a real ELF mmap,
because init_elf_patch_state parses an ELF header and the test filesystem has
no ELF fixture. The interleaving, the fd reuse and the inheritance are real; only
the provenance of the state object is synthetic.
Suggested fix. Key the cache on file identity rather than the fd integer, or
finalise the patch state under the same guard that vacates the slot.
A bug found by our static analysis tool
Severity: High
File:
litebox_shim_linux/src/syscalls/mm.rsThe documentation states the flaw.
do_close_and_replacevacates the fd slotunder the descriptor-store guard, drops it, and only then runs the subsystem
teardown that calls
finalize_elf_patch(raw_fd). Between those two points thenumber is free and the next
openreceives it.Two consequences follow:
trampoline_addr,pre_patchedflag and cursor — because the key is already present.finalize_elf_patchthen deletes that state, and iftrampoline_mappedwas set it munmapsstate.trampoline_addr, an addressbelonging to a mapping the closer never owned.
Evidence.
syscalls::mm::tests::patch_state_is_not_inherited_by_a_recycled_fd:The test seeds the cache entry directly rather than driving a real ELF
mmap,because
init_elf_patch_stateparses an ELF header and the test filesystem hasno ELF fixture. The interleaving, the fd reuse and the inheritance are real; only
the provenance of the state object is synthetic.
Suggested fix. Key the cache on file identity rather than the fd integer, or
finalise the patch state under the same guard that vacates the slot.