mirror of
https://github.com/HeyPuter/gex.git
synced 2026-11-13 20:12:43 -10:00
libprov: a forked child keeps its parent's replaced libraries
A forked child's shim starts with no libprov state, so the addresses a replaced library's exports were bound at fell to tier 1, which ran the original library's code over the provider's state: with zlib's own allocator, Debian's zcfree handed the provider's heap blocks to glibc's free and the child jumped to garbage. Before a Worker's guest first runs (gex_elfprov_wanted, with ELF providers on or off), a new process now binds the replaced libraries it inherited again, over the data and code page its parent made (found by mapping name): the child's provider has its parent's heap and open streams. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_1896c5b302a54696aaa92c5f4047f20b
This commit is contained in:
1 parent
ff4bbc90aa
commit
be9eba23ad
2 files changed
+59
-5
No files matched your search
@@ -204,10 +204,14 @@ fn unbind_overlapped(m: &gex_kernel::CodeMap, trace: bool) {
|
||||
/// it inherited.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn gex_elfprov_wanted() -> i32 {
|
||||
if !on() {
|
||||
return lock(&QUEUE).pop_front().map_or(-1, |u| u as i32);
|
||||
if on() {
|
||||
rescan_if_new();
|
||||
}
|
||||
// Replaced libraries too, with providers on or off (after the rescan,
|
||||
// which starts the queue over).
|
||||
if let Some(u) = super::libprov::rescan_if_new() {
|
||||
lock(&QUEUE).push_back(u);
|
||||
}
|
||||
rescan_if_new();
|
||||
lock(&QUEUE).pop_front().map_or(-1, |u| u as i32)
|
||||
}
|
||||
|
||||
|
||||
@@ -111,6 +111,45 @@ fn alloc_low(size: u64, prot: u32, name: &str) -> Result<u64, String> {
|
||||
crate::with_host(|h| h.kernel.mem.mmap_low(0, len, prot, flags::PRIVATE | flags::ANONYMOUS, name)).map_err(|e| format!("mapping {name}: errno {}", e.0))
|
||||
}
|
||||
|
||||
/// A forked child's copy of the mapping [`alloc_low`] made in its parent
|
||||
/// (by name): the provider's data, heap and code page are the child's as
|
||||
/// the rest of its memory is.
|
||||
fn inherited(name: &str) -> Option<u64> {
|
||||
crate::with_host(|h| h.kernel.mem.vmas().iter().find(|v| v.name == name).map(|v| v.start))
|
||||
}
|
||||
|
||||
/// The process the state above is for: a forked child's shim starts with
|
||||
/// none of it (or its parent's copy), while its memory holds the parent's
|
||||
/// replaced libraries, their providers' data and heap, and the guest's
|
||||
/// pointers into them.
|
||||
static PID: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
|
||||
|
||||
/// Before a Worker's guest first runs (`gex_elfprov_wanted`): in a new
|
||||
/// process, the replaced libraries it inherited are bound again, over the
|
||||
/// data and code page its parent made (a child must not start a provider
|
||||
/// over: an open `z_stream` points into its heap). Returns a unit to serve.
|
||||
pub(crate) fn rescan_if_new() -> Option<usize> {
|
||||
let pid = crate::with_host(|h| h.kernel.pid);
|
||||
let was = PID.swap(pid, Ordering::AcqRel);
|
||||
if was == pid || lock(&DLLS).is_empty() {
|
||||
return None;
|
||||
}
|
||||
lock(&LIBS).clear();
|
||||
lock(&REPLACED).clear();
|
||||
THREAD_ENTRY.store(0, Ordering::Release);
|
||||
let mapped: Vec<gex_kernel::CodeMap> = crate::with_host(|h| {
|
||||
use gex_kernel::mem::prot;
|
||||
h.kernel
|
||||
.mem
|
||||
.vmas()
|
||||
.iter()
|
||||
.filter(|v| v.prot & prot::EXEC != 0)
|
||||
.filter_map(|v| v.code.as_ref().map(|(p, off)| gex_kernel::CodeMap { start: v.start, end: v.end, path: p.clone(), offset: *off }))
|
||||
.collect()
|
||||
});
|
||||
maps(&mapped)
|
||||
}
|
||||
|
||||
/// After a syscall that mapped code: a replaced library becomes a unit with
|
||||
/// one provider for JS to serve (`elfprov`'s queue). Returns whether one was
|
||||
/// queued.
|
||||
@@ -119,6 +158,9 @@ pub(crate) fn maps(mapped: &[gex_kernel::CodeMap]) -> Option<usize> {
|
||||
if dlls.is_empty() {
|
||||
return None;
|
||||
}
|
||||
if PID.load(Ordering::Acquire) == 0 {
|
||||
PID.store(crate::with_host(|h| h.kernel.pid), Ordering::Release);
|
||||
}
|
||||
let mut queued = None;
|
||||
for m in mapped {
|
||||
let file = m.path.rsplit('/').next().unwrap_or(&m.path);
|
||||
@@ -167,7 +209,11 @@ fn prepare(m: &gex_kernel::CodeMap, wasm_path: &str) -> Result<Option<usize>, St
|
||||
}
|
||||
}
|
||||
if let Some(k) = slot("__gex_thread_start") {
|
||||
let at = alloc_low(CODE_PAGE, gex_kernel::mem::prot::READ, &format!("[provider {file} code]", file = wasm_path))?;
|
||||
let name = format!("[provider {wasm_path} code]");
|
||||
let at = match inherited(&name) {
|
||||
Some(a) => a,
|
||||
None => alloc_low(CODE_PAGE, gex_kernel::mem::prot::READ, &name)?,
|
||||
};
|
||||
pairs.push((at, k));
|
||||
THREAD_ENTRY.store(at, Ordering::Release);
|
||||
if let Some(r) = slot("gexp__reverse") {
|
||||
@@ -189,7 +235,11 @@ fn prepare(m: &gex_kernel::CodeMap, wasm_path: &str) -> Result<Option<usize>, St
|
||||
lock(&MISSING).get_or_insert_with(Default::default).extend(unprovided);
|
||||
let (size, align) = dylink_memory(&wasm)?;
|
||||
let a = 1u64 << align;
|
||||
let data = alloc_low((size as u64).max(16) + a, gex_kernel::mem::prot::READ | gex_kernel::mem::prot::WRITE, &format!("[provider {wasm_path} data]"))?;
|
||||
let name = format!("[provider {wasm_path} data]");
|
||||
let data = match inherited(&name) {
|
||||
Some(d) => d,
|
||||
None => alloc_low((size as u64).max(16) + a, gex_kernel::mem::prot::READ | gex_kernel::mem::prot::WRITE, &name)?,
|
||||
};
|
||||
let data = (data + a - 1) & !(a - 1);
|
||||
crate::warn_line(&format!("gex: {} replaced by {wasm_path}: {} addresses bound, data at {data:#x}\n", m.path, pairs.len()));
|
||||
let index = {
|
||||
|
||||
Reference in new issue
Block a user