From 4eed03801889f38ac5af136358a963dac6603f37 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 8 Aug 2022 11:06:28 -0700 Subject: [PATCH] Switch from `.init_array` constructors to /proc/self/auxv. (#385) * Switch from `.init_array` constructors to /proc/self/auxv. In the linux_raw backend, switch from using a `.init_array` constructor for obtaining the aux values to reading them from /proc/self/auxv. This avoids problems in situation where other Rust code can run before the constructor, potentially distrupting the `__environ` value. Also, for the linux_raw backend, introduce a new "use-libc-auxv" feature, which enables use of libc to read the aux values, instead of reading them from /proc/self/auxv. The "use-libc-auxv" option is enabled by default, because it's more efficient and doesn't depend on /proc, so it's likely better for most users. Mustang, for its part, continues to be able to use the incoming auxv on the stack because it controls program startup. Since it doesn't have to worry about the hazards of /proc or QEMU, it can trust the incoming values, and do less checking. Fixes #382. * Remove the param `init` function from the libc backend. * Fix the no-std build. * Fix the backends test to accept that the default options now depend on libc. * Use `NonNull` in `check_raw_pointer`'s return type. This allows it to pack the return value into a single pointer-sized value. * Update more code to the new `check_raw_pointer` API. * Fix an unused-import warning. * Fix copy+paste. * Thread `check_elf_base` through `check_vdso_base` too. --- Cargo.toml | 13 +- src/backend/libc/param/auxv.rs | 12 - src/backend/linux_raw/elf.rs | 4 + src/backend/linux_raw/param/auxv.rs | 410 ++++++++++++++------ src/backend/linux_raw/param/libc_auxv.rs | 74 ++++ src/backend/linux_raw/param/mod.rs | 11 + src/backend/linux_raw/param/mustang_auxv.rs | 159 ++++++++ src/backend/linux_raw/vdso.rs | 372 ++++++------------ src/fs/mod.rs | 18 +- src/param/mod.rs | 10 +- src/utils.rs | 16 + test-crates/use-rustix-auxv/Cargo.toml | 9 + test-crates/use-rustix-auxv/src/lib.rs | 0 tests/backends.rs | 30 +- 14 files changed, 745 insertions(+), 393 deletions(-) create mode 100644 src/backend/linux_raw/param/libc_auxv.rs create mode 100644 src/backend/linux_raw/param/mustang_auxv.rs create mode 100644 test-crates/use-rustix-auxv/Cargo.toml create mode 100644 test-crates/use-rustix-auxv/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index c88907ba..bb2ead0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,7 +100,13 @@ targets = [ ] [features] -default = ["std"] + +# By default, use `std` and use libc for aux values. +# +# It turns out to be bizarrely awkward to obtain the aux values reliably and +# efficiently on Linux from anywhere other than libc. We can do it, but most +# users are better served by just using libc for this. +default = ["std", "use-libc-auxv"] # This enables use of std. Disabling this enables `#![no_std], and requires # nightly Rust. @@ -170,6 +176,11 @@ all-apis = [ "time", ] +# When using the linux_raw backend, and not using Mustang, should we use libc +# for reading the aux vectors, instead of reading them ourselves from +# /proc/self/auxv? +use-libc-auxv = ["libc"] + # Expose io-lifetimes' features for third-party crate impls. async-std = ["io-lifetimes/async-std"] tokio = ["io-lifetimes/tokio"] diff --git a/src/backend/libc/param/auxv.rs b/src/backend/libc/param/auxv.rs index 584329da..a770c60d 100644 --- a/src/backend/libc/param/auxv.rs +++ b/src/backend/libc/param/auxv.rs @@ -52,15 +52,3 @@ pub(crate) fn linux_execfn() -> &'static CStr { cstr!("") } } - -/// Initialize process-wide state. -#[cfg(any( - target_vendor = "mustang", - not(any(target_env = "gnu", target_env = "musl")), -))] -#[inline] -#[doc(hidden)] -pub(crate) unsafe fn init(_envp: *mut *mut u8) { - // Nothing to do. This is the libc backend, and libc does the - // initialization for us. -} diff --git a/src/backend/linux_raw/elf.rs b/src/backend/linux_raw/elf.rs index 7b9bb324..87fb5fa0 100644 --- a/src/backend/linux_raw/elf.rs +++ b/src/backend/linux_raw/elf.rs @@ -1,6 +1,10 @@ //! The ELF ABI. #![allow(non_snake_case)] +#![cfg_attr( + all(not(target_vendor = "mustang"), feature = "use-libc-auxv"), + allow(dead_code) +)] pub(super) const SELFMAG: usize = 4; pub(super) const ELFMAG: [u8; SELFMAG] = [0x7f, b'E', b'L', b'F']; diff --git a/src/backend/linux_raw/param/auxv.rs b/src/backend/linux_raw/param/auxv.rs index 3a86fded..1111736e 100644 --- a/src/backend/linux_raw/param/auxv.rs +++ b/src/backend/linux_raw/param/auxv.rs @@ -6,42 +6,77 @@ #![allow(unsafe_code)] use super::super::c; -use super::super::elf::{Elf_Ehdr, Elf_Phdr}; +use super::super::elf::*; #[cfg(feature = "param")] use crate::ffi::CStr; +use crate::fs::{Mode, OFlags}; +use crate::io::OwnedFd; +use crate::utils::as_ptr; +use crate::utils::check_raw_pointer; +use alloc::vec::Vec; use core::ffi::c_void; use core::mem::size_of; -use core::ptr::null; +use core::ptr::NonNull; +use core::ptr::{null_mut, read_unaligned}; #[cfg(feature = "runtime")] use core::slice; +use core::sync::atomic::Ordering::Relaxed; +use core::sync::atomic::{AtomicPtr, AtomicUsize}; use linux_raw_sys::general::{ - AT_CLKTCK, AT_EXECFN, AT_HWCAP, AT_HWCAP2, AT_NULL, AT_PAGESZ, AT_PHDR, AT_PHENT, AT_PHNUM, - AT_SYSINFO_EHDR, + AT_BASE, AT_CLKTCK, AT_EXECFN, AT_HWCAP, AT_HWCAP2, AT_NULL, AT_PAGESZ, AT_PHDR, AT_PHENT, + AT_PHNUM, AT_SYSINFO_EHDR, }; #[cfg(feature = "param")] #[inline] pub(crate) fn page_size() -> usize { - auxv().page_size + let mut page_size = PAGE_SIZE.load(Relaxed); + + if page_size == 0 { + init_from_proc_self_auxv(); + page_size = PAGE_SIZE.load(Relaxed); + } + + page_size } #[cfg(feature = "param")] #[inline] pub(crate) fn clock_ticks_per_second() -> u64 { - auxv().clock_ticks_per_second as u64 + let mut ticks = CLOCK_TICKS_PER_SECOND.load(Relaxed); + + if ticks == 0 { + init_from_proc_self_auxv(); + ticks = CLOCK_TICKS_PER_SECOND.load(Relaxed); + } + + ticks as u64 } #[cfg(feature = "param")] #[inline] pub(crate) fn linux_hwcap() -> (usize, usize) { - let auxv = auxv(); - (auxv.hwcap, auxv.hwcap2) + let mut hwcap = HWCAP.load(Relaxed); + let mut hwcap2 = HWCAP2.load(Relaxed); + + if hwcap == 0 || hwcap2 == 0 { + init_from_proc_self_auxv(); + hwcap = HWCAP.load(Relaxed); + hwcap2 = HWCAP2.load(Relaxed); + } + + (hwcap, hwcap2) } #[cfg(feature = "param")] #[inline] pub(crate) fn linux_execfn() -> &'static CStr { - let execfn = auxv().execfn; + let mut execfn = EXECFN.load(Relaxed); + + if execfn.is_null() { + init_from_proc_self_auxv(); + execfn = EXECFN.load(Relaxed); + } // Safety: We assume the `AT_EXECFN` value provided by the kernel is a // valid pointer to a valid NUL-terminated array of bytes. @@ -51,143 +86,276 @@ pub(crate) fn linux_execfn() -> &'static CStr { #[cfg(feature = "runtime")] #[inline] pub(crate) fn exe_phdrs() -> (*const c::c_void, usize) { - let auxv = auxv(); - (auxv.phdr.cast(), auxv.phnum) + let mut phdr = PHDR.load(Relaxed); + let mut phnum = PHNUM.load(Relaxed); + + if phdr.is_null() || phnum == 0 { + init_from_proc_self_auxv(); + phdr = PHDR.load(Relaxed); + phnum = PHNUM.load(Relaxed); + } + + (phdr.cast(), phnum) } #[cfg(feature = "runtime")] #[inline] pub(in super::super) fn exe_phdrs_slice() -> &'static [Elf_Phdr] { - let auxv = auxv(); + let (phdr, phnum) = exe_phdrs(); // Safety: We assume the `AT_PHDR` and `AT_PHNUM` values provided by the // kernel form a valid slice. - unsafe { slice::from_raw_parts(auxv.phdr, auxv.phnum) } + unsafe { slice::from_raw_parts(phdr.cast(), phnum) } } +/// `AT_SYSINFO_EHDR` isn't present on all platforms in all configurations, +/// so if we don't see it, this function returns a null pointer. #[inline] pub(in super::super) fn sysinfo_ehdr() -> *const Elf_Ehdr { - auxv().sysinfo_ehdr -} + let mut ehdr = SYSINFO_EHDR.load(Relaxed); -#[inline] -fn auxv() -> &'static Auxv { - // Safety: `AUXV` is initialized from the `.init_array`, and we never - // mutate it thereafter, so it's effectively initialized read-only in all - // other code. - unsafe { - // Assert that the initialization has happened. On glibc and musl, this - // is handled automatically by `.init_array` functions. Otherwise, - // `rustix::process::init` must be called explicitly. - debug_assert_ne!(AUXV.page_size, 0); - - &AUXV + if ehdr.is_null() { + init_from_proc_self_auxv(); + ehdr = SYSINFO_EHDR.load(Relaxed); } + + ehdr } -/// A struct for holding fields obtained from the kernel-provided auxv array. -struct Auxv { - page_size: usize, - clock_ticks_per_second: usize, - hwcap: usize, - hwcap2: usize, - sysinfo_ehdr: *const Elf_Ehdr, - phdr: *const Elf_Phdr, - phnum: usize, - execfn: *const c::c_char, +static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0); +static CLOCK_TICKS_PER_SECOND: AtomicUsize = AtomicUsize::new(0); +static HWCAP: AtomicUsize = AtomicUsize::new(0); +static HWCAP2: AtomicUsize = AtomicUsize::new(0); +static SYSINFO_EHDR: AtomicPtr = AtomicPtr::new(null_mut()); +static PHDR: AtomicPtr = AtomicPtr::new(null_mut()); +static PHNUM: AtomicUsize = AtomicUsize::new(0); +static EXECFN: AtomicPtr = AtomicPtr::new(null_mut()); + +/// On non-Mustang platforms, we read the aux vector from /proc/self/auxv. +fn init_from_proc_self_auxv() { + // Open "/proc/self/auxv", either because we trust "/proc", or because + // we're running inside QEMU and `proc_self_auxv`'s extra checking foils + // QEMU's emulation so we need to do a plain open to get the right + // auxv records. + let file = crate::fs::openat( + crate::fs::cwd(), + "/proc/self/auxv", + OFlags::empty(), + Mode::empty(), + ) + .unwrap(); + + let _ = init_from_auxv_file(file); } -/// Data obtained from the kernel-provided auxv array. This is initialized at -/// program startup below. -static mut AUXV: Auxv = Auxv { - page_size: 0, - clock_ticks_per_second: 0, - hwcap: 0, - hwcap2: 0, - sysinfo_ehdr: null(), - phdr: null(), - phnum: 0, - execfn: null(), -}; - -/// GLIBC passes argc, argv, and envp to functions in .init_array, as a -/// non-standard extension. Use priority 99 so that we run before any -/// normal user-defined constructor functions. -#[cfg(all(target_env = "gnu", not(target_vendor = "mustang")))] -#[used] -#[link_section = ".init_array.00099"] -static INIT_ARRAY: unsafe extern "C" fn(c::c_int, *mut *mut u8, *mut *mut u8) = { - unsafe extern "C" fn function(_argc: c::c_int, _argv: *mut *mut u8, envp: *mut *mut u8) { - init_from_envp(envp); - } - function -}; - -/// For musl, assume that `__environ` is available and points to the original -/// environment from the kernel, so we can find the auxv array in memory after -/// it. Use priority 99 so that we run before any normal user-defined -/// constructor functions. -/// -/// -#[cfg(all(target_env = "musl", not(target_vendor = "mustang")))] -#[used] -#[link_section = ".init_array.00099"] -static INIT_ARRAY: unsafe extern "C" fn() = { - unsafe extern "C" fn function() { - extern "C" { - static __environ: *mut *mut u8; - } - - init_from_envp(__environ) - } - function -}; - -/// On mustang or any non-musl non-glibic platform where we don't know that we -/// have `.init_array`, we export a function to be called during -/// initialization, and passed a pointer to the original environment variable -/// block set up by the OS. -#[cfg(any( - target_vendor = "mustang", - not(any(target_env = "gnu", target_env = "musl")), -))] -#[inline] -pub(crate) unsafe fn init(envp: *mut *mut u8) { - init_from_envp(envp); -} - -/// # Safety -/// -/// This must be passed a pointer to the environment variable buffer -/// provided by the kernel, which is followed in memory by the auxv array. -unsafe fn init_from_envp(mut envp: *mut *mut u8) { - while !(*envp).is_null() { - envp = envp.add(1); - } - init_from_auxp(envp.add(1).cast()) -} - -/// # Safety -/// -/// This must be passed a pointer to the auxv array provided by the kernel. -unsafe fn init_from_auxp(mut auxp: *const Elf_auxv_t) { +/// Process auxv entries from the open file `auxv`. +fn init_from_auxv_file(auxv: OwnedFd) -> Option<()> { + let mut buffer = Vec::::with_capacity(512); loop { - let Elf_auxv_t { a_type, a_val } = *auxp; + let cur = buffer.len(); + + // Request one extra byte; `Vec` will often allocate more. + buffer.reserve(1); + + // Use all the space it allocated. + buffer.resize(buffer.capacity(), 0); + + // Read up to that many bytes. + let n = match crate::io::read(&auxv, &mut buffer[cur..]) { + Err(crate::io::Errno::INTR) => 0, + Err(_err) => panic!(), + Ok(0) => break, + Ok(n) => n, + }; + + // Account for the number of bytes actually read. + buffer.resize(cur + n, 0_u8); + } + + // Safety: We loaded from an auxv file into the buffer. + unsafe { init_from_auxp(buffer.as_ptr().cast()) } +} + +/// Process auxv entries from the auxv array pointed to by `auxp`. +/// +/// # Safety +/// +/// This must be passed a pointer to an auxv array. +/// +/// The buffer contains `Elf_aux_t` elements, though it need not be aligned; +/// function uses `read_unaligned` to read from it. +unsafe fn init_from_auxp(mut auxp: *const Elf_auxv_t) -> Option<()> { + let mut pagesz = 0; + let mut clktck = 0; + let mut hwcap = 0; + let mut hwcap2 = 0; + let mut phdr = null_mut(); + let mut phnum = 0; + let mut execfn = null_mut(); + let mut sysinfo_ehdr = null_mut(); + let mut phent = 0; + + loop { + let Elf_auxv_t { a_type, a_val } = read_unaligned(auxp); + match a_type as _ { - AT_PAGESZ => AUXV.page_size = a_val as usize, - AT_CLKTCK => AUXV.clock_ticks_per_second = a_val as usize, - AT_HWCAP => AUXV.hwcap = a_val as usize, - AT_HWCAP2 => AUXV.hwcap2 = a_val as usize, - AT_SYSINFO_EHDR => AUXV.sysinfo_ehdr = a_val.cast(), - AT_PHDR => AUXV.phdr = a_val.cast(), - AT_PHNUM => AUXV.phnum = a_val as usize, - AT_PHENT => assert_eq!(a_val as usize, size_of::()), - AT_EXECFN => AUXV.execfn = a_val.cast(), + AT_PAGESZ => pagesz = a_val as usize, + AT_CLKTCK => clktck = a_val as usize, + AT_HWCAP => hwcap = a_val as usize, + AT_HWCAP2 => hwcap2 = a_val as usize, + AT_PHDR => phdr = check_raw_pointer::(a_val as *mut _)?.as_ptr(), + AT_PHNUM => phnum = a_val as usize, + AT_PHENT => phent = a_val as usize, + AT_EXECFN => execfn = check_raw_pointer::(a_val as *mut _)?.as_ptr(), + AT_BASE => check_interpreter_base(a_val.cast())?, + AT_SYSINFO_EHDR => sysinfo_ehdr = check_vdso_base(a_val as *mut _)?.as_ptr(), AT_NULL => break, _ => (), } auxp = auxp.add(1); } + + assert_eq!(phent, size_of::()); + + // The base and sysinfo_ehdr (if present) matches our platform. Accept + // the aux values. + PAGE_SIZE.store(pagesz, Relaxed); + CLOCK_TICKS_PER_SECOND.store(clktck, Relaxed); + HWCAP.store(hwcap, Relaxed); + HWCAP2.store(hwcap2, Relaxed); + PHDR.store(phdr, Relaxed); + PHNUM.store(phnum, Relaxed); + EXECFN.store(execfn, Relaxed); + SYSINFO_EHDR.store(sysinfo_ehdr, Relaxed); + + Some(()) +} + +/// Check that `base` is a valid pointer to the program interpreter. +/// +/// `base` is some value we got from a `AT_BASE` aux record somewhere, +/// which hopefully holds the value of the program interpreter in memory. Do a +/// series of checks to be as sure as we can that it's safe to use. +unsafe fn check_interpreter_base(base: *const Elf_Ehdr) -> Option<()> { + check_elf_base(base)?; + Some(()) +} + +/// Check that `base` is a valid pointer to the kernel-provided vDSO. +/// +/// `base` is some value we got from a `AT_SYSINFO_EHDR` aux record somewhere, +/// which hopefully holds the value of the kernel-provided vDSO in memory. Do a +/// series of checks to be as sure as we can that it's safe to use. +unsafe fn check_vdso_base(base: *const Elf_Ehdr) -> Option> { + // In theory, we could check that we're not attempting to parse our own ELF + // image, as an additional check. However, older Linux toolchains don't + // support this, and Rust's `#[linkage = "extern_weak"]` isn't stable yet, + // so just disable this for now. + /* + { + extern "C" { + static __ehdr_start: c::c_void; + } + + let ehdr_start: *const c::c_void = &__ehdr_start; + if base == ehdr_start { + return None; + } + } + */ + + let hdr = check_elf_base(base)?; + + // Check that the ELF is not writable, since that would indicate that this + // isn't the ELF we think it is. Here we're just using `clock_getres` just + // as an arbitrary system call which writes to a buffer and fails with + // `EFAULT` if the buffer is not writable. + { + use super::super::conv::{c_uint, ret}; + if ret(syscall!( + __NR_clock_getres, + c_uint(linux_raw_sys::general::CLOCK_MONOTONIC), + base + )) != Err(crate::io::Errno::FAULT) + { + // We can't gracefully fail here because we would seem to have just + // mutated some unknown memory. + #[cfg(feature = "std")] + { + std::process::abort(); + } + #[cfg(all(not(feature = "std"), feature = "rustc-dep-of-std"))] + { + core::intrinsics::abort(); + } + } + } + + Some(hdr) +} + +/// Check that `base` is a valid pointer to an ELF image. +unsafe fn check_elf_base(base: *const Elf_Ehdr) -> Option> { + // If we're reading a 64-bit auxv on a 32-bit platform, we'll see + // a zero `a_val` because `AT_*` values are never greater than + // `u32::MAX`. Zero is used by libc's `getauxval` to indicate + // errors, so it should never be a valid value. + if base.is_null() { + return None; + } + + let hdr = match check_raw_pointer::(base as *mut _) { + Some(hdr) => hdr, + None => return None, + }; + + let hdr = hdr.as_ref(); + if hdr.e_ident[..SELFMAG] != ELFMAG { + return None; // Wrong ELF magic + } + if !matches!(hdr.e_ident[EI_OSABI], ELFOSABI_SYSV | ELFOSABI_LINUX) { + return None; // Unrecognized ELF OS ABI + } + if hdr.e_ident[EI_ABIVERSION] != ELFABIVERSION { + return None; // Unrecognized ELF ABI version + } + if hdr.e_type != ET_DYN { + return None; // Wrong ELF type + } + + // If ELF is extended, we'll need to adjust. + if hdr.e_ident[EI_VERSION] != EV_CURRENT + || hdr.e_ehsize as usize != size_of::() + || hdr.e_phentsize as usize != size_of::() + { + return None; + } + // We don't currently support extra-large numbers of segments. + if hdr.e_phnum == PN_XNUM { + return None; + } + + // If `e_phoff` is zero, it's more likely that we're looking at memory that + // has been zeroed than that the kernel has somehow aliased the `Ehdr` and + // the `Phdr`. + if hdr.e_phoff < size_of::() { + return None; + } + + // Verify that the `EI_CLASS`/`EI_DATA`/`e_machine` fields match the + // architecture we're running as. This helps catch cases where we're + // running under QEMU. + if hdr.e_ident[EI_CLASS] != ELFCLASS { + return None; // Wrong ELF class + } + if hdr.e_ident[EI_DATA] != ELFDATA { + return None; // Wrong ELF data + } + if hdr.e_machine != EM_CURRENT { + return None; // Wrong machine type + } + + Some(NonNull::new_unchecked(as_ptr(hdr) as *mut _)) } // ELF ABI diff --git a/src/backend/linux_raw/param/libc_auxv.rs b/src/backend/linux_raw/param/libc_auxv.rs new file mode 100644 index 00000000..1597fd72 --- /dev/null +++ b/src/backend/linux_raw/param/libc_auxv.rs @@ -0,0 +1,74 @@ +//! Linux auxv support, using libc. +//! +//! # Safety +//! +//! This uses raw pointers to locate and read the kernel-provided auxv array. +#![allow(unsafe_code)] + +#[cfg(any(feature = "param", feature = "runtime"))] +use super::super::c; +use super::super::elf::*; +#[cfg(feature = "param")] +use crate::ffi::CStr; +#[cfg(feature = "runtime")] +use core::slice; + +#[cfg(feature = "param")] +#[inline] +pub(crate) fn page_size() -> usize { + unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize } +} + +#[cfg(feature = "param")] +#[inline] +pub(crate) fn clock_ticks_per_second() -> u64 { + unsafe { libc::getauxval(libc::AT_CLKTCK) as u64 } +} + +#[cfg(feature = "param")] +#[inline] +pub(crate) fn linux_hwcap() -> (usize, usize) { + unsafe { + ( + libc::getauxval(libc::AT_HWCAP) as usize, + libc::getauxval(libc::AT_HWCAP2) as usize, + ) + } +} + +#[cfg(feature = "param")] +#[inline] +pub(crate) fn linux_execfn() -> &'static CStr { + unsafe { + let execfn = libc::getauxval(libc::AT_EXECFN) as *const c::c_char; + CStr::from_ptr(execfn.cast()) + } +} + +#[cfg(feature = "runtime")] +#[inline] +pub(crate) fn exe_phdrs() -> (*const c::c_void, usize) { + unsafe { + ( + libc::getauxval(libc::AT_PHDR) as *const c::c_void, + libc::getauxval(libc::AT_PHNUM) as usize, + ) + } +} + +#[cfg(feature = "runtime")] +#[inline] +pub(in super::super) fn exe_phdrs_slice() -> &'static [Elf_Phdr] { + let (phdr, phnum) = exe_phdrs(); + + // Safety: We assume the `AT_PHDR` and `AT_PHNUM` values provided by the + // kernel form a valid slice. + unsafe { slice::from_raw_parts(phdr.cast(), phnum) } +} + +/// `AT_SYSINFO_EHDR` isn't present on all platforms in all configurations, +/// so if we don't see it, this function returns a null pointer. +#[inline] +pub(in super::super) fn sysinfo_ehdr() -> *const Elf_Ehdr { + unsafe { libc::getauxval(linux_raw_sys::general::AT_SYSINFO_EHDR.into()) as *const Elf_Ehdr } +} diff --git a/src/backend/linux_raw/param/mod.rs b/src/backend/linux_raw/param/mod.rs index 2cb2fe78..95628207 100644 --- a/src/backend/linux_raw/param/mod.rs +++ b/src/backend/linux_raw/param/mod.rs @@ -1 +1,12 @@ +// On Mustang, origin is in control of program startup and can access the +// incoming aux values on the stack. +// +// With "use-libc-auxv" enabled, use libc's `getauxval`. +// +// Otherwise, we read aux values from /proc/self/auxv. +#[cfg_attr(target_vendor = "mustang", path = "mustang_auxv.rs")] +#[cfg_attr( + all(not(target_vendor = "mustang"), feature = "use-libc-auxv"), + path = "libc_auxv.rs" +)] pub(crate) mod auxv; diff --git a/src/backend/linux_raw/param/mustang_auxv.rs b/src/backend/linux_raw/param/mustang_auxv.rs new file mode 100644 index 00000000..e9b89b50 --- /dev/null +++ b/src/backend/linux_raw/param/mustang_auxv.rs @@ -0,0 +1,159 @@ +//! Linux auxv support, for Mustang. +//! +//! # Safety +//! +//! This uses raw pointers to locate and read the kernel-provided auxv array. +#![allow(unsafe_code)] + +use super::super::c; +use super::super::elf::*; +#[cfg(feature = "param")] +use crate::ffi::CStr; +use core::ffi::c_void; +use core::mem::size_of; +use core::ptr::{null, read}; +#[cfg(feature = "runtime")] +use core::slice; +use linux_raw_sys::general::{ + AT_CLKTCK, AT_EXECFN, AT_HWCAP, AT_HWCAP2, AT_NULL, AT_PAGESZ, AT_PHDR, AT_PHENT, AT_PHNUM, + AT_SYSINFO_EHDR, +}; + +#[cfg(feature = "param")] +#[inline] +pub(crate) fn page_size() -> usize { + // Safety: This is initialized during program startup. + unsafe { PAGE_SIZE } +} + +#[cfg(feature = "param")] +#[inline] +pub(crate) fn clock_ticks_per_second() -> u64 { + // Safety: This is initialized during program startup. + unsafe { CLOCK_TICKS_PER_SECOND as u64 } +} + +#[cfg(feature = "param")] +#[inline] +pub(crate) fn linux_hwcap() -> (usize, usize) { + // Safety: This is initialized during program startup. + unsafe { (HWCAP, HWCAP2) } +} + +#[cfg(feature = "param")] +#[inline] +pub(crate) fn linux_execfn() -> &'static CStr { + // Safety: This is initialized during program startup. And we + // assume it's a valid pointer to a NUL-terminated string. + unsafe { CStr::from_ptr(EXECFN.0.cast()) } +} + +#[cfg(feature = "runtime")] +#[inline] +pub(crate) fn exe_phdrs() -> (*const c_void, usize) { + // Safety: This is initialized during program startup. + unsafe { (PHDR.0.cast(), PHNUM) } +} + +#[cfg(feature = "runtime")] +#[inline] +pub(in super::super) fn exe_phdrs_slice() -> &'static [Elf_Phdr] { + let (phdr, phnum) = exe_phdrs(); + + // Safety: We assume the `AT_PHDR` and `AT_PHNUM` values provided by the + // kernel form a valid slice. + unsafe { slice::from_raw_parts(phdr.cast(), phnum) } +} + +/// `AT_SYSINFO_EHDR` isn't present on all platforms in all configurations, +/// so if we don't see it, this function returns a null pointer. +#[inline] +pub(in super::super) fn sysinfo_ehdr() -> *const Elf_Ehdr { + // Safety: This is initialized during program startup. + unsafe { SYSINFO_EHDR.0 } +} + +/// A const pointer to `T` that implements [`Sync`]. +#[repr(transparent)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct SyncConstPtr(*const T); +unsafe impl Sync for SyncConstPtr {} + +impl SyncConstPtr { + /// Creates a `SyncConstPointer` from a raw pointer. + /// + /// Behavior is undefined if `ptr` is actually not + /// safe to share across threads. + pub const unsafe fn new(ptr: *const T) -> Self { + Self(ptr) + } +} + +static mut PAGE_SIZE: usize = 0; +static mut CLOCK_TICKS_PER_SECOND: usize = 0; +static mut HWCAP: usize = 0; +static mut HWCAP2: usize = 0; +static mut SYSINFO_EHDR: SyncConstPtr = unsafe { SyncConstPtr::new(null()) }; +static mut PHDR: SyncConstPtr = unsafe { SyncConstPtr::new(null()) }; +static mut PHNUM: usize = 0; +static mut EXECFN: SyncConstPtr = unsafe { SyncConstPtr::new(null()) }; + +/// On mustang, we export a function to be called during initialization, and +/// passed a pointer to the original environment variable block set up by the +/// OS. +pub(crate) unsafe fn init(envp: *mut *mut u8) { + init_from_envp(envp); +} + +/// # Safety +/// +/// This must be passed a pointer to the environment variable buffer +/// provided by the kernel, which is followed in memory by the auxv array. +unsafe fn init_from_envp(mut envp: *mut *mut u8) { + while !(*envp).is_null() { + envp = envp.add(1); + } + init_from_auxp(envp.add(1).cast()) +} + +/// Process auxv entries from the auxv array pointed to by `auxp`. +/// +/// # Safety +/// +/// This must be passed a pointer to an auxv array. +/// +/// The buffer contains `Elf_aux_t` elements, though it need not be aligned; +/// function uses `read_unaligned` to read from it. +unsafe fn init_from_auxp(mut auxp: *const Elf_auxv_t) { + loop { + let Elf_auxv_t { a_type, a_val } = read(auxp); + + match a_type as _ { + AT_PAGESZ => PAGE_SIZE = a_val as usize, + AT_CLKTCK => CLOCK_TICKS_PER_SECOND = a_val as usize, + AT_HWCAP => HWCAP = a_val as usize, + AT_HWCAP2 => HWCAP2 = a_val as usize, + AT_PHDR => PHDR = SyncConstPtr::new(a_val.cast::()), + AT_PHNUM => PHNUM = a_val as usize, + AT_PHENT => assert_eq!(a_val as usize, size_of::()), + AT_EXECFN => EXECFN = SyncConstPtr::new(a_val.cast::()), + AT_SYSINFO_EHDR => SYSINFO_EHDR = SyncConstPtr::new(a_val.cast::()), + AT_NULL => break, + _ => (), + } + auxp = auxp.add(1); + } +} + +// ELF ABI + +#[repr(C)] +#[derive(Copy, Clone)] +struct Elf_auxv_t { + a_type: usize, + + // Some of the values in the auxv array are pointers, so we make `a_val` a + // pointer, in order to preserve their provenance. For the values which are + // integers, we cast this to `usize`. + a_val: *const c_void, +} diff --git a/src/backend/linux_raw/vdso.rs b/src/backend/linux_raw/vdso.rs index 3c85139e..da7910b8 100644 --- a/src/backend/linux_raw/vdso.rs +++ b/src/backend/linux_raw/vdso.rs @@ -15,9 +15,9 @@ use super::c; use super::elf::*; use crate::ffi::CStr; -use crate::io; +use crate::utils::check_raw_pointer; use core::ffi::c_void; -use core::mem::{align_of, size_of}; +use core::mem::size_of; use core::ptr::{null, null_mut}; pub(super) struct Vdso { @@ -53,232 +53,142 @@ fn elf_hash(name: &CStr) -> u32 { h } -/// Cast `value` to a pointer type, doing some checks for validity. -fn make_pointer(value: *const c_void) -> Option<*const T> { - if value.is_null() - || (value as usize).checked_add(size_of::()).is_none() - || (value as usize) % align_of::() != 0 - { - return None; - } +/// Create a `Vdso` value by parsing the vDSO at the `sysinfo_ehdr` address. +fn init_from_sysinfo_ehdr() -> Option { + // Safety: the auxv initialization code does extensive checks to ensure + // that the value we get really is an `AT_SYSINFO_EHDR` value from the + // kernel. + unsafe { + let hdr = super::param::auxv::sysinfo_ehdr(); - Some(value.cast()) -} - -/// Create a `Vdso` value by parsing the vDSO at the given address. -/// -/// # Safety -/// -/// `base` must be a valid pointer to an ELF image in memory. -unsafe fn init_from_sysinfo_ehdr(base: *const Elf_Ehdr) -> Option { - // Check that `base` is a valid pointer to the kernel-provided vDSO. - let hdr = check_vdso_base(base)?; - - let mut vdso = Vdso { - load_addr: base, - load_end: base.cast(), - pv_offset: 0, - symtab: null(), - symstrings: null(), - bucket: null(), - chain: null(), - nbucket: 0, - //nchain: 0, - versym: null(), - verdef: null(), - }; - - let pt = make_pointer::(vdso.base_plus(hdr.e_phoff)?)?; - let mut dyn_: *const Elf_Dyn = null(); - let mut num_dyn = 0; - - // We need two things from the segment table: the load offset - // and the dynamic table. - let mut found_vaddr = false; - for i in 0..hdr.e_phnum { - let phdr = &*pt.add(i as usize); - if phdr.p_flags & PF_W != 0 { - // Don't trust any vDSO that claims to be loading writable - // segments into memory. + // If the platform doesn't provide a `AT_SYSINFO_EHDR`, we can't locate + // the vDSO. + if hdr.is_null() { return None; } - if phdr.p_type == PT_LOAD && !found_vaddr { - // The segment should be readable and executable, because it - // contains the symbol table and the function bodies. - if phdr.p_flags & (PF_R | PF_X) != (PF_R | PF_X) { + + let mut vdso = Vdso { + load_addr: hdr, + load_end: hdr.cast(), + pv_offset: 0, + symtab: null(), + symstrings: null(), + bucket: null(), + chain: null(), + nbucket: 0, + //nchain: 0, + versym: null(), + verdef: null(), + }; + + let hdr = &*hdr; + let pt = check_raw_pointer::(vdso.base_plus(hdr.e_phoff)? as *mut _)?.as_ptr(); + let mut dyn_: *const Elf_Dyn = null(); + let mut num_dyn = 0; + + // We need two things from the segment table: the load offset + // and the dynamic table. + let mut found_vaddr = false; + for i in 0..hdr.e_phnum { + let phdr = &*pt.add(i as usize); + if phdr.p_flags & PF_W != 0 { + // Don't trust any vDSO that claims to be loading writable + // segments into memory. return None; } - found_vaddr = true; - vdso.load_end = vdso.base_plus(phdr.p_offset.checked_add(phdr.p_memsz)?)?; - vdso.pv_offset = phdr.p_offset.wrapping_sub(phdr.p_vaddr); - } else if phdr.p_type == PT_DYNAMIC { - // If `p_offset` is zero, it's more likely that we're looking at memory - // that has been zeroed than that the kernel has somehow aliased the - // `Ehdr` and the `Elf_Dyn` array. - if phdr.p_offset < size_of::() { - return None; - } - - dyn_ = make_pointer::(vdso.base_plus(phdr.p_offset)?)?; - num_dyn = phdr.p_memsz / size_of::(); - } else if phdr.p_type == PT_INTERP || phdr.p_type == PT_GNU_RELRO { - // Don't trust any ELF image that has an "interpreter" or that uses - // RELRO, which is likely to be a user ELF image rather and not the - // kernel vDSO. - return None; - } - } - - if !found_vaddr || dyn_.is_null() { - return None; // Failed - } - - // Fish out the useful bits of the dynamic table. - let mut hash: *const u32 = null(); - vdso.symstrings = null(); - vdso.symtab = null(); - vdso.versym = null(); - vdso.verdef = null(); - let mut i = 0; - loop { - if i == num_dyn { - return None; - } - let d = &*dyn_.add(i); - match d.d_tag { - DT_STRTAB => { - vdso.symstrings = make_pointer::(vdso.addr_from_elf(d.d_val)?)?; - } - DT_SYMTAB => { - vdso.symtab = make_pointer::(vdso.addr_from_elf(d.d_val)?)?; - } - DT_HASH => { - hash = make_pointer::(vdso.addr_from_elf(d.d_val)?)?; - } - DT_VERSYM => { - vdso.versym = make_pointer::(vdso.addr_from_elf(d.d_val)?)?; - } - DT_VERDEF => { - vdso.verdef = make_pointer::(vdso.addr_from_elf(d.d_val)?)?; - } - DT_SYMENT => { - if d.d_val != size_of::() { - return None; // Failed + if phdr.p_type == PT_LOAD && !found_vaddr { + // The segment should be readable and executable, because it + // contains the symbol table and the function bodies. + if phdr.p_flags & (PF_R | PF_X) != (PF_R | PF_X) { + return None; + } + found_vaddr = true; + vdso.load_end = vdso.base_plus(phdr.p_offset.checked_add(phdr.p_memsz)?)?; + vdso.pv_offset = phdr.p_offset.wrapping_sub(phdr.p_vaddr); + } else if phdr.p_type == PT_DYNAMIC { + // If `p_offset` is zero, it's more likely that we're looking at memory + // that has been zeroed than that the kernel has somehow aliased the + // `Ehdr` and the `Elf_Dyn` array. + if phdr.p_offset < size_of::() { + return None; } - } - DT_NULL => break, - _ => {} - } - i = i.checked_add(1)?; - } - if vdso.symstrings.is_null() || vdso.symtab.is_null() || hash.is_null() { - return None; // Failed - } - if vdso.verdef.is_null() { + dyn_ = check_raw_pointer::(vdso.base_plus(phdr.p_offset)? as *mut _)? + .as_ptr(); + num_dyn = phdr.p_memsz / size_of::(); + } else if phdr.p_type == PT_INTERP || phdr.p_type == PT_GNU_RELRO { + // Don't trust any ELF image that has an "interpreter" or that uses + // RELRO, which is likely to be a user ELF image rather and not the + // kernel vDSO. + return None; + } + } + + if !found_vaddr || dyn_.is_null() { + return None; // Failed + } + + // Fish out the useful bits of the dynamic table. + let mut hash: *const u32 = null(); + vdso.symstrings = null(); + vdso.symtab = null(); vdso.versym = null(); - } - - // Parse the hash table header. - vdso.nbucket = *hash.add(0); - //vdso.nchain = *hash.add(1); - vdso.bucket = hash.add(2); - vdso.chain = hash.add(vdso.nbucket as usize + 2); - - // That's all we need. - Some(vdso) -} - -/// Check that `base` is a valid pointer to the kernel-provided vDSO. -/// -/// `base` is some value we got from a `AT_SYSINFO_EHDR` aux record somewhere, -/// which hopefully holds the value of the kernel-provided vDSO in memory. Do a -/// series of checks to be as sure as we can that it's safe to use. -unsafe fn check_vdso_base<'vdso>(base: *const Elf_Ehdr) -> Option<&'vdso Elf_Ehdr> { - // In theory, we could check that we're not attempting to parse our own ELF - // image, as an additional check. However, older Linux toolchains don't - // support this, and Rust's `#[linkage = "extern_weak"]` isn't stable yet, - // so just disable this for now. - /* - { - extern "C" { - static __ehdr_start: c::c_void; - } - - let ehdr_start: *const c::c_void = &__ehdr_start; - if base == ehdr_start { - return None; - } - } - */ - - let hdr = &*make_pointer::(base.cast())?; - - if hdr.e_ident[..SELFMAG] != ELFMAG { - return None; // Wrong ELF magic - } - if hdr.e_ident[EI_CLASS] != ELFCLASS { - return None; // Wrong ELF class - } - if hdr.e_ident[EI_DATA] != ELFDATA { - return None; // Wrong ELF data - } - if !matches!(hdr.e_ident[EI_OSABI], ELFOSABI_SYSV | ELFOSABI_LINUX) { - return None; // Unrecognized ELF OS ABI - } - if hdr.e_ident[EI_ABIVERSION] != ELFABIVERSION { - return None; // Unrecognized ELF ABI version - } - if hdr.e_type != ET_DYN { - return None; // Wrong ELF type - } - // Verify that the `e_machine` matches the architecture we're running as. - // This helps catch cases where we're running under qemu. - if hdr.e_machine != EM_CURRENT { - return None; // Wrong machine type - } - - // If ELF is extended, we'll need to adjust. - if hdr.e_ident[EI_VERSION] != EV_CURRENT - || hdr.e_ehsize as usize != size_of::() - || hdr.e_phentsize as usize != size_of::() - { - return None; - } - // We don't currently support extra-large numbers of segments. - if hdr.e_phnum == PN_XNUM { - return None; - } - - // If `e_phoff` is zero, it's more likely that we're looking at memory that - // has been zeroed than that the kernel has somehow aliased the `Ehdr` and - // the `Phdr`. - if hdr.e_phoff < size_of::() { - return None; - } - - // Check that the vDSO is not writable, since that would indicate that this - // isn't the kernel vDSO. Here we're just using `clock_getres` just as an - // arbitrary system call which writes to a buffer and fails with `EFAULT` - // if the buffer is not writable. - { - use super::conv::ret; - use super::time::types::ClockId; - if ret(syscall!(__NR_clock_getres, ClockId::Monotonic, base)) != Err(io::Errno::FAULT) { - // We can't gracefully fail here because we would seem to have just - // mutated some unknown memory. - #[cfg(feature = "std")] - { - std::process::abort(); + vdso.verdef = null(); + let mut i = 0; + loop { + if i == num_dyn { + return None; } - #[cfg(all(not(feature = "std"), feature = "rustc-dep-of-std"))] - { - core::intrinsics::abort(); + let d = &*dyn_.add(i); + match d.d_tag { + DT_STRTAB => { + vdso.symstrings = + check_raw_pointer::(vdso.addr_from_elf(d.d_val)? as *mut _)?.as_ptr(); + } + DT_SYMTAB => { + vdso.symtab = + check_raw_pointer::(vdso.addr_from_elf(d.d_val)? as *mut _)? + .as_ptr(); + } + DT_HASH => { + hash = + check_raw_pointer::(vdso.addr_from_elf(d.d_val)? as *mut _)?.as_ptr(); + } + DT_VERSYM => { + vdso.versym = + check_raw_pointer::(vdso.addr_from_elf(d.d_val)? as *mut _)?.as_ptr(); + } + DT_VERDEF => { + vdso.verdef = + check_raw_pointer::(vdso.addr_from_elf(d.d_val)? as *mut _)? + .as_ptr(); + } + DT_SYMENT => { + if d.d_val != size_of::() { + return None; // Failed + } + } + DT_NULL => break, + _ => {} } + i = i.checked_add(1)?; } - } + // The upstream code checks `symstrings`, `symtab`, and `hash` for null; + // here, `check_raw_pointer` has already done that. - Some(hdr) + if vdso.verdef.is_null() { + vdso.versym = null(); + } + + // Parse the hash table header. + vdso.nbucket = *hash.add(0); + //vdso.nchain = *hash.add(1); + vdso.bucket = hash.add(2); + vdso.chain = hash.add(vdso.nbucket as usize + 2); + + // That's all we need. + Some(vdso) + } } impl Vdso { @@ -288,7 +198,7 @@ impl Vdso { /// to our expectations. #[inline] pub(super) fn new() -> Option { - init_from_auxv() + init_from_sysinfo_ehdr() } /// Check the version for a symbol. @@ -398,31 +308,3 @@ impl Vdso { self.base_plus(elf_addr.wrapping_add(self.pv_offset)) } } - -// Find the `AT_SYSINFO_EHDR` in auxv records in memory. We have our own code -// for reading the auxv records in memory, so we don't currently use this. -// -// # Safety -// -// `elf_auxv` must point to a valid array of AUXV records terminated by an -// `AT_NULL` record. -/* -unsafe fn init_from_auxv(elf_auxv: *const Elf_auxv_t) -> Option { - let mut i = 0; - while (*elf_auxv.add(i)).a_type != AT_NULL { - if (*elf_auxv.add(i)).a_type == AT_SYSINFO_EHDR { - return init_from_sysinfo_ehdr((*elf_auxv.add(i)).a_val); - } - i += 1; - } - - None -} -*/ - -/// Find the vDSO image by following the `AT_SYSINFO_EHDR` auxv record pointer. -fn init_from_auxv() -> Option { - // Safety: `sysinfo_ehdr` does extensive checks to ensure that the value - // we get really is an `AT_SYSINFO_EHDR` value from the kernel. - unsafe { init_from_sysinfo_ehdr(super::param::auxv::sysinfo_ehdr()) } -} diff --git a/src/fs/mod.rs b/src/fs/mod.rs index 99c7158f..9faded18 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -3,7 +3,14 @@ #[cfg(feature = "fs")] mod abs; #[cfg(not(target_os = "redox"))] -#[cfg(any(feature = "fs", feature = "procfs"))] +#[cfg(any( + feature = "fs", + feature = "param", + feature = "procfs", + feature = "runtime", + feature = "time", + target_arch = "x86" +))] mod at; mod constants; #[cfg(any(target_os = "android", target_os = "linux"))] @@ -86,7 +93,14 @@ pub use at::renameat_with; #[cfg(feature = "fs")] pub use at::{chmodat, chownat}; #[cfg(not(target_os = "redox"))] -#[cfg(any(feature = "fs", feature = "procfs"))] +#[cfg(any( + feature = "fs", + feature = "param", + feature = "procfs", + feature = "runtime", + feature = "time", + target_arch = "x86" +))] pub use at::{ linkat, mkdirat, openat, readlinkat, renameat, statat, symlinkat, unlinkat, utimensat, RawMode, UTIME_NOW, UTIME_OMIT, diff --git a/src/param/mod.rs b/src/param/mod.rs index 5169d0a5..c47aca98 100644 --- a/src/param/mod.rs +++ b/src/param/mod.rs @@ -7,10 +7,7 @@ #[cfg(feature = "param")] mod auxv; -#[cfg(any( - target_vendor = "mustang", - not(any(target_env = "gnu", target_env = "musl")), -))] +#[cfg(target_vendor = "mustang")] mod init; #[cfg(feature = "param")] @@ -30,8 +27,5 @@ pub use auxv::page_size; ) ))] pub use auxv::{linux_execfn, linux_hwcap}; -#[cfg(any( - target_vendor = "mustang", - not(any(target_env = "gnu", target_env = "musl")), -))] +#[cfg(target_vendor = "mustang")] pub use init::init; diff --git a/src/utils.rs b/src/utils.rs index efbbe81a..dcbadb26 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -11,3 +11,19 @@ pub(crate) const fn as_ptr(t: &T) -> *const T { pub(crate) fn as_mut_ptr(t: &mut T) -> *mut T { t } + +/// Convert a `*mut c_void` to a `*mut T`, checking that it is not null, +/// misaligned, or pointing to a region of memory that wraps around the address +/// space. +#[allow(dead_code)] +pub(crate) fn check_raw_pointer(value: *mut core::ffi::c_void) -> Option> { + if (value as usize) + .checked_add(core::mem::size_of::()) + .is_none() + || (value as usize) % core::mem::align_of::() != 0 + { + return None; + } + + core::ptr::NonNull::new(value.cast()) +} diff --git a/test-crates/use-rustix-auxv/Cargo.toml b/test-crates/use-rustix-auxv/Cargo.toml new file mode 100644 index 00000000..8122570b --- /dev/null +++ b/test-crates/use-rustix-auxv/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "use-rustix-auxv" +version = "0.0.0" +edition = "2018" +publish = false + +[dependencies] +# Request that rustix use the default backend, and disable use-libc-auxv. +rustix = { path = "../..", default-features = false, features = ["std"] } diff --git a/test-crates/use-rustix-auxv/src/lib.rs b/test-crates/use-rustix-auxv/src/lib.rs new file mode 100644 index 00000000..e69de29b diff --git a/tests/backends.rs b/tests/backends.rs index 4b88bd11..ff5b681a 100644 --- a/tests/backends.rs +++ b/tests/backends.rs @@ -6,10 +6,6 @@ fn test_backends() { // ensure that the use-default crate uses it. #[cfg(all(target_os = "linux", target_arch = "aarch64"))] { - assert!( - !has_dependency("test-crates/use-default", &[], &[], &["RUSTFLAGS"], "libc"), - "use-default depends on libc" - ); assert!( has_dependency( "test-crates/use-default", @@ -22,6 +18,32 @@ fn test_backends() { ); } + // Pick an arbitrary platform where linux_raw is enabled by default and + // ensure that the use-rustix-auxv crate uses it, and does not use libc. + #[cfg(all(target_os = "linux", target_arch = "aarch64"))] + { + assert!( + !has_dependency( + "test-crates/use-rustix-auxv", + &[], + &[], + &["RUSTFLAGS"], + "libc" + ), + "use-rustix-auxv depends on libc" + ); + assert!( + has_dependency( + "test-crates/use-rustix-auxv", + &[], + &[], + &["RUSTFLAGS"], + "linux-raw-sys" + ), + "use-rustix-auxv does not depend on linux-raw-sys" + ); + } + #[cfg(windows)] let libc_dep = "windows-sys"; #[cfg(unix)]