Skip to content

Unwind safety issues for Kotlin and Swift bindings  #3

@VaynNecol

Description

@VaynNecol
#[no_mangle]
pub unsafe extern "C" fn stretch_node_compute_layout(
    stretch: *mut c_void,
    node: *mut c_void,
    width: f32,
    height: f32,
    create_layout: fn(*const f32) -> *mut c_void,
) -> *mut c_void {
    let mut stretch = Box::from_raw(stretch as *mut Stretch);
    let node = Box::from_raw(node as *mut Node);

    stretch
        .compute_layout(
            *node,
            Size {
                width: if f32::is_nan(width) { Number::Undefined } else { Number::Defined(width) },
                height: if f32::is_nan(height) { Number::Undefined } else { Number::Defined(height) },
            },
        )
        .unwrap();

    let mut output = vec![];
    copy_output(&stretch, *node, &mut output);

    Box::leak(stretch);
    Box::leak(node);

    create_layout(output.as_ptr())
}

The unwinding of stretch.compute_layout will introduce double free due to dropping stretch and node in the unwinding path. With the appearance of this question, we noticed that this kind of from_raw usage is not strong enough for FFI functions😨.

The leak should take forward to avoid the automated deallocation towards objects generating by from_raw. We found std::mem::ManuallyDrop is a good wrapper for us to avoid the drop as well as making the variable act like the smart pointer.

The fixed code we listed below. And this refinement is zero-cost.

#[no_mangle]
pub unsafe extern "C" fn stretch_node_compute_layout(
    stretch: *mut c_void,
    node: *mut c_void,
    width: f32,
    height: f32,
    create_layout: fn(*const f32) -> *mut c_void,
) -> *mut c_void {
    let mut stretch = mem::ManuallyDrop::new(Box::from_raw(stretch as *mut Stretch));
    let node = mem::ManuallyDrop::new(Box::from_raw(node as *mut Node));

    stretch
        .compute_layout(
            *node,
            Size {
                width: if f32::is_nan(width) { Number::Undefined } else { Number::Defined(width) },
                height: if f32::is_nan(height) { Number::Undefined } else { Number::Defined(height) },
            },
        )
        .unwrap();

    let mut output = vec![];
    copy_output(&stretch, *node, &mut output);
    create_layout(output.as_ptr())
}

We think all the usage (involving 24 functions) of from_raw in bindings for Swift and Kotlin should be changed by wrapping with ManuallyDrop to avoid double free incurred by unwinding 😊.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions