Skip to content

Potential soundness issue in ffi-convert 0.7.0 #66

@varies-rust

Description

@varies-rust

Hello, and thank you for maintaining this crate.

I found a sanitizer/Miri failure reachable through public crate APIs using safe Rust code. I may be missing crate-specific preconditions, but the behavior looks worth checking because safe callers should not be able to trigger undefined behavior.

Summary

  • Crate: ffi-convert
  • Version tested: 0.7.0
  • API paths with similar failures observed:
    • CStringArray::c_repr_of
    • CStringArray::do_drop
    • CStringArray::drop

Observed diagnostics

MemorySanitizer use of uninitialized value: WARNING: MemorySanitizer: use-of-uninitialized-value
Miri undefined behavior: error: Undefined Behavior: constructing invalid value of type std::boxed::Box<[*mut i8]>: encountered a dangling box (use-after-free)

Reproduction

The snippets below are minimal readable reproducers. Each PoC also includes the source location most relevant to the reported failure.

PoC 1: test_ffi_convert0::generated_test_0

Relevant source location:

  • src/types.rs:68-77: do_drop reconstructs and frees the boxed raw pointer slice.
  • src/types.rs:82-84: Drop calls do_drop again if the value is later dropped normally.

Readable equivalent PoC:

use ffi_convert::{CDrop, CReprOf};

#[test]
fn poc() {
    let strings = vec![String::from("a")];
    let mut c_array = ffi_convert::CStringArray::c_repr_of(strings).unwrap();

    c_array.do_drop().unwrap();
    // `c_array` is dropped again at the end of the scope.
}

Observed diagnostic:

WARNING: MemorySanitizer: use-of-uninitialized-value

PoC 2: test_ffi_convert0::generated_test_0

Relevant source location:

  • src/types.rs:68-77: do_drop reconstructs and frees the boxed raw pointer slice.
  • src/types.rs:82-84: Drop calls do_drop again if the value is later dropped normally.

Readable equivalent PoC:

use ffi_convert::{CDrop, CReprOf};

#[test]
fn poc() {
    let strings = vec![String::from("a")];
    let mut c_array = ffi_convert::CStringArray::c_repr_of(strings).unwrap();

    c_array.do_drop().unwrap();
    // `c_array` is dropped again at the end of the scope.
}

Observed diagnostic:

error: Undefined Behavior: constructing invalid value of type std::boxed::Box<[*mut i8]>: encountered a dangling box (use-after-free)

Source review and suggested fix

Brief reasoning:

  • do_drop frees the owned pointers but leaves the struct fields intact.
  • A later Drop then frees the same allocation again.

Suggested fix:

  • Make the drop helper idempotent by nulling the pointer and zeroing the length after freeing, or consume self for explicit destruction.

Thanks again for taking a look.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions