Ridwanul-OpenRefactory opened issue #10031:
In file: options.rs, function new, a parameter is borrowed in a return value. But the lifetime of the parameter doesn't match the lifetime of the return value. It may cause memory bugs like Use After Free or Non Exclusive Mutability in several places in the code.
pub unsafe fn new( store: StoreContextMut<'a, T>, options: &'a Options, types: &'a ComponentTypes, instance: *mut ComponentInstance, ) -> LowerContext<'a, T> { LowerContext { store, options, types, instance, } }The parameter
instanceis a raw pointer inside the return value, but its lifetime is not compatible with the lifetime ('a) of the return value. Sinceinstanceis a raw pointer, it is not subject to Rust's borrow checker. The compiler does not enforce borrowing rules for raw pointers, such as ensuring no data races or aliasing for mutable pointers, or checking the validity of the memory they point to. As a result,instancemay outlive the lifetime of the return value, leading to potential memory bugs like use-after-free or non-exclusive mutabilityHow to access the raw pointer to trigger use-after-free:
pub unsafe fn bar( store: StoreContextMut<'a, T>, options: &'a Options, types: &'a ComponentTypes, instance: *mut ComponentInstance, ) -> LowerContext<'a, T> { // Convert the raw pointer into a Box to take ownership let boxed_instance = Box::from_raw(instance); // Drop the boxed instance to deallocate its memory drop(boxed_instance); // Calling the `new` function may cause memory bug }Here, we add a function
barwhere theBox::from_raw(instance)call takes ownership of the raw pointer. Callingdrop(boxed_instance)explicitly deallocates the memory thatinstancepoints to. If we create a newLowerContextobject, theinstancewill have dangling pointer.An example to demonstrate the impact:
Let us consider this example code that is identical to the mentioned code snippet-
struct Data<'a> { x: &'a str, y: *mut String, } fn bar<'a>(arg1: &'a String, arg2: *mut String) -> Data<'a> { // Data has max life-time 'a Data { x: arg1, y: arg2, } } fn foo() { let v1 = "Hello".to_string(); let mut v2 = "World".to_string(); let bar_obj = bar(&v1, &mut v2); // We dropped v2 but it is assigned inside bar_obj because of lifetime issues drop(v2); unsafe{ // This is where the use after free happens println!("Value of v2: {}", *bar_obj.y) } }Output:
Value of v2: +�r{As we can see in the output,
v2has garbage value. It is because we have droppedv2but it is assigned inside thebar_obj. Thedropfunction explicitly deallocatesv2, meaning the memory thatv2pointed to is freed. However, the raw pointer still points to this freed memory which causes use-after-free bug when we try to access theyfield ofbar_obj.Similarly, the
instanceparameter of the new function can be manifested for memory bugs.Possible Solution:
A possible solution is to ensure that the
instanceparameter has a safe lifetime tied to the lifetime'aof the return objectLowerContext<'a, T>:pub unsafe fn new( store: StoreContextMut<'a, T>, options: &'a Options, types: &'a ComponentTypes, - instance: *mut ComponentInstance, + instance: &'a ComponentInstance, ) -> LowerContext<'a, T> {The function signature now enforces that
instancemust have the same lifetime as'aand will be valid for as long asLowerContext<'a, T>exists.Sponsorship and Support:
This work is done by the security researchers from OpenRefactory under the Project Clean Beach initiative. The OpenRefactory researchers are proactively scanning open source critical open source projects and finding previously undetected bugs in them. The goal of Project Clean Beach is to improve global software supply chain security.
The bug is found by running the iCR tool by OpenRefactory, Inc. and then manually triaging the results.
bjorn3 commented on issue #10031:
and then manually triaging the results.
Clearly not enough given https://github.com/rust-lang/cargo/issues/15039.
The compiler does not enforce borrowing rules for raw pointers, such as ensuring no data races or aliasing for mutable pointers, or checking the validity of the memory they point to. As a result, instance may outlive the lifetime of the return value, leading to potential memory bugs like use-after-free or non-exclusive mutability
The safety invariant of
unsafe fn newsays that this must not happen. All callers ofneware required to manually prove that the safety invariant is upheld. Unless you can show a case where a caller ofnewdoesn't uphold the safety invariant ofnewthere is nothing to be learnt from this bullshit LLM generated report.
alexcrichton closed issue #10031:
In file: options.rs, function new, a parameter is borrowed in a return value. But the lifetime of the parameter doesn't match the lifetime of the return value. It may cause memory bugs like Use After Free or Non Exclusive Mutability in several places in the code.
pub unsafe fn new( store: StoreContextMut<'a, T>, options: &'a Options, types: &'a ComponentTypes, instance: *mut ComponentInstance, ) -> LowerContext<'a, T> { LowerContext { store, options, types, instance, } }The parameter
instanceis a raw pointer inside the return value, but its lifetime is not compatible with the lifetime ('a) of the return value. Sinceinstanceis a raw pointer, it is not subject to Rust's borrow checker. The compiler does not enforce borrowing rules for raw pointers, such as ensuring no data races or aliasing for mutable pointers, or checking the validity of the memory they point to. As a result,instancemay outlive the lifetime of the return value, leading to potential memory bugs like use-after-free or non-exclusive mutabilityHow to access the raw pointer to trigger use-after-free:
pub unsafe fn bar( store: StoreContextMut<'a, T>, options: &'a Options, types: &'a ComponentTypes, instance: *mut ComponentInstance, ) -> LowerContext<'a, T> { // Convert the raw pointer into a Box to take ownership let boxed_instance = Box::from_raw(instance); // Drop the boxed instance to deallocate its memory drop(boxed_instance); // Calling the `new` function may cause memory bug }Here, we add a function
barwhere theBox::from_raw(instance)call takes ownership of the raw pointer. Callingdrop(boxed_instance)explicitly deallocates the memory thatinstancepoints to. If we create a newLowerContextobject, theinstancewill have dangling pointer.An example to demonstrate the impact:
Let us consider this example code that is identical to the mentioned code snippet-
struct Data<'a> { x: &'a str, y: *mut String, } fn bar<'a>(arg1: &'a String, arg2: *mut String) -> Data<'a> { // Data has max life-time 'a Data { x: arg1, y: arg2, } } fn foo() { let v1 = "Hello".to_string(); let mut v2 = "World".to_string(); let bar_obj = bar(&v1, &mut v2); // We dropped v2 but it is assigned inside bar_obj because of lifetime issues drop(v2); unsafe{ // This is where the use after free happens println!("Value of v2: {}", *bar_obj.y) } }Output:
Value of v2: +�r{As we can see in the output,
v2has garbage value. It is because we have droppedv2but it is assigned inside thebar_obj. Thedropfunction explicitly deallocatesv2, meaning the memory thatv2pointed to is freed. However, the raw pointer still points to this freed memory which causes use-after-free bug when we try to access theyfield ofbar_obj.Similarly, the
instanceparameter of the new function can be manifested for memory bugs.Possible Solution:
A possible solution is to ensure that the
instanceparameter has a safe lifetime tied to the lifetime'aof the return objectLowerContext<'a, T>:pub unsafe fn new( store: StoreContextMut<'a, T>, options: &'a Options, types: &'a ComponentTypes, - instance: *mut ComponentInstance, + instance: &'a ComponentInstance, ) -> LowerContext<'a, T> {The function signature now enforces that
instancemust have the same lifetime as'aand will be valid for as long asLowerContext<'a, T>exists.Sponsorship and Support:
This work is done by the security researchers from OpenRefactory under the Project Clean Beach initiative. The OpenRefactory researchers are proactively scanning open source critical open source projects and finding previously undetected bugs in them. The goal of Project Clean Beach is to improve global software supply chain security.
The bug is found by running the iCR tool by OpenRefactory, Inc. and then manually triaging the results.
alexcrichton commented on issue #10031:
Please remove Wasmtime (and other Bytecode Alliance) projects from your automatic scans. Not only is this an inaccurate report which looks like it's generated by an LLM but it doesn't follow our security policy.
alexcrichton edited a comment on issue #10031:
Please remove Wasmtime (and other Bytecode Alliance projects) from your automatic scans. Not only is this an inaccurate report which looks like it's generated by an LLM but it doesn't follow our security policy.
Ridwanul-OpenRefactory commented on issue #10031:
Thanks a lot for evaluating the bug report and giving us advice on how to better triage the bug.
We did not use any LLMs to generate the finding (LLMs cannot analyze at that level) or the bug report. We have been doing this successfully for the past 18 months (300+ bugs found in critical OSS projects) for Java, Python and Go and with our tool expanding into Rust, we have just started doing this in Rust.
Clearly, from our initial feedback, our tool has to get better at detecting and we have to get better at manually vetting. In the other case referenced, our tool was not able to capture the concept of owned, which we have now fixed. In this case, we also did not pick the context correctly. With your guidance, in future we will be more careful about reporting such bugs.
Thanks again for the feedback.
Last updated: Dec 13 2025 at 19:03 UTC