Skip to content

Unsound Use of CString::from_vec_unchecked in bytes_to_cstring #351

@lwz23

Description

@lwz23

Description
The bytes_to_cstring function uses unsafe { CString::from_vec_unchecked(buf) } to convert a Vec into a CString without validating the input. This introduces undefined behavior (UB) if the input vector buf contains any interior null bytes (\0), as CString guarantees that its contents are null-terminated and contain no interior null bytes.

pub fn bytes_to_cstring(buf: Vec<u8>) -> CString {

pub fn bytes_to_cstring(buf: Vec<u8>) -> CString {
    unsafe { std::ffi::CString::from_vec_unchecked(buf) }
}

Problems:
Undefined Behavior on Invalid Input:

If the input vector buf contains any interior null bytes, the invariants required by CString are violated, leading to undefined behavior.
Lack of Validation:

The function does not check whether the input vector satisfies the constraints required by CString. This shifts the responsibility of ensuring valid input to the caller without marking the function as unsafe or providing documentation.
Misleading Safety:

The function's signature does not indicate that it relies on the caller to ensure the absence of null bytes in the input. This can mislead users into thinking the function is safe to use with any Vec.
Poc:

fn main() {
    // Input vector with interior null bytes
    let invalid_buf = vec![72, 101, 108, 108, 0, 111]; // "Hell\0o"

    // Invoke the function
    let cstring = bytes_to_cstring(invalid_buf); // UB!
    println!("{:?}", cstring);
}

Expected behavior: The function should validate the input and reject invalid vectors with interior null bytes.

Actual behavior: The program invokes undefined behavior, which could lead to crashes, memory corruption, or other unpredictable results.
Fix:

pub fn bytes_to_cstring(buf: Vec<u8>) -> Result<CString, std::ffi::NulError> {
    CString::new(buf)
}

Additional Context:
The use of unsafe in CString::from_vec_unchecked is only valid if the caller guarantees that the input meets all the constraints required by CString. The current implementation of bytes_to_cstring does not enforce or document these constraints, making it unsound. By switching to CString::new, the function can remain safe and robust while adhering to Rust's guarantees.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions