Uninitialized data from external functions

A lot of compound types in the libc crate contain initialized types which variables of it are assumed to be init. But sometimes we call external functions returning (usually by outpointer) these types and in their program, and those functions may not initialize every byte that rust requires to be initialized.
For example, the ifreq type's ifr_name field is represented as an array of c_char (type alias for i8 usually, and i8 is a type variables of which rust requires to be initialized). But ifr_name should be a c-style string representing the name of the network interface on which this ifreq is requesting operations, and if I obtained an ifreq from some external library, say:

// libfoo
struct ifreq foo()
{
    struct ifreq r;
    strlcpy(r.ifr_name, "en0", sizeof(r.ifr_name));
    /* initalizing other fields */
    return r;
}
#[link(name = "foo")]
unsafe extern "C"
{
    fn foo() -> libc::ifreq;
}

In the libfoo code, as we can see, only the first 4 c_chars are properly initialized. So calling foo in rust invokes undefined behavior?
So my main query is, can we call foo in rust anyway, even though the program's outcome is, in theory, unpredictable? Or do we have to instead write the ffi declaration of foo as:

#[link(name = "foo")]
unsafe extern "C"
{
    fn foo() -> MaybeUninit<libc::ifreq>;
}

Will the compiler actually care about whether data from external functions are "initialized"? Maybe it only cares about bit patterns for those?

That's a good question. I think such combination of a C implementation and a return-by-value API is unsound.

It allows Rust code to see uninitialized memory. Uninitialized memory isn't just some garbage — it could contain secrets leaked from other data on the stack, similar to the Heartbleed vulnerability.

If the C function directly returned [u8; 16] with only 1 byte initialized, then it would clearly be an unsound API. Wrapping the same array in a struct (without any special types like UnsafeCell in between) doesn't change the requirements, so I think the partially initialized struct is still problematic.

This could be made safe at zero cost if the string field was a wrapper around [MaybeUninit<u8>; N], like ArrayString, but for NUL-terminated strings. But a plain [u8; N] may be a bad translation of the C type into Rust if you're going to return it by value.


In practice, if you use such struct from Rust, you will typically initialize it first anyway with let r: libc::ifreq = std::mem::zeroed(). Then it would be safe for the libc function to write just the NUL-terminated string to the beginning of the array (hopefully it wouldn't deliberately deinit the rest of the bytes!). So I think such struct definition is in general usable in the libc crate, just needs to be used in a specific way. I've raised an issue just in case.