-
Notifications
You must be signed in to change notification settings - Fork 825
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dangling pointer in JIT Engine #1568
Comments
Adding impl Drop for CodeMemory {
fn drop(&mut self) {
for section in self.read_sections.iter() {
println!("dropping section at: {:p} {:?}", section.as_ptr(), section);
}
}
} and this print demonstrates that the Vec is dropped before the pointer to it gets passed to impl Drop for UnwindRegistry {
fn drop(&mut self) {
if self.published {
unsafe {
// libgcc stores the frame entries as a linked list in decreasing sort order
// based on the PC value of the registered entry.
//
// As we store the registrations in increasing order, it would be O(N^2) to
// deregister in that order.
//
// To ensure that we just pop off the first element in the list upon every
// deregistration, walk our list of registrations backwards.
for fde in self.registrations.iter().rev() {
println!("deregistering fde at {:p}, {:?}", *fde as *const u8, &*slice_from_raw_parts(*fde as *const u8, 4));
__deregister_frame(*fde as *const _);
}
}
}
}
} This prints then:
The print in the drop impl for |
The problem is twofold. The one you found is correct! But you have also #1581 that fixes the root of the problem. |
Thank you for your help by the help! I wasn't expected that much involvement! |
I'd expect the issue to persist tho, maybe it's harder to trigger. There is still no mechanism to ensure that the If
No worries, that was a fun exercise ;) |
Correct! I've created a new PR to fix that! |
Sorry to keep bugging you, but that's also just another band-aid, there's still unsoundness in safe code I'm aware that this particular case passes with the empty-frame fix, but here you have no control over drop-order. There needs to be some association between the use std::mem;
use wasmer_engine_jit::CodeMemory;
use wasmer_compiler::SectionBody;
fn main() {
let store = wasmer::Store::default();
let bytes = wat::parse_bytes(b"(module)").unwrap();
let artifact = store.engine().compile(&bytes, store.tunables()).unwrap();
mem::drop(store);
} |
Changing the drop impl a bit on impl Drop for UnwindRegistry {
fn drop(&mut self) {
if self.published {
unsafe {
// libgcc stores the frame entries as a linked list in decreasing sort order
// based on the PC value of the registered entry.
//
// As we store the registrations in increasing order, it would be O(N^2) to
// deregister in that order.
//
// To ensure that we just pop off the first element in the list upon every
// deregistration, walk our list of registrations backwards.
for fde in self.registrations.iter().rev() {
let s = &mut *slice_from_raw_parts_mut(*fde as *mut u8, 4);
s[0] = s[0];
__deregister_frame(*fde as *const _);
}
}
}
}
} Running the following code with use std::mem;
use wasmer_engine_jit::CodeMemory;
use wasmer_compiler::SectionBody;
fn main() {
let store = wasmer::Store::default();
let bytes = wat::parse_bytes(b"(module
(func (export \"add\") (param $x i64) (param $y i64) (result i64) (i64.add (local.get $x) (local.get $y)))
)").unwrap();
let artifact = store.engine().compile(&bytes, store.tunables()).unwrap();
mem::drop(store);
} $ RUSTFLAGS="-Z sanitizer=address" cargo run --target x86_64-unknown-linux-gnu
dropping section at: 0x607000000d40 [20, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 120, 16, 12, 7, 8, 144, 1, 0, 0, 0, 0, 0, 0, 36, 0, 0, 0, 28, 0, 0, 0, 0, 144, 19, 64, 99, 127, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 66, 14, 16, 134, 2, 67, 13, 6, 72, 12, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0] 68 68
deregistering 0x607000000d40
=================================================================
==25181==ERROR: AddressSanitizer: heap-use-after-free on address 0x607000000d40 at pc 0x555c1ceccd28 bp 0x7ffdebe87c50 sp 0x7ffdebe87c48
READ of size 1 at 0x607000000d40 thread T0
AddressSanitizer:DEADLYSIGNAL
AddressSanitizer: nested bug in the same thread, aborting. |
I wasn't able to reproduce this on master. I also tried reverting #1628 that might have fixed this and even locally reverting that change I still didn't see the failure. I added examples/issue1568.rs with:
and added the example to Cargo.toml and ran Is this bug still present? |
No. #1628 fixes this issue. I'm closing it. |
We are still seeing this issue in 4.2.2. Stack trace example here: https://gist.github.com/gorgos/b8a485d2c31430728bdad7b04d985eca. Only with Docker + Alpine Linux + muslc though. Edit: Issue now being tracked here: #4488 |
Describe the bug
Initially reported by @Hywan at PyO3/pyo3#1120
This test causes abort on linux:
Stepping through the code shows that the abort happens while dropping
wasmer_engine_jit::unwind::systemv::UnwindRegistry
. More specifically, the last call before abort is__deregister_frame
inUnwindRegistry::drop
.Artifact
holds a pointer to aVec
owned byStore
that gets passed to__deregister_frame
. If theStore
goes out first, the pointer inArtifact
'sUnwindRegistry
becomes invalid but gets used anyways.Allocation happens here:
wasmer/lib/engine-jit/src/artifact.rs
Lines 165 to 166 in 80290e4
Subverting lifetimes happens here:
wasmer/lib/engine-jit/src/unwind/systemv.rs
Line 86 in 80290e4
Assuming dead-pointers are still alive happens here:
wasmer/lib/engine-jit/src/unwind/systemv.rs
Lines 104 to 106 in 80290e4
A fix is switching the order of the struct members in
wasmer::Module
since dropping happens in reverse order. It'd probably be cleaner to keep theVec
alive independently of member order.Steps to reproduce
Create project with test and run cargo test.
Expected behavior
Exit succesfully
Actual behavior
(signal: 6, SIGABRT: process abort signal)
Additional context
The text was updated successfully, but these errors were encountered: