Skip to content

[Security] FFI layer vulnerabilities: buffer overflow and NULL pointer dereference #4084

Closed as not planned

Description

@CupCupRay

Summary

We discovered two security vulnerabilities in hyper's unstable FFI layer (--cfg hyper_unstable_ffi):

  1. Buffer overflow in read callback (CWE-120, HIGH severity)
  2. NULL pointer dereference in hyper_buf_copy (CWE-476, HIGH severity)

Both issues affect only the C FFI API and do not impact regular Rust users.


Issue 1: Buffer Overflow in FFI Read Callback

Location: src/ffi/io.rs:162

Description:
The hyper_io read callback does not validate the return value before calling buf.advance(ok), which can cause buffer overflow if the callback returns a value larger than the buffer size.

Vulnerable Code:

match (self.read)(self.userdata, hyper_context::wrap(cx), buf_ptr, buf_len) {
    ok => {
        // We have to trust that the user's read callback actually
        // filled in that many bytes... :(
        unsafe { buf.advance(ok) };  // ← No bounds check
        Poll::Ready(Ok(()))
    }
}

Impact:

  • Buffer overflow leading to memory corruption
  • Potential remote code execution in specific scenarios

Suggested Fix:

match (self.read)(self.userdata, hyper_context::wrap(cx), buf_ptr, buf_len) {
    ok if ok <= buf_len => {
        unsafe { buf.advance(ok) };
        Poll::Ready(Ok(()))
    }
    invalid => {
        Poll::Ready(Err(io::Error::new(
            io::ErrorKind::InvalidInput,
            format!("read callback returned {} but buffer size is {}", invalid, buf_len)
        )))
    }
}

PoC: Available upon request (demonstrates UB with inflated return value)


Issue 2: NULL Pointer Dereference in hyper_buf_copy

Location: src/ffi/body.rs:262

Description:
The hyper_buf_copy() function does not check if the input hyper_buf pointer is NULL before dereferencing it.

Vulnerable Code:

#[no_mangle]
pub unsafe extern "C" fn hyper_buf_copy(buf: *const hyper_buf, dst: *mut u8, len: usize) -> usize {
    let buf = &*buf;  // ← Direct dereference without NULL check
    let src = buf.bytes();
    let n = std::cmp::min(len, src.len());
    std::ptr::copy_nonoverlapping(src.as_ptr(), dst, n);
    n
}

Impact:

  • Denial of service (program crash)
  • Violation of FFI safety expectations

Suggested Fix:

#[no_mangle]
pub unsafe extern "C" fn hyper_buf_copy(buf: *const hyper_buf, dst: *mut u8, len: usize) -> usize {
    if buf.is_null() || dst.is_null() {
        return 0;
    }
    let buf = &*buf;
    let src = buf.bytes();
    let n = std::cmp::min(len, src.len());
    std::ptr::copy_nonoverlapping(src.as_ptr(), dst, n);
    n
}

PoC: Available upon request (demonstrates crash with NULL pointer)


References

  • CWE-120: Buffer Copy without Checking Size of Input
  • CWE-476: NULL Pointer Dereference
  • CWE-805: Buffer Access with Incorrect Length Value

We have working PoCs for both vulnerabilities and can provide additional details if needed.

Activity

  1. seanmonstar commented on May 28, 2026

    @seanmonstar
    Member

    Warning

    🚨 First of all, if reporting a security vulnerability, we request that you follow the responsible disclosure outlined in the SECURITY.md policy. 🚨

    That said, neither of the issues are vulnerabilities.


    1. Buffer Overflow in Read

    The documentation for hyper_io_set_read already makes it very clear that returning a value larger than the buf is illegal:

    It is undefined behavior to try to access the bytes in the buf pointer, unless you have already written them yourself. It is also undefined behavior to return that more bytes have been written than actually set on the buf.

    2. Null Pointer Deref hyper_buf_copy

    It is implied by the function that you should not pass a null pointer to hyper_buf_copy, since it is copying the buffer. For defensive programming, we can add a null check, the crate has a macro to make that easier. But that it doesn't check isn't a vulnerability in hyper.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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