Skip to content

sys::unix::fs::canonicalize can lead to undefined-ish behavior on Android #58862

@glandium

Description

@glandium

Its code is:

    let path = CString::new(p.as_os_str().as_bytes())?;
    let buf;
    unsafe {
        let r = libc::realpath(path.as_ptr(), ptr::null_mut());
        if r.is_null() {
            return Err(io::Error::last_os_error())
        }
        buf = CStr::from_ptr(r).to_bytes().to_vec();
        libc::free(r as *mut _);
    }
    Ok(PathBuf::from(OsString::from_vec(buf)))
}

The problem comes from the pair of calls libc::realpath, libc::free. Under normal conditions, libc::realpath calls malloc, libc::free corresponds to free and everyone lives happily every after.

Now, funky things are funky. Say you have a binary that makes malloc and free be jemalloc. From rust, libc::malloc and libc::free then point to jemalloc. But here's the catch: libc's malloc is not necessarily jemalloc's. That is, while on e.g. Linux, when you have such a setup, libc will happily use jemalloc's malloc/free for its allocations (as long as it's not built with -Bsymbolic or -Bsymbolic-functions), that's not the case on Android: IIRC, libc is linked in such a way that all its "internal" function calls stay internal (that is, it's built with -Bsymbolic). So when realpath calls malloc, it calls libc's not jemalloc's. And then canonicalize calls libc::free with the resulting pointer, which is jemalloc's. Kaboom.

Yes, there is __malloc_hook and friends, which would solve the problem if they existed on Android. (Well, they do now, but they don't on Android versions < 9 (and apparently 7 for IoT if I'm to believe tags in the bionic repository).

https://bugzilla.mozilla.org/show_bug.cgi?id=1531887 is a real issue that results from this, although it doesn't involve -Bsymbolic, but some subtle dynamic linking setup where rust code is in libxul.so, which depends on libmozglue.so, which provides malloc/free as jemalloc's, for use by libxul, but all native Android code still ends up using libc's malloc/free.

For this particular case, it's possible to pass an appropriately-sized buffer to realpath instead of null, which makes it allocate on its own (https://pubs.opengroup.org/onlinepubs/009696799/functions/realpath.html says PATH_MAX ; the only system I know that doesn't use those limits is Hurd). For the more general case, it almost feels like there should be an overridable GlobalAlloc for the non-rust system allocator.

Cc: @SimonSapin

Metadata

Metadata

Assignees

No one assigned

    Labels

    O-androidOperating system: AndroidT-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions