diff --git a/Cargo.toml b/Cargo.toml index 99766b89..d9735cb8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,6 +76,7 @@ io-lifetimes = { version = "1.0.0-rc1", default-features = false } # `parking_lot_core` version which is not compatible with our MSRV of 1.48. serial_test = "0.6" memoffset = "0.6.5" +flate2 = "1.0" [target.'cfg(not(target_os = "emscripten"))'.dev-dependencies] criterion = "0.3" diff --git a/src/backend/libc/process/syscalls.rs b/src/backend/libc/process/syscalls.rs index c15ce343..3f6598bd 100644 --- a/src/backend/libc/process/syscalls.rs +++ b/src/backend/libc/process/syscalls.rs @@ -441,3 +441,15 @@ pub(crate) fn kill_process_group(pid: Pid, sig: Signal) -> io::Result<()> { pub(crate) fn kill_current_process_group(sig: Signal) -> io::Result<()> { unsafe { ret(c::kill(0, sig as i32)) } } + +#[cfg(any(target_os = "android", target_os = "linux"))] +#[inline] +pub(crate) unsafe fn prctl( + option: c::c_int, + arg2: *mut c::c_void, + arg3: *mut c::c_void, + arg4: *mut c::c_void, + arg5: *mut c::c_void, +) -> io::Result { + ret_c_int(c::prctl(option, arg2, arg3, arg4, arg5)) +} diff --git a/src/backend/linux_raw/process/syscalls.rs b/src/backend/linux_raw/process/syscalls.rs index 6ac9f171..ac62e694 100644 --- a/src/backend/linux_raw/process/syscalls.rs +++ b/src/backend/linux_raw/process/syscalls.rs @@ -547,3 +547,14 @@ pub(crate) fn kill_process_group(pid: Pid, sig: Signal) -> io::Result<()> { pub(crate) fn kill_current_process_group(sig: Signal) -> io::Result<()> { unsafe { ret(syscall_readonly!(__NR_kill, pass_usize(0), sig)) } } + +#[inline] +pub(crate) unsafe fn prctl( + option: c::c_int, + arg2: *mut c::c_void, + arg3: *mut c::c_void, + arg4: *mut c::c_void, + arg5: *mut c::c_void, +) -> io::Result { + ret_c_int(syscall!(__NR_prctl, c_int(option), arg2, arg3, arg4, arg5)) +} diff --git a/src/process/mod.rs b/src/process/mod.rs index 2c167176..8b517961 100644 --- a/src/process/mod.rs +++ b/src/process/mod.rs @@ -9,6 +9,8 @@ mod id; mod kill; #[cfg(any(target_os = "android", target_os = "linux"))] mod membarrier; +#[cfg(any(target_os = "android", target_os = "linux"))] +mod prctl; #[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))] // WASI doesn't have [gs]etpriority. mod priority; #[cfg(not(any(target_os = "fuchsia", target_os = "redox", target_os = "wasi")))] @@ -48,6 +50,8 @@ pub use kill::{kill_current_process_group, kill_process, kill_process_group, Sig pub use membarrier::{ membarrier, membarrier_cpu, membarrier_query, MembarrierCommand, MembarrierQuery, }; +#[cfg(any(target_os = "android", target_os = "linux"))] +pub use prctl::*; #[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))] pub use priority::nice; #[cfg(not(any(target_os = "fuchsia", target_os = "redox", target_os = "wasi")))] diff --git a/src/process/prctl.rs b/src/process/prctl.rs new file mode 100644 index 00000000..5269014c --- /dev/null +++ b/src/process/prctl.rs @@ -0,0 +1,1107 @@ +#![allow(unsafe_code)] + +use core::convert::{TryFrom, TryInto}; +use core::mem::MaybeUninit; +use core::ptr::NonNull; +use core::{mem, ptr}; + +use bitflags::bitflags; + +use crate::backend::c::{c_int, c_uint, c_void}; +use crate::backend::process::syscalls; +use crate::backend::process::types::Signal; +use crate::fd::{AsRawFd, BorrowedFd}; +use crate::ffi::CStr; +use crate::io; +use crate::process::{Pid, RawPid}; + +// +// Helper functions. +// + +#[inline] +pub(crate) unsafe fn prctl_1arg(option: c_int) -> io::Result { + const NULL: *mut c_void = ptr::null_mut(); + syscalls::prctl(option, NULL, NULL, NULL, NULL) +} + +#[inline] +pub(crate) unsafe fn prctl_2args(option: c_int, arg2: *mut c_void) -> io::Result { + const NULL: *mut c_void = ptr::null_mut(); + syscalls::prctl(option, arg2, NULL, NULL, NULL) +} + +#[inline] +pub(crate) unsafe fn prctl_3args( + option: c_int, + arg2: *mut c_void, + arg3: *mut c_void, +) -> io::Result { + syscalls::prctl(option, arg2, arg3, ptr::null_mut(), ptr::null_mut()) +} + +#[inline] +pub(crate) unsafe fn prctl_get_at_arg2_optional

(option: i32) -> io::Result

{ + let mut value: MaybeUninit

= MaybeUninit::uninit(); + prctl_2args(option, value.as_mut_ptr().cast())?; + Ok(value.assume_init()) +} + +#[inline] +pub(crate) unsafe fn prctl_get_at_arg2(option: i32) -> io::Result +where + P: Default, + T: TryFrom, +{ + let mut value: P = Default::default(); + prctl_2args(option, ((&mut value) as *mut P).cast())?; + TryFrom::try_from(value) +} + +// +// PR_GET_PDEATHSIG/PR_SET_PDEATHSIG +// + +const PR_GET_PDEATHSIG: c_int = 2; + +/// Get the current value of the parent process death signal. +/// +/// # References +/// - [`prctl(PR_GET_PDEATHSIG,...)`] +/// +/// [`prctl(PR_GET_PDEATHSIG,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn parent_process_death_signal() -> io::Result> { + unsafe { prctl_get_at_arg2_optional::(PR_GET_PDEATHSIG) }.map(Signal::from_raw) +} + +const PR_SET_PDEATHSIG: c_int = 1; + +/// Set the parent-death signal of the calling process. +/// +/// # References +/// - [`prctl(PR_SET_PDEATHSIG,...)`] +/// +/// [`prctl(PR_SET_PDEATHSIG,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn set_parent_process_death_signal(signal: Option) -> io::Result<()> { + let signal = signal.map_or(0_usize, |signal| signal as usize); + unsafe { prctl_2args(PR_SET_PDEATHSIG, signal as *mut _) }.map(|_r| ()) +} + +// +// PR_GET_DUMPABLE/PR_SET_DUMPABLE +// + +const PR_GET_DUMPABLE: c_int = 3; + +const SUID_DUMP_DISABLE: i32 = 0; +const SUID_DUMP_USER: i32 = 1; +const SUID_DUMP_ROOT: i32 = 2; + +/// `SUID_DUMP_*`. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(i32)] +pub enum DumpableBehavior { + /// Not dumpable. + NotDumpable = SUID_DUMP_DISABLE, + /// Dumpable. + Dumpable = SUID_DUMP_USER, + /// Dumpable but only readable by root. + DumpableReadableOnlyByRoot = SUID_DUMP_ROOT, +} + +impl TryFrom for DumpableBehavior { + type Error = io::Errno; + + fn try_from(value: i32) -> Result { + match value { + SUID_DUMP_DISABLE => Ok(Self::NotDumpable), + SUID_DUMP_USER => Ok(Self::Dumpable), + SUID_DUMP_ROOT => Ok(Self::DumpableReadableOnlyByRoot), + _ => Err(io::Errno::RANGE), + } + } +} + +/// Get the current state of the calling process's `dumpable` attribute. +/// +/// # References +/// - [`prctl(PR_GET_DUMPABLE,...)`] +/// +/// [`prctl(PR_GET_DUMPABLE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn dumpable_behavior() -> io::Result { + unsafe { prctl_1arg(PR_GET_DUMPABLE) }.and_then(TryInto::try_into) +} + +const PR_SET_DUMPABLE: c_int = 4; + +/// Set the state of the `dumpable` attribute, which determines whether core dumps are produced +/// for the calling process upon delivery of a signal whose default behavior is to produce +/// a core dump. +/// +/// # References +/// - [`prctl(PR_SET_DUMPABLE,...)`] +/// +/// [`prctl(PR_SET_DUMPABLE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn set_dumpable_behavior(config: DumpableBehavior) -> io::Result<()> { + unsafe { prctl_2args(PR_SET_DUMPABLE, config as usize as *mut _) }.map(|_r| ()) +} + +// +// PR_GET_UNALIGN/PR_SET_UNALIGN +// + +const PR_GET_UNALIGN: c_int = 5; + +bitflags! { + /// `PR_UNALIGN_*`. + pub struct UnalignedAccessControl: u32 { + /// Silently fix up unaligned user accesses. + const NO_PRINT = 1; + /// Generate `SIGBUS` on unaligned user access. + const SIGBUS = 2; + } +} + +/// Get unaligned access control bits. +/// +/// # References +/// - [`prctl(PR_GET_UNALIGN,...)`] +/// +/// [`prctl(PR_GET_UNALIGN,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn unaligned_access_control() -> io::Result { + let r = unsafe { prctl_get_at_arg2_optional::(PR_GET_UNALIGN)? }; + UnalignedAccessControl::from_bits(r).ok_or(io::Errno::RANGE) +} + +const PR_SET_UNALIGN: c_int = 6; + +/// Set unaligned access control bits. +/// +/// # References +/// - [`prctl(PR_SET_UNALIGN,...)`] +/// +/// [`prctl(PR_SET_UNALIGN,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn set_unaligned_access_control(config: UnalignedAccessControl) -> io::Result<()> { + unsafe { prctl_2args(PR_SET_UNALIGN, config.bits() as usize as *mut _) }.map(|_r| ()) +} + +// +// PR_GET_FPEMU/PR_SET_FPEMU +// + +const PR_GET_FPEMU: c_int = 9; + +bitflags! { + /// `PR_FPEMU_*`. + pub struct FloatingPointEmulationControl: u32 { + /// Silently emulate floating point operations accesses. + const NO_PRINT = 1; + /// Don't emulate floating point operations, send `SIGFPE` instead. + const SIGFPE = 2; + } +} + +/// Get floating point emulation control bits. +/// +/// # References +/// - [`prctl(PR_GET_FPEMU,...)`] +/// +/// [`prctl(PR_GET_FPEMU,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn floating_point_emulation_control() -> io::Result { + let r = unsafe { prctl_get_at_arg2_optional::(PR_GET_FPEMU)? }; + FloatingPointEmulationControl::from_bits(r).ok_or(io::Errno::RANGE) +} + +const PR_SET_FPEMU: c_int = 10; + +/// Set floating point emulation control bits. +/// +/// # References +/// - [`prctl(PR_SET_FPEMU,...)`] +/// +/// [`prctl(PR_SET_FPEMU,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn set_floating_point_emulation_control( + config: FloatingPointEmulationControl, +) -> io::Result<()> { + unsafe { prctl_2args(PR_SET_FPEMU, config.bits() as usize as *mut _) }.map(|_r| ()) +} + +// +// PR_GET_FPEXC/PR_SET_FPEXC +// + +const PR_GET_FPEXC: c_int = 11; + +bitflags! { + /// Zero means floating point exceptions are disabled. + pub struct FloatingPointExceptionMode: u32 { + /// Async non-recoverable exception mode. + const NONRECOV = 1; + /// Async recoverable exception mode. + const ASYNC = 2; + /// Precise exception mode. + const PRECISE = 3; + + /// Use FPEXC for floating point exception enables. + const SW_ENABLE = 0x80; + /// Floating point divide by zero. + const DIV = 0x01_0000; + /// Floating point overflow. + const OVF = 0x02_0000; + /// Floating point underflow. + const UND = 0x04_0000; + /// Floating point inexact result. + const RES = 0x08_0000; + /// Floating point invalid operation. + const INV = 0x10_0000; + } +} + +/// Get floating point exception mode. +/// +/// # References +/// - [`prctl(PR_GET_FPEXC,...)`] +/// +/// [`prctl(PR_GET_FPEXC,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn floating_point_exception_mode() -> io::Result> { + unsafe { prctl_get_at_arg2_optional::(PR_GET_FPEXC) } + .map(FloatingPointExceptionMode::from_bits) +} + +const PR_SET_FPEXC: c_int = 12; + +/// Set floating point exception mode. +/// +/// # References +/// - [`prctl(PR_SET_FPEXC,...)`] +/// +/// [`prctl(PR_SET_FPEXC,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn set_floating_point_exception_mode( + config: Option, +) -> io::Result<()> { + let config = config.as_ref().map_or(0, FloatingPointExceptionMode::bits); + unsafe { prctl_2args(PR_SET_FPEXC, config as usize as *mut _) }.map(|_r| ()) +} + +// +// PR_GET_TIMING/PR_SET_TIMING +// + +const PR_GET_TIMING: c_int = 13; + +const PR_TIMING_STATISTICAL: i32 = 0; +const PR_TIMING_TIMESTAMP: i32 = 1; + +/// `PR_TIMING_*`. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(i32)] +pub enum TimingMethod { + /// Normal, traditional, statistical process timing. + Statistical = PR_TIMING_STATISTICAL, + /// Accurate timestamp based process timing. + TimeStamp = PR_TIMING_TIMESTAMP, +} + +impl TryFrom for TimingMethod { + type Error = io::Errno; + + fn try_from(value: i32) -> Result { + match value { + PR_TIMING_STATISTICAL => Ok(Self::Statistical), + PR_TIMING_TIMESTAMP => Ok(Self::TimeStamp), + _ => Err(io::Errno::RANGE), + } + } +} + +/// Get which process timing method is currently in use. +/// +/// # References +/// - [`prctl(PR_GET_TIMING,...)`] +/// +/// [`prctl(PR_GET_TIMING,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn timing_method() -> io::Result { + unsafe { prctl_1arg(PR_GET_TIMING) }.and_then(TryInto::try_into) +} + +const PR_SET_TIMING: c_int = 14; + +/// Set whether to use (normal, traditional) statistical process timing or accurate +/// timestamp-based process timing. +/// +/// # References +/// - [`prctl(PR_SET_TIMING,...)`] +/// +/// [`prctl(PR_SET_TIMING,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn set_timing_method(method: TimingMethod) -> io::Result<()> { + unsafe { prctl_2args(PR_SET_TIMING, method as usize as *mut _) }.map(|_r| ()) +} + +// +// PR_GET_ENDIAN/PR_SET_ENDIAN +// + +const PR_GET_ENDIAN: c_int = 19; + +const PR_ENDIAN_BIG: u32 = 0; +const PR_ENDIAN_LITTLE: u32 = 1; +const PR_ENDIAN_PPC_LITTLE: u32 = 2; + +/// `PR_ENDIAN_*`. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(u32)] +pub enum EndianMode { + /// Big endian mode. + Big = PR_ENDIAN_BIG, + /// True little endian mode. + Little = PR_ENDIAN_LITTLE, + /// `PowerPC` pseudo little endian. + PowerPCLittle = PR_ENDIAN_PPC_LITTLE, +} + +impl TryFrom for EndianMode { + type Error = io::Errno; + + fn try_from(value: u32) -> Result { + match value { + PR_ENDIAN_BIG => Ok(Self::Big), + PR_ENDIAN_LITTLE => Ok(Self::Little), + PR_ENDIAN_PPC_LITTLE => Ok(Self::PowerPCLittle), + _ => Err(io::Errno::RANGE), + } + } +} + +/// Get the endianness of the calling process. +/// +/// # References +/// - [`prctl(PR_GET_ENDIAN,...)`] +/// +/// [`prctl(PR_GET_ENDIAN,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn endian_mode() -> io::Result { + unsafe { prctl_get_at_arg2::(PR_GET_ENDIAN) } +} + +const PR_SET_ENDIAN: c_int = 20; + +/// Set the endianness of the calling process. +/// +/// # References +/// - [`prctl(PR_SET_ENDIAN,...)`] +/// +/// # Safety +/// +/// Please ensure the conditions necessary to safely call this function, +/// as detailed in the references above. +/// +/// [`prctl(PR_SET_ENDIAN,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub unsafe fn set_endian_mode(mode: EndianMode) -> io::Result<()> { + prctl_2args(PR_SET_ENDIAN, mode as usize as *mut _).map(|_r| ()) +} + +// +// PR_GET_TSC/PR_SET_TSC +// + +const PR_GET_TSC: c_int = 25; + +const PR_TSC_ENABLE: u32 = 1; +const PR_TSC_SIGSEGV: u32 = 2; + +/// `PR_TSC_*`. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(u32)] +pub enum TimeStampCounterReadability { + /// Allow the use of the timestamp counter. + Readable = PR_TSC_ENABLE, + /// Throw a `SIGSEGV` instead of reading the TSC. + RaiseSIGSEGV = PR_TSC_SIGSEGV, +} + +impl TryFrom for TimeStampCounterReadability { + type Error = io::Errno; + + fn try_from(value: u32) -> Result { + match value { + PR_TSC_ENABLE => Ok(Self::Readable), + PR_TSC_SIGSEGV => Ok(Self::RaiseSIGSEGV), + _ => Err(io::Errno::RANGE), + } + } +} + +/// Get the state of the flag determining if the timestamp counter can be read. +/// +/// # References +/// - [`prctl(PR_GET_TSC,...)`] +/// +/// [`prctl(PR_GET_TSC,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn time_stamp_counter_readability() -> io::Result { + unsafe { prctl_get_at_arg2::(PR_GET_TSC) } +} + +const PR_SET_TSC: c_int = 26; + +/// Set the state of the flag determining if the timestamp counter can be read by the process. +/// +/// # References +/// - [`prctl(PR_SET_TSC,...)`] +/// +/// [`prctl(PR_SET_TSC,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn set_time_stamp_counter_readability( + readability: TimeStampCounterReadability, +) -> io::Result<()> { + unsafe { prctl_2args(PR_SET_TSC, readability as usize as *mut _) }.map(|_r| ()) +} + +// +// PR_TASK_PERF_EVENTS_DISABLE/PR_TASK_PERF_EVENTS_ENABLE +// + +const PR_TASK_PERF_EVENTS_DISABLE: c_int = 31; +const PR_TASK_PERF_EVENTS_ENABLE: c_int = 32; + +/// Enable or disable all performance counters attached to the calling process. +/// +/// # References +/// - [`prctl(PR_TASK_PERF_EVENTS_ENABLE,...)`] +/// - [`prctl(PR_TASK_PERF_EVENTS_DISABLE,...)`] +/// +/// [`prctl(PR_TASK_PERF_EVENTS_ENABLE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +/// [`prctl(PR_TASK_PERF_EVENTS_DISABLE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn configure_performance_counters(enable: bool) -> io::Result<()> { + let option = if enable { + PR_TASK_PERF_EVENTS_ENABLE + } else { + PR_TASK_PERF_EVENTS_DISABLE + }; + + unsafe { prctl_1arg(option) }.map(|_r| ()) +} + +// +// PR_MCE_KILL_GET/PR_MCE_KILL +// + +const PR_MCE_KILL_GET: c_int = 34; + +const PR_MCE_KILL_LATE: u32 = 0; +const PR_MCE_KILL_EARLY: u32 = 1; +const PR_MCE_KILL_DEFAULT: u32 = 2; + +/// `PR_MCE_KILL_*`. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(u32)] +pub enum MachineCheckMemoryCorruptionKillPolicy { + /// Late kill policy. + Late = PR_MCE_KILL_LATE, + /// Early kill policy. + Early = PR_MCE_KILL_EARLY, + /// System-wide default policy. + Default = PR_MCE_KILL_DEFAULT, +} + +impl TryFrom for MachineCheckMemoryCorruptionKillPolicy { + type Error = io::Errno; + + fn try_from(value: u32) -> Result { + match value { + PR_MCE_KILL_LATE => Ok(Self::Late), + PR_MCE_KILL_EARLY => Ok(Self::Early), + PR_MCE_KILL_DEFAULT => Ok(Self::Default), + _ => Err(io::Errno::RANGE), + } + } +} + +/// Get the current per-process machine check kill policy. +/// +/// # References +/// - [`prctl(PR_MCE_KILL_GET,...)`] +/// +/// [`prctl(PR_MCE_KILL_GET,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn machine_check_memory_corruption_kill_policy( +) -> io::Result { + let r = unsafe { prctl_1arg(PR_MCE_KILL_GET)? } as c_uint; + MachineCheckMemoryCorruptionKillPolicy::try_from(r) +} + +const PR_MCE_KILL: c_int = 33; + +const PR_MCE_KILL_CLEAR: usize = 0; +const PR_MCE_KILL_SET: usize = 1; + +/// Set the machine check memory corruption kill policy for the calling thread. +/// +/// # References +/// - [`prctl(PR_MCE_KILL,...)`] +/// +/// [`prctl(PR_MCE_KILL,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn set_machine_check_memory_corruption_kill_policy( + policy: Option, +) -> io::Result<()> { + let (sub_operation, policy) = if let Some(policy) = policy { + (PR_MCE_KILL_SET, policy as usize as *mut _) + } else { + (PR_MCE_KILL_CLEAR, ptr::null_mut()) + }; + + unsafe { prctl_3args(PR_MCE_KILL, sub_operation as *mut _, policy) }.map(|_r| ()) +} + +// +// PR_SET_MM +// + +const PR_SET_MM: c_int = 35; + +const PR_SET_MM_START_CODE: u32 = 1; +const PR_SET_MM_END_CODE: u32 = 2; +const PR_SET_MM_START_DATA: u32 = 3; +const PR_SET_MM_END_DATA: u32 = 4; +const PR_SET_MM_START_STACK: u32 = 5; +const PR_SET_MM_START_BRK: u32 = 6; +const PR_SET_MM_BRK: u32 = 7; +const PR_SET_MM_ARG_START: u32 = 8; +const PR_SET_MM_ARG_END: u32 = 9; +const PR_SET_MM_ENV_START: u32 = 10; +const PR_SET_MM_ENV_END: u32 = 11; +const PR_SET_MM_AUXV: usize = 12; +const PR_SET_MM_EXE_FILE: usize = 13; +const PR_SET_MM_MAP: usize = 14; +const PR_SET_MM_MAP_SIZE: usize = 15; + +/// `PR_SET_MM_*`. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(u32)] +pub enum VirtualMemoryMapAddress { + /// Set the address above which the program text can run. + CodeStart = PR_SET_MM_START_CODE, + /// Set the address below which the program text can run. + CodeEnd = PR_SET_MM_END_CODE, + /// Set the address above which initialized and uninitialized (bss) data are placed. + DataStart = PR_SET_MM_START_DATA, + /// Set the address below which initialized and uninitialized (bss) data are placed. + DataEnd = PR_SET_MM_END_DATA, + /// Set the start address of the stack. + StackStart = PR_SET_MM_START_STACK, + /// Set the address above which the program heap can be expanded with `brk` call. + BrkStart = PR_SET_MM_START_BRK, + /// Set the current `brk` value. + BrkCurrent = PR_SET_MM_BRK, + /// Set the address above which the program command line is placed. + ArgStart = PR_SET_MM_ARG_START, + /// Set the address below which the program command line is placed. + ArgEnd = PR_SET_MM_ARG_END, + /// Set the address above which the program environment is placed. + EnvironmentStart = PR_SET_MM_ENV_START, + /// Set the address below which the program environment is placed. + EnvironmentEnd = PR_SET_MM_ENV_END, +} + +/// Modify certain kernel memory map descriptor addresses of the calling process. +/// +/// # References +/// - [`prctl(PR_SET_MM,...)`] +/// +/// # Safety +/// +/// Please ensure the conditions necessary to safely call this function, +/// as detailed in the references above. +/// +/// [`prctl(PR_SET_MM,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub unsafe fn set_virtual_memory_map_address( + option: VirtualMemoryMapAddress, + address: Option>, +) -> io::Result<()> { + let address = address.map_or_else(ptr::null_mut, NonNull::as_ptr); + prctl_3args(PR_SET_MM, option as usize as *mut _, address).map(|_r| ()) +} + +/// Supersede the `/proc/pid/exe` symbolic link with a new one pointing to a new executable file. +/// +/// # References +/// - [`prctl(PR_SET_MM,PR_SET_MM_EXE_FILE,...)`] +/// +/// [`prctl(PR_SET_MM,PR_SET_MM_EXE_FILE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn set_executable_file(fd: BorrowedFd) -> io::Result<()> { + let fd = usize::try_from(fd.as_raw_fd()).map_err(|_r| io::Errno::RANGE)?; + unsafe { prctl_3args(PR_SET_MM, PR_SET_MM_EXE_FILE as *mut _, fd as *mut _) }.map(|_r| ()) +} + +/// Set a new auxiliary vector. +/// +/// # References +/// - [`prctl(PR_SET_MM,PR_SET_MM_AUXV,...)`] +/// +/// # Safety +/// +/// Please ensure the conditions necessary to safely call this function, +/// as detailed in the references above. +/// +/// [`prctl(PR_SET_MM,PR_SET_MM_AUXV,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub unsafe fn set_auxiliary_vector(auxv: &[*const c_void]) -> io::Result<()> { + syscalls::prctl( + PR_SET_MM, + PR_SET_MM_AUXV as *mut _, + auxv.as_ptr() as *mut _, + auxv.len() as *mut _, + ptr::null_mut(), + ) + .map(|_r| ()) +} + +/// Get the size of the [`PrctlMmMap`] the kernel expects. +/// +/// # References +/// - [`prctl(PR_SET_MM,PR_SET_MM_MAP_SIZE,...)`] +/// +/// [`prctl(PR_SET_MM,PR_SET_MM_MAP_SIZE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn virtual_memory_map_config_struct_size() -> io::Result { + let mut value: c_uint = 0; + let value_ptr = (&mut value) as *mut c_uint; + unsafe { prctl_3args(PR_SET_MM, PR_SET_MM_MAP_SIZE as *mut _, value_ptr.cast())? }; + Ok(value as usize) +} + +/// This structure provides new memory descriptor map which mostly modifies `/proc/pid/stat[m]` +/// output for a task. +/// This mostly done in a sake of checkpoint/restore functionality. +#[repr(C)] +#[derive(Debug, Clone)] +pub struct PrctlMmMap { + /// Code section start address. + pub start_code: u64, + /// Code section end address. + pub end_code: u64, + /// Data section start address. + pub start_data: u64, + /// Data section end address. + pub end_data: u64, + /// brk() start address. + pub start_brk: u64, + /// brk() current address. + pub brk: u64, + /// Stack start address. + pub start_stack: u64, + /// Program command line start address. + pub arg_start: u64, + /// Program command line end address. + pub arg_end: u64, + /// Program environment start address. + pub env_start: u64, + /// Program environment end address. + pub env_end: u64, + /// Auxiliary vector start address. + pub auxv: *mut u64, + /// Auxiliary vector size. + pub auxv_size: u32, + /// File descriptor of executable file that was used to create this process. + pub exe_fd: u32, +} + +/// Provides one-shot access to all the addresses by passing in a [`PrctlMmMap`]. +/// +/// # References +/// - [`prctl(PR_SET_MM,PR_SET_MM_MAP,...)`] +/// +/// # Safety +/// +/// Please ensure the conditions necessary to safely call this function, +/// as detailed in the references above. +/// +/// [`prctl(PR_SET_MM,PR_SET_MM_MAP,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub unsafe fn configure_virtual_memory_map(config: &PrctlMmMap) -> io::Result<()> { + syscalls::prctl( + PR_SET_MM, + PR_SET_MM_MAP as *mut _, + config as *const PrctlMmMap as *mut _, + mem::size_of::() as *mut _, + ptr::null_mut(), + ) + .map(|_r| ()) +} + +// +// PR_SET_PTRACER +// + +const PR_SET_PTRACER: c_int = 0x59_61_6d_61; + +const PR_SET_PTRACER_ANY: usize = usize::MAX; + +/// Process ptracer. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum PTracer { + /// None. + None, + /// Disable `ptrace` restrictions for the calling process. + Any, + /// Specific process. + ProcessID(Pid), +} + +/// Declare that the ptracer process can `ptrace` the calling process as if it were a direct +/// process ancestor. +/// +/// # References +/// - [`prctl(PR_SET_PTRACER,...)`] +/// +/// [`prctl(PR_SET_PTRACER,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn set_ptracer(tracer: PTracer) -> io::Result<()> { + let pid = match tracer { + PTracer::None => ptr::null_mut(), + PTracer::Any => PR_SET_PTRACER_ANY as *mut _, + PTracer::ProcessID(pid) => pid.as_raw_nonzero().get() as usize as *mut _, + }; + + unsafe { prctl_2args(PR_SET_PTRACER, pid) }.map(|_r| ()) +} + +// +// PR_GET_CHILD_SUBREAPER/PR_SET_CHILD_SUBREAPER +// + +const PR_GET_CHILD_SUBREAPER: c_int = 37; + +/// Get the `child subreaper` setting of the calling process. +/// +/// # References +/// - [`prctl(PR_GET_CHILD_SUBREAPER,...)`] +/// +/// [`prctl(PR_GET_CHILD_SUBREAPER,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn child_subreaper() -> io::Result> { + unsafe { + let r = prctl_get_at_arg2_optional::(PR_GET_CHILD_SUBREAPER)?; + Ok(Pid::from_raw(r as RawPid)) + } +} + +const PR_SET_CHILD_SUBREAPER: c_int = 36; + +/// Set the `child subreaper` attribute of the calling process. +/// +/// # References +/// - [`prctl(PR_SET_CHILD_SUBREAPER,...)`] +/// +/// [`prctl(PR_SET_CHILD_SUBREAPER,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn set_child_subreaper(pid: Option) -> io::Result<()> { + let pid = pid.map_or(0_usize, |pid| pid.as_raw_nonzero().get() as usize); + unsafe { prctl_2args(PR_SET_CHILD_SUBREAPER, pid as *mut _) }.map(|_r| ()) +} + +// +// PR_GET_FP_MODE/PR_SET_FP_MODE +// + +const PR_GET_FP_MODE: c_int = 46; + +const PR_FP_MODE_FR: u32 = 1_u32 << 0; +const PR_FP_MODE_FRE: u32 = 1_u32 << 1; + +/// `PR_FP_MODE_*`. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(u32)] +pub enum FloatingPointMode { + /// 64-bit floating point registers. + FloatingPointRegisters = PR_FP_MODE_FR, + /// Enable emulation of 32-bit floating-point mode. + FloatingPointEmulation = PR_FP_MODE_FRE, +} + +impl TryFrom for FloatingPointMode { + type Error = io::Errno; + + fn try_from(value: u32) -> Result { + match value { + PR_FP_MODE_FR => Ok(Self::FloatingPointRegisters), + PR_FP_MODE_FRE => Ok(Self::FloatingPointEmulation), + _ => Err(io::Errno::RANGE), + } + } +} + +/// Get the current floating point mode. +/// +/// # References +/// - [`prctl(PR_GET_FP_MODE,...)`] +/// +/// [`prctl(PR_GET_FP_MODE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn floating_point_mode() -> io::Result { + let r = unsafe { prctl_1arg(PR_GET_FP_MODE)? } as c_uint; + FloatingPointMode::try_from(r) +} + +const PR_SET_FP_MODE: c_int = 45; + +/// Allow control of the floating point mode from user space. +/// +/// # References +/// - [`prctl(PR_SET_FP_MODE,...)`] +/// +/// [`prctl(PR_SET_FP_MODE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn set_floating_point_mode(mode: FloatingPointMode) -> io::Result<()> { + unsafe { prctl_2args(PR_SET_FP_MODE, mode as usize as *mut _) }.map(|_r| ()) +} + +// +// PR_GET_SPECULATION_CTRL/PR_SET_SPECULATION_CTRL +// + +const PR_GET_SPECULATION_CTRL: c_int = 52; + +const PR_SPEC_STORE_BYPASS: u32 = 0; +const PR_SPEC_INDIRECT_BRANCH: u32 = 1; +const PR_SPEC_L1D_FLUSH: u32 = 2; + +/// `PR_SPEC_*`. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(u32)] +pub enum SpeculationFeature { + /// Set the state of the speculative store bypass misfeature. + SpeculativeStoreBypass = PR_SPEC_STORE_BYPASS, + /// Set the state of the indirect branch speculation misfeature. + IndirectBranchSpeculation = PR_SPEC_INDIRECT_BRANCH, + /// Flush L1D Cache on context switch out of the task. + FlushL1DCacheOnContextSwitchOutOfTask = PR_SPEC_L1D_FLUSH, +} + +impl TryFrom for SpeculationFeature { + type Error = io::Errno; + + fn try_from(value: u32) -> Result { + match value { + PR_SPEC_STORE_BYPASS => Ok(Self::SpeculativeStoreBypass), + PR_SPEC_INDIRECT_BRANCH => Ok(Self::IndirectBranchSpeculation), + PR_SPEC_L1D_FLUSH => Ok(Self::FlushL1DCacheOnContextSwitchOutOfTask), + _ => Err(io::Errno::RANGE), + } + } +} + +bitflags! { + /// `PR_SPEC_*`. + pub struct SpeculationFeatureControl: u32 { + /// The speculation feature is enabled, mitigation is disabled. + const ENABLE = 1_u32 << 1; + /// The speculation feature is disabled, mitigation is enabled. + const DISABLE = 1_u32 << 2; + /// The speculation feature is disabled, mitigation is enabled, and it cannot be undone. + const FORCE_DISABLE = 1_u32 << 3; + /// The speculation feature is disabled, mitigation is enabled, and the state will be cleared on `execve`. + const DISABLE_NOEXEC = 1_u32 << 4; + } +} + +bitflags! { + /// Zero means the processors are not vulnerable. + pub struct SpeculationFeatureState: u32 { + /// Mitigation can be controlled per thread by `PR_SET_SPECULATION_CTRL`. + const PRCTL = 1_u32 << 0; + /// The speculation feature is enabled, mitigation is disabled. + const ENABLE = 1_u32 << 1; + /// The speculation feature is disabled, mitigation is enabled. + const DISABLE = 1_u32 << 2; + /// The speculation feature is disabled, mitigation is enabled, and it cannot be undone. + const FORCE_DISABLE = 1_u32 << 3; + /// The speculation feature is disabled, mitigation is enabled, and the state will be cleared on `execve`. + const DISABLE_NOEXEC = 1_u32 << 4; + } +} + +/// Get the state of the speculation misfeature. +/// +/// # References +/// - [`prctl(PR_GET_SPECULATION_CTRL,...)`] +/// +/// [`prctl(PR_GET_SPECULATION_CTRL,...)`]: https://www.kernel.org/doc/html/v5.18/userspace-api/spec_ctrl.html +#[inline] +pub fn speculative_feature_state( + feature: SpeculationFeature, +) -> io::Result> { + let r = unsafe { prctl_2args(PR_GET_SPECULATION_CTRL, feature as usize as *mut _)? } as c_uint; + Ok(SpeculationFeatureState::from_bits(r)) +} + +const PR_SET_SPECULATION_CTRL: c_int = 53; + +/// Sets the state of the speculation misfeature. +/// +/// # References +/// - [`prctl(PR_SET_SPECULATION_CTRL,...)`] +/// +/// [`prctl(PR_SET_SPECULATION_CTRL,...)`]: https://www.kernel.org/doc/html/v5.18/userspace-api/spec_ctrl.html +#[inline] +pub fn control_speculative_feature( + feature: SpeculationFeature, + config: SpeculationFeatureControl, +) -> io::Result<()> { + let feature = feature as usize as *mut _; + let config = config.bits() as usize as *mut _; + unsafe { prctl_3args(PR_SET_SPECULATION_CTRL, feature, config) }.map(|_r| ()) +} + +// +// PR_GET_IO_FLUSHER/PR_SET_IO_FLUSHER +// + +const PR_GET_IO_FLUSHER: c_int = 58; + +/// Get the `IO_FLUSHER` state of the caller. +/// +/// # References +/// - [`prctl(PR_GET_IO_FLUSHER,...)`] +/// +/// [`prctl(PR_GET_IO_FLUSHER,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn is_io_flusher() -> io::Result { + unsafe { prctl_1arg(PR_GET_IO_FLUSHER) }.map(|r| r != 0) +} + +const PR_SET_IO_FLUSHER: c_int = 57; + +/// Put the process in the `IO_FLUSHER` state, allowing it to make progress when +/// allocating memory. +/// +/// # References +/// - [`prctl(PR_SET_IO_FLUSHER,...)`] +/// +/// [`prctl(PR_SET_IO_FLUSHER,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn configure_io_flusher_behavior(enable: bool) -> io::Result<()> { + unsafe { prctl_2args(PR_SET_IO_FLUSHER, enable as usize as *mut _) }.map(|_r| ()) +} + +// +// PR_PAC_GET_ENABLED_KEYS/PR_PAC_SET_ENABLED_KEYS +// + +const PR_PAC_GET_ENABLED_KEYS: c_int = 61; + +bitflags! { + /// `PR_PAC_AP*`. + pub struct PointerAuthenticationKeys: u32 { + /// Instruction authentication key `A`. + const INSTRUCTION_AUTHENTICATION_KEY_A = 1_u32 << 0; + /// Instruction authentication key `B`. + const INSTRUCTION_AUTHENTICATION_KEY_B = 1_u32 << 1; + /// Data authentication key `A`. + const DATA_AUTHENTICATION_KEY_A = 1_u32 << 2; + /// Data authentication key `B`. + const DATA_AUTHENTICATION_KEY_B = 1_u32 << 3; + /// Generic authentication `A` key. + const GENERIC_AUTHENTICATION_KEY_A = 1_u32 << 4; + } +} + +/// Get enabled pointer authentication keys. +/// +/// # References +/// - [`prctl(PR_PAC_GET_ENABLED_KEYS,...)`] +/// +/// [`prctl(PR_PAC_GET_ENABLED_KEYS,...)`]: https://www.kernel.org/doc/html/v5.18/arm64/pointer-authentication.html +#[inline] +pub fn enabled_pointer_authentication_keys() -> io::Result { + let r = unsafe { prctl_1arg(PR_PAC_GET_ENABLED_KEYS)? } as c_uint; + PointerAuthenticationKeys::from_bits(r).ok_or(io::Errno::RANGE) +} + +const PR_PAC_SET_ENABLED_KEYS: c_int = 60; + +/// Set enabled pointer authentication keys. +/// +/// # References +/// - [`prctl(PR_PAC_SET_ENABLED_KEYS,...)`] +/// +/// # Safety +/// +/// Please ensure the conditions necessary to safely call this function, +/// as detailed in the references above. +/// +/// [`prctl(PR_PAC_SET_ENABLED_KEYS,...)`]: https://www.kernel.org/doc/html/v5.18/arm64/pointer-authentication.html +#[inline] +pub unsafe fn configure_pointer_authentication_keys( + config: impl Iterator, +) -> io::Result<()> { + let mut affected_keys: u32 = 0; + let mut enabled_keys: u32 = 0; + + for (key, enable) in config { + let key = key.bits(); + affected_keys |= key; + + if enable { + enabled_keys |= key; + } else { + enabled_keys &= !key; + } + } + + if affected_keys == 0 { + return Ok(()); // Nothing to do. + } + + prctl_3args( + PR_PAC_SET_ENABLED_KEYS, + affected_keys as usize as *mut _, + enabled_keys as usize as *mut _, + ) + .map(|_r| ()) +} + +// +// PR_SET_VMA +// + +const PR_SET_VMA: c_int = 0x53_56_4d_41; + +const PR_SET_VMA_ANON_NAME: usize = 0; + +/// Set the name for a virtual memory region. +/// +/// # References +/// - [`prctl(PR_SET_VMA,PR_SET_VMA_ANON_NAME,...)`] +/// +/// [`prctl(PR_SET_VMA,PR_SET_VMA_ANON_NAME,...)`]: https://lwn.net/Articles/867818/ +#[inline] +pub fn set_virtual_memory_region_name(region: &[u8], name: Option<&CStr>) -> io::Result<()> { + unsafe { + syscalls::prctl( + PR_SET_VMA, + PR_SET_VMA_ANON_NAME as *mut _, + region.as_ptr() as *mut _, + region.len() as *mut _, + name.map_or_else(ptr::null, CStr::as_ptr) as *mut _, + ) + .map(|_r| ()) + } +} diff --git a/src/thread/mod.rs b/src/thread/mod.rs index ac48b435..fd30a2d2 100644 --- a/src/thread/mod.rs +++ b/src/thread/mod.rs @@ -6,6 +6,8 @@ mod clock; mod futex; #[cfg(any(target_os = "android", target_os = "linux"))] mod id; +#[cfg(any(target_os = "android", target_os = "linux"))] +mod prctl; #[cfg(not(any( target_os = "dragonfly", @@ -24,3 +26,5 @@ pub use clock::{nanosleep, NanosleepRelativeResult, Timespec}; pub use futex::{futex, FutexFlags, FutexOperation}; #[cfg(any(target_os = "android", target_os = "linux"))] pub use id::gettid; +#[cfg(any(target_os = "android", target_os = "linux"))] +pub use prctl::*; diff --git a/src/thread/prctl.rs b/src/thread/prctl.rs new file mode 100644 index 00000000..a2191f7c --- /dev/null +++ b/src/thread/prctl.rs @@ -0,0 +1,989 @@ +#![allow(unsafe_code)] + +use core::convert::TryFrom; +use core::mem::MaybeUninit; +use core::num::NonZeroU64; +use core::ptr; +use core::ptr::NonNull; +use core::sync::atomic::AtomicU8; + +use bitflags::bitflags; + +use crate::backend::c::{c_int, c_uint, c_void}; +use crate::backend::process::syscalls; +use crate::ffi::{CStr, CString}; +use crate::io; +use crate::process::{ + prctl_1arg, prctl_2args, prctl_3args, prctl_get_at_arg2_optional, Pid, + PointerAuthenticationKeys, +}; + +// +// PR_GET_KEEPCAPS/PR_SET_KEEPCAPS +// + +const PR_GET_KEEPCAPS: c_int = 7; + +/// Get the current state of the calling thread's `keep capabilities` flag. +/// +/// # References +/// - [`prctl(PR_GET_KEEPCAPS,...)`] +/// +/// [`prctl(PR_GET_KEEPCAPS,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn get_keep_capabilities() -> io::Result { + unsafe { prctl_1arg(PR_GET_KEEPCAPS) }.map(|r| r != 0) +} + +const PR_SET_KEEPCAPS: c_int = 8; + +/// Set the state of the calling thread's `keep capabilities` flag. +/// +/// # References +/// - [`prctl(PR_SET_KEEPCAPS,...)`] +/// +/// [`prctl(PR_SET_KEEPCAPS,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn set_keep_capabilities(enable: bool) -> io::Result<()> { + unsafe { prctl_2args(PR_SET_KEEPCAPS, enable as usize as *mut _) }.map(|_r| ()) +} + +// +// PR_GET_NAME/PR_SET_NAME +// + +const PR_GET_NAME: c_int = 16; + +/// Get the name of the calling thread. +/// +/// # References +/// - [`prctl(PR_GET_NAME,...)`] +/// +/// [`prctl(PR_GET_NAME,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn name() -> io::Result { + let mut buffer = [0_u8; 16]; + unsafe { prctl_2args(PR_GET_NAME, buffer.as_mut_ptr().cast())? }; + + let len = buffer.iter().position(|&x| x == 0_u8).unwrap_or(0); + CString::new(&buffer[..len]).map_err(|_r| io::Errno::ILSEQ) +} + +const PR_SET_NAME: c_int = 15; + +/// Set the name of the calling thread. +/// +/// # References +/// - [`prctl(PR_SET_NAME,...)`] +/// +/// [`prctl(PR_SET_NAME,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn set_name(name: &CStr) -> io::Result<()> { + unsafe { prctl_2args(PR_SET_NAME, name.as_ptr() as *mut _) }.map(|_r| ()) +} + +// +// PR_GET_SECCOMP/PR_SET_SECCOMP +// + +//const PR_GET_SECCOMP: c_int = 21; + +const SECCOMP_MODE_DISABLED: i32 = 0; +const SECCOMP_MODE_STRICT: i32 = 1; +const SECCOMP_MODE_FILTER: i32 = 2; + +/// `SECCOMP_MODE_*`. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(i32)] +pub enum SecureComputingMode { + /// Secure computing is not in use. + Disabled = SECCOMP_MODE_DISABLED, + /// Use hard-coded filter. + Strict = SECCOMP_MODE_STRICT, + /// Use user-supplied filter. + Filter = SECCOMP_MODE_FILTER, +} + +impl TryFrom for SecureComputingMode { + type Error = io::Errno; + + fn try_from(value: i32) -> Result { + match value { + SECCOMP_MODE_DISABLED => Ok(Self::Disabled), + SECCOMP_MODE_STRICT => Ok(Self::Strict), + SECCOMP_MODE_FILTER => Ok(Self::Filter), + _ => Err(io::Errno::RANGE), + } + } +} + +/* +/// Get the secure computing mode of the calling thread. +/// +/// If the caller is not in secure computing mode, this returns [`SecureComputingMode::Disabled`]. +/// If the caller is in strict secure computing mode, then this call will cause a `SIGKILL` signal +/// to be sent to the process. +/// If the caller is in filter mode, and this system call is allowed by the seccomp filters, +/// it returns [`SecureComputingMode::Filter`]; otherwise, the process is killed with +/// a `SIGKILL` signal. +/// +/// Since Linux 3.8, the Seccomp field of the `/proc/[pid]/status` file provides a method +/// of obtaining the same information, without the risk that the process is killed; see `proc(5)`. +/// +/// # References +/// - [`prctl(PR_GET_SECCOMP,...)`] +/// +/// [`prctl(PR_GET_SECCOMP,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn secure_computing_mode() -> io::Result { + unsafe { prctl_1arg(PR_GET_SECCOMP) }.and_then(TryInto::try_into) +} +*/ + +const PR_SET_SECCOMP: c_int = 22; + +/// Set the secure computing mode for the calling thread, to limit the available system calls. +/// +/// # References +/// - [`prctl(PR_SET_SECCOMP,...)`] +/// +/// [`prctl(PR_SET_SECCOMP,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn set_secure_computing_mode(mode: SecureComputingMode) -> io::Result<()> { + unsafe { prctl_2args(PR_SET_SECCOMP, mode as usize as *mut _) }.map(|_r| ()) +} + +// +// PR_CAPBSET_READ/PR_CAPBSET_DROP +// + +const PR_CAPBSET_READ: c_int = 23; + +const CAP_CHOWN: u32 = 0; +const CAP_DAC_OVERRIDE: u32 = 1; +const CAP_DAC_READ_SEARCH: u32 = 2; +const CAP_FOWNER: u32 = 3; +const CAP_FSETID: u32 = 4; +const CAP_KILL: u32 = 5; +const CAP_SETGID: u32 = 6; +const CAP_SETUID: u32 = 7; +const CAP_SETPCAP: u32 = 8; +const CAP_LINUX_IMMUTABLE: u32 = 9; +const CAP_NET_BIND_SERVICE: u32 = 10; +const CAP_NET_BROADCAST: u32 = 11; +const CAP_NET_ADMIN: u32 = 12; +const CAP_NET_RAW: u32 = 13; +const CAP_IPC_LOCK: u32 = 14; +const CAP_IPC_OWNER: u32 = 15; +const CAP_SYS_MODULE: u32 = 16; +const CAP_SYS_RAWIO: u32 = 17; +const CAP_SYS_CHROOT: u32 = 18; +const CAP_SYS_PTRACE: u32 = 19; +const CAP_SYS_PACCT: u32 = 20; +const CAP_SYS_ADMIN: u32 = 21; +const CAP_SYS_BOOT: u32 = 22; +const CAP_SYS_NICE: u32 = 23; +const CAP_SYS_RESOURCE: u32 = 24; +const CAP_SYS_TIME: u32 = 25; +const CAP_SYS_TTY_CONFIG: u32 = 26; +const CAP_MKNOD: u32 = 27; +const CAP_LEASE: u32 = 28; +const CAP_AUDIT_WRITE: u32 = 29; +const CAP_AUDIT_CONTROL: u32 = 30; +const CAP_SETFCAP: u32 = 31; +const CAP_MAC_OVERRIDE: u32 = 32; +const CAP_MAC_ADMIN: u32 = 33; +const CAP_SYSLOG: u32 = 34; +const CAP_WAKE_ALARM: u32 = 35; +const CAP_BLOCK_SUSPEND: u32 = 36; +const CAP_AUDIT_READ: u32 = 37; +const CAP_PERFMON: u32 = 38; +const CAP_BPF: u32 = 39; +const CAP_CHECKPOINT_RESTORE: u32 = 40; + +/// Linux per-thread capability. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(u32)] +pub enum Capability { + /// In a system with the `_POSIX_CHOWN_RESTRICTED` option defined, this overrides + /// the restriction of changing file ownership and group ownership. + ChangeOwnership = CAP_CHOWN, + /// Override all DAC access, including ACL execute access if `_POSIX_ACL` is defined. + /// Excluding DAC access covered by [`Capability::LinuxImmutable`]. + DACOverride = CAP_DAC_OVERRIDE, + /// Overrides all DAC restrictions regarding read and search on files and directories, + /// including ACL restrictions if `_POSIX_ACL` is defined. Excluding DAC access covered + /// by [`Capability::LinuxImmutable`]. + DACReadSearch = CAP_DAC_READ_SEARCH, + /// Overrides all restrictions about allowed operations on files, where file owner ID must be + /// equal to the user ID, except where [`Capability::FileSetID`] is applicable. + /// It doesn't override MAC and DAC restrictions. + FileOwner = CAP_FOWNER, + /// Overrides the following restrictions that the effective user ID shall match the file owner + /// ID when setting the `S_ISUID` and `S_ISGID` bits on that file; that the effective group ID + /// (or one of the supplementary group IDs) shall match the file owner ID when setting the + /// `S_ISGID` bit on that file; that the `S_ISUID` and `S_ISGID` bits are cleared on successful + /// return from `chown` (not implemented). + FileSetID = CAP_FSETID, + /// Overrides the restriction that the real or effective user ID of a process sending a signal + /// must match the real or effective user ID of the process receiving the signal. + Kill = CAP_KILL, + /// Allows `setgid` manipulation. Allows `setgroups`. Allows forged gids on socket + /// credentials passing. + SetGroupID = CAP_SETGID, + /// Allows `set*uid` manipulation (including fsuid). Allows forged pids on socket + /// credentials passing. + SetUserID = CAP_SETUID, + /// Without VFS support for capabilities: + /// - Transfer any capability in your permitted set to any pid. + /// - remove any capability in your permitted set from any pid. + /// With VFS support for capabilities (neither of above, but) + /// - Add any capability from current's capability bounding set to the current process' + /// inheritable set. + /// - Allow taking bits out of capability bounding set. + /// - Allow modification of the securebits for a process. + SetPermittedCapabilities = CAP_SETPCAP, + /// Allow modification of `S_IMMUTABLE` and `S_APPEND` file attributes. + LinuxImmutable = CAP_LINUX_IMMUTABLE, + /// Allows binding to TCP/UDP sockets below 1024. Allows binding to ATM VCIs below 32. + NetBindService = CAP_NET_BIND_SERVICE, + /// Allow broadcasting, listen to multicast. + NetBroadcast = CAP_NET_BROADCAST, + /// Allow interface configuration. Allow administration of IP firewall, masquerading and + /// accounting. Allow setting debug option on sockets. Allow modification of routing tables. + /// Allow setting arbitrary process / process group ownership on sockets. Allow binding to any + /// address for transparent proxying (also via [`Capability::NetRaw`]). Allow setting TOS + /// (type of service). Allow setting promiscuous mode. Allow clearing driver statistics. + /// Allow multicasting. Allow read/write of device-specific registers. Allow activation of ATM + /// control sockets. + NetAdmin = CAP_NET_ADMIN, + /// Allow use of `RAW` sockets. Allow use of `PACKET` sockets. Allow binding to any address for + /// transparent proxying (also via [`Capability::NetAdmin`]). + NetRaw = CAP_NET_RAW, + /// Allow locking of shared memory segments. Allow mlock and mlockall (which doesn't really have + /// anything to do with IPC). + IPCLock = CAP_IPC_LOCK, + /// Override IPC ownership checks. + IPCOwner = CAP_IPC_OWNER, + /// Insert and remove kernel modules - modify kernel without limit. + SystemModule = CAP_SYS_MODULE, + /// Allow ioperm/iopl access. Allow sending USB messages to any device via `/dev/bus/usb`. + SystemRawIO = CAP_SYS_RAWIO, + /// Allow use of `chroot`. + SystemChangeRoot = CAP_SYS_CHROOT, + /// Allow `ptrace` of any process. + SystemProcessTrace = CAP_SYS_PTRACE, + /// Allow configuration of process accounting. + SystemProcessAccounting = CAP_SYS_PACCT, + /// Allow configuration of the secure attention key. Allow administration of the random device. + /// Allow examination and configuration of disk quotas. Allow setting the domainname. + /// Allow setting the hostname. Allow `mount` and `umount`, setting up new smb connection. + /// Allow some autofs root ioctls. Allow nfsservctl. Allow `VM86_REQUEST_IRQ`. + /// Allow to read/write pci config on alpha. Allow `irix_prctl` on mips (setstacksize). + /// Allow flushing all cache on m68k (`sys_cacheflush`). Allow removing semaphores. + /// Used instead of [`Capability::ChangeOwnership`] to "chown" IPC message queues, semaphores + /// and shared memory. Allow locking/unlocking of shared memory segment. Allow turning swap + /// on/off. Allow forged pids on socket credentials passing. Allow setting readahead and + /// flushing buffers on block devices. Allow setting geometry in floppy driver. Allow turning + /// DMA on/off in `xd` driver. Allow administration of md devices (mostly the above, but some + /// extra ioctls). Allow tuning the ide driver. Allow access to the nvram device. Allow + /// administration of `apm_bios`, serial and bttv (TV) device. Allow manufacturer commands in + /// isdn CAPI support driver. Allow reading non-standardized portions of pci configuration + /// space. Allow DDI debug ioctl on sbpcd driver. Allow setting up serial ports. Allow sending + /// raw qic-117 commands. Allow enabling/disabling tagged queuing on SCSI controllers and + /// sending arbitrary SCSI commands. Allow setting encryption key on loopback filesystem. + /// Allow setting zone reclaim policy. Allow everything under + /// [`Capability::BerkeleyPacketFilters`] and [`Capability::PerformanceMonitoring`] for backward + /// compatibility. + SystemAdmin = CAP_SYS_ADMIN, + /// Allow use of `reboot`. + SystemBoot = CAP_SYS_BOOT, + /// Allow raising priority and setting priority on other (different UID) processes. Allow use of + /// FIFO and round-robin (realtime) scheduling on own processes and setting the scheduling + /// algorithm used by another process. Allow setting cpu affinity on other processes. + /// Allow setting realtime ioprio class. Allow setting ioprio class on other processes. + SystemNice = CAP_SYS_NICE, + /// Override resource limits. Set resource limits. Override quota limits. Override reserved + /// space on ext2 filesystem. Modify data journaling mode on ext3 filesystem (uses journaling + /// resources). NOTE: ext2 honors fsuid when checking for resource overrides, so you can + /// override using fsuid too. Override size restrictions on IPC message queues. Allow more than + /// 64hz interrupts from the real-time clock. Override max number of consoles on console + /// allocation. Override max number of keymaps. Control memory reclaim behavior. + SystemResource = CAP_SYS_RESOURCE, + /// Allow manipulation of system clock. Allow `irix_stime` on mips. Allow setting the real-time + /// clock. + SystemTime = CAP_SYS_TIME, + /// Allow configuration of tty devices. Allow `vhangup` of tty. + SystemTTYConfig = CAP_SYS_TTY_CONFIG, + /// Allow the privileged aspects of `mknod`. + MakeNode = CAP_MKNOD, + /// Allow taking of leases on files. + Lease = CAP_LEASE, + /// Allow writing the audit log via unicast netlink socket. + AuditWrite = CAP_AUDIT_WRITE, + /// Allow configuration of audit via unicast netlink socket. + AuditControl = CAP_AUDIT_CONTROL, + /// Set or remove capabilities on files. Map `uid=0` into a child user namespace. + SetFileCapabilities = CAP_SETFCAP, + /// Override MAC access. The base kernel enforces no MAC policy. An LSM may enforce a MAC + /// policy, and if it does and it chooses to implement capability based overrides of that + /// policy, this is the capability it should use to do so. + MACOverride = CAP_MAC_OVERRIDE, + /// Allow MAC configuration or state changes. The base kernel requires no MAC configuration. + /// An LSM may enforce a MAC policy, and if it does and it chooses to implement capability based + /// checks on modifications to that policy or the data required to maintain it, this is the + /// capability it should use to do so. + MACAdmin = CAP_MAC_ADMIN, + /// Allow configuring the kernel's `syslog` (`printk` behaviour). + SystemLog = CAP_SYSLOG, + /// Allow triggering something that will wake the system. + WakeAlarm = CAP_WAKE_ALARM, + /// Allow preventing system suspends. + BlockSuspend = CAP_BLOCK_SUSPEND, + /// Allow reading the audit log via multicast netlink socket. + AuditRead = CAP_AUDIT_READ, + /// Allow system performance and observability privileged operations using `perf_events`, + /// `i915_perf` and other kernel subsystems. + PerformanceMonitoring = CAP_PERFMON, + /// This capability allows the following BPF operations: + /// - Creating all types of BPF maps + /// - Advanced verifier features + /// - Indirect variable access + /// - Bounded loops + /// - BPF to BPF function calls + /// - Scalar precision tracking + /// - Larger complexity limits + /// - Dead code elimination + /// - And potentially other features + /// - Loading BPF Type Format (BTF) data + /// - Retrieve `xlated` and JITed code of BPF programs + /// - Use `bpf_spin_lock` helper + /// + /// [`Capability::PerformanceMonitoring`] relaxes the verifier checks further: + /// - BPF progs can use of pointer-to-integer conversions + /// - speculation attack hardening measures are bypassed + /// - `bpf_probe_read` to read arbitrary kernel memory is allowed + /// - `bpf_trace_printk` to print kernel memory is allowed + /// + /// [`Capability::SystemAdmin`] is required to use bpf_probe_write_user. + /// + /// [`Capability::SystemAdmin`] is required to iterate system wide loaded + /// programs, maps, links, BTFs and convert their IDs to file descriptors. + /// + /// [`Capability::PerformanceMonitoring`] and [`Capability::BerkeleyPacketFilters`] are required + /// to load tracing programs. + /// [`Capability::NetAdmin`] and [`Capability::BerkeleyPacketFilters`] are required to load + /// networking programs. + BerkeleyPacketFilters = CAP_BPF, + /// Allow checkpoint/restore related operations. Allow PID selection during `clone3`. + /// Allow writing to `ns_last_pid`. + CheckpointRestore = CAP_CHECKPOINT_RESTORE, +} + +/// Check if the specified capability is in the calling thread's capability bounding set. +/// +/// # References +/// - [`prctl(PR_CAPBSET_READ,...)`] +/// +/// [`prctl(PR_CAPBSET_READ,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn is_in_capability_bounding_set(capability: Capability) -> io::Result { + unsafe { prctl_2args(PR_CAPBSET_READ, capability as usize as *mut _) }.map(|r| r != 0) +} + +const PR_CAPBSET_DROP: c_int = 24; + +/// If the calling thread has the [`Capability::SetPermittedCapabilities`] capability within its +/// user namespace, then drop the specified capability from the thread's capability bounding set. +/// +/// # References +/// - [`prctl(PR_CAPBSET_DROP,...)`] +/// +/// [`prctl(PR_CAPBSET_DROP,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn remove_capability_from_capability_bounding_set(capability: Capability) -> io::Result<()> { + unsafe { prctl_2args(PR_CAPBSET_DROP, capability as usize as *mut _) }.map(|_r| ()) +} + +// +// PR_GET_SECUREBITS/PR_SET_SECUREBITS +// + +const PR_GET_SECUREBITS: c_int = 27; + +bitflags! { + /// `SECBIT_*`. + pub struct CapabilitiesSecureBits: u32 { + /// If this bit is set, then the kernel does not grant capabilities when + /// a `set-user-ID-root` program is executed, or when a process with an effective or real + /// UID of 0 calls `execve`. + const NO_ROOT = 1_u32 << 0; + /// Set [`NO_ROOT`] irreversibly. + const NO_ROOT_LOCKED = 1_u32 << 1; + /// Setting this flag stops the kernel from adjusting the process's permitted, effective, + /// and ambient capability sets when the thread's effective and filesystem UIDs are switched + /// between zero and nonzero values. + const NO_SETUID_FIXUP = 1_u32 << 2; + /// Set [`NO_SETUID_FIXUP`] irreversibly. + const NO_SETUID_FIXUP_LOCKED = 1_u32 << 3; + /// Setting this flag allows a thread that has one or more 0 UIDs to retain capabilities in + /// its permitted set when it switches all of its UIDs to nonzero values. + const KEEP_CAPS = 1_u32 << 4; + /// Set [`KEEP_CAPS`] irreversibly. + const KEEP_CAPS_LOCKED = 1_u32 << 5; + /// Setting this flag disallows raising ambient capabilities via the `prctl`'s + /// `PR_CAP_AMBIENT_RAISE` operation. + const NO_CAP_AMBIENT_RAISE = 1_u32 << 6; + /// Set [`NO_CAP_AMBIENT_RAISE`] irreversibly. + const NO_CAP_AMBIENT_RAISE_LOCKED = 1_u32 << 7; + } +} + +/// Get the `securebits` flags of the calling thread. +/// +/// # References +/// - [`prctl(PR_GET_SECUREBITS,...)`] +/// +/// [`prctl(PR_GET_SECUREBITS,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn capabilities_secure_bits() -> io::Result { + let r = unsafe { prctl_1arg(PR_GET_SECUREBITS)? } as c_uint; + CapabilitiesSecureBits::from_bits(r).ok_or(io::Errno::RANGE) +} + +const PR_SET_SECUREBITS: c_int = 28; + +/// Set the `securebits` flags of the calling thread. +/// +/// # References +/// - [`prctl(PR_SET_SECUREBITS,...)`] +/// +/// [`prctl(PR_SET_SECUREBITS,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn set_capabilities_secure_bits(bits: CapabilitiesSecureBits) -> io::Result<()> { + unsafe { prctl_2args(PR_SET_SECUREBITS, bits.bits() as usize as *mut _) }.map(|_r| ()) +} + +// +// PR_GET_TIMERSLACK/PR_SET_TIMERSLACK +// + +const PR_GET_TIMERSLACK: c_int = 30; + +/// Get the `current` timer slack value of the calling thread. +/// +/// # References +/// - [`prctl(PR_GET_TIMERSLACK,...)`] +/// +/// [`prctl(PR_GET_TIMERSLACK,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn current_timer_slack() -> io::Result { + unsafe { prctl_1arg(PR_GET_TIMERSLACK) }.map(|r| r as u64) +} + +const PR_SET_TIMERSLACK: c_int = 29; + +/// Sets the `current` timer slack value for the calling thread. +/// +/// # References +/// - [`prctl(PR_SET_TIMERSLACK,...)`] +/// +/// [`prctl(PR_SET_TIMERSLACK,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn set_current_timer_slack(value: Option) -> io::Result<()> { + let value = usize::try_from(value.map_or(0, NonZeroU64::get)).map_err(|_r| io::Errno::RANGE)?; + unsafe { prctl_2args(PR_SET_TIMERSLACK, value as *mut _) }.map(|_r| ()) +} + +// +// PR_GET_NO_NEW_PRIVS/PR_SET_NO_NEW_PRIVS +// + +const PR_GET_NO_NEW_PRIVS: c_int = 39; + +/// Get the value of the `no_new_privs` attribute for the calling thread. +/// +/// # References +/// - [`prctl(PR_GET_NO_NEW_PRIVS,...)`] +/// +/// [`prctl(PR_GET_NO_NEW_PRIVS,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn no_new_privs() -> io::Result { + unsafe { prctl_1arg(PR_GET_NO_NEW_PRIVS) }.map(|r| r != 0) +} + +const PR_SET_NO_NEW_PRIVS: c_int = 38; + +/// Set the calling thread's `no_new_privs` attribute. +/// +/// # References +/// - [`prctl(PR_SET_NO_NEW_PRIVS,...)`] +/// +/// [`prctl(PR_SET_NO_NEW_PRIVS,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn set_no_new_privs(no_new_privs: bool) -> io::Result<()> { + unsafe { prctl_2args(PR_SET_NO_NEW_PRIVS, no_new_privs as usize as *mut _) }.map(|_r| ()) +} + +// +// PR_GET_TID_ADDRESS +// + +const PR_GET_TID_ADDRESS: c_int = 40; + +/// Get the `clear_child_tid` address set by `set_tid_address` +/// and `clone`'s `CLONE_CHILD_CLEARTID` flag. +/// +/// # References +/// - [`prctl(PR_GET_TID_ADDRESS,...)`] +/// +/// [`prctl(PR_GET_TID_ADDRESS,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn get_clear_child_tid_address() -> io::Result>> { + unsafe { prctl_get_at_arg2_optional::<*mut c_void>(PR_GET_TID_ADDRESS) }.map(NonNull::new) +} + +// +// PR_GET_THP_DISABLE/PR_SET_THP_DISABLE +// + +const PR_GET_THP_DISABLE: c_int = 42; + +/// Get the current setting of the `THP disable` flag for the calling thread. +/// +/// # References +/// - [`prctl(PR_GET_THP_DISABLE,...)`] +/// +/// [`prctl(PR_GET_THP_DISABLE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn transparent_huge_pages_are_disabled() -> io::Result { + unsafe { prctl_1arg(PR_GET_THP_DISABLE) }.map(|r| r != 0) +} + +const PR_SET_THP_DISABLE: c_int = 41; + +/// Set the state of the `THP disable` flag for the calling thread. +/// +/// # References +/// - [`prctl(PR_SET_THP_DISABLE,...)`] +/// +/// [`prctl(PR_SET_THP_DISABLE,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn disable_transparent_huge_pages(thp_disable: bool) -> io::Result<()> { + unsafe { prctl_2args(PR_SET_THP_DISABLE, thp_disable as usize as *mut _) }.map(|_r| ()) +} + +// +// PR_CAP_AMBIENT +// + +const PR_CAP_AMBIENT: c_int = 47; + +const PR_CAP_AMBIENT_IS_SET: usize = 1; + +/// Check if the specified capability is in the ambient set. +/// +/// # References +/// - [`prctl(PR_CAP_AMBIENT,PR_CAP_AMBIENT_IS_SET,...)`] +/// +/// [`prctl(PR_CAP_AMBIENT,PR_CAP_AMBIENT_IS_SET,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn capability_is_in_ambient_capability_set(capability: Capability) -> io::Result { + let cap = capability as usize as *mut _; + unsafe { prctl_3args(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET as *mut _, cap) }.map(|r| r != 0) +} + +const PR_CAP_AMBIENT_CLEAR_ALL: usize = 4; + +/// Remove all capabilities from the ambient set. +/// +/// # References +/// - [`prctl(PR_CAP_AMBIENT,PR_CAP_AMBIENT_CLEAR_ALL,...)`] +/// +/// [`prctl(PR_CAP_AMBIENT,PR_CAP_AMBIENT_CLEAR_ALL,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn clear_ambient_capability_set() -> io::Result<()> { + unsafe { prctl_2args(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL as *mut _) }.map(|_r| ()) +} + +const PR_CAP_AMBIENT_RAISE: usize = 2; +const PR_CAP_AMBIENT_LOWER: usize = 3; + +/// Add or remove the specified capability to the ambient set. +/// +/// # References +/// - [`prctl(PR_CAP_AMBIENT,...)`] +/// +/// [`prctl(PR_CAP_AMBIENT,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn configure_capability_in_ambient_capability_set( + capability: Capability, + enable: bool, +) -> io::Result<()> { + let sub_operation = if enable { + PR_CAP_AMBIENT_RAISE + } else { + PR_CAP_AMBIENT_LOWER + }; + let cap = capability as usize as *mut _; + + unsafe { prctl_3args(PR_CAP_AMBIENT, sub_operation as *mut _, cap) }.map(|_r| ()) +} + +// +// PR_SVE_GET_VL/PR_SVE_SET_VL +// + +const PR_SVE_GET_VL: c_int = 51; + +const PR_SVE_VL_LEN_MASK: u32 = 0xffff; +const PR_SVE_VL_INHERIT: u32 = 1_u32 << 17; + +/// Scalable Vector Extension vector length configuration. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct SVEVectorLengthConfig { + /// Vector length in bytes. + pub vector_length_in_bytes: u32, + /// Vector length inherited across `execve`. + pub vector_length_inherited_across_execve: bool, +} + +/// Get the thread's current SVE vector length configuration. +/// +/// # References +/// - [`prctl(PR_SVE_GET_VL,...)`] +/// +/// [`prctl(PR_SVE_GET_VL,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn sve_vector_length_configuration() -> io::Result { + let bits = unsafe { prctl_1arg(PR_SVE_GET_VL)? } as c_uint; + Ok(SVEVectorLengthConfig { + vector_length_in_bytes: bits & PR_SVE_VL_LEN_MASK, + vector_length_inherited_across_execve: (bits & PR_SVE_VL_INHERIT) != 0, + }) +} + +const PR_SVE_SET_VL: c_int = 50; + +const PR_SVE_SET_VL_ONEXEC: u32 = 1_u32 << 18; + +/// Configure the thread's vector length of Scalable Vector Extension. +/// +/// # References +/// - [`prctl(PR_SVE_SET_VL,...)`] +/// +/// # Safety +/// +/// Please ensure the conditions necessary to safely call this function, +/// as detailed in the references above. +/// +/// [`prctl(PR_SVE_SET_VL,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub unsafe fn set_sve_vector_length_configuration( + vector_length_in_bytes: usize, + vector_length_inherited_across_execve: bool, + defer_change_to_next_execve: bool, +) -> io::Result<()> { + let vector_length_in_bytes = + u32::try_from(vector_length_in_bytes).map_err(|_r| io::Errno::RANGE)?; + + let mut bits = vector_length_in_bytes & PR_SVE_VL_LEN_MASK; + + if vector_length_inherited_across_execve { + bits |= PR_SVE_VL_INHERIT; + } + + if defer_change_to_next_execve { + bits |= PR_SVE_SET_VL_ONEXEC; + } + + prctl_2args(PR_SVE_SET_VL, bits as usize as *mut _).map(|_r| ()) +} + +// +// PR_PAC_RESET_KEYS +// + +const PR_PAC_RESET_KEYS: c_int = 54; + +/// Securely reset the thread's pointer authentication keys to fresh random values generated +/// by the kernel. +/// +/// # References +/// - [`prctl(PR_PAC_RESET_KEYS,...)`] +/// +/// # Safety +/// +/// Please ensure the conditions necessary to safely call this function, +/// as detailed in the references above. +/// +/// [`prctl(PR_PAC_RESET_KEYS,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub unsafe fn reset_pointer_authentication_keys( + keys: Option, +) -> io::Result<()> { + let keys = keys.as_ref().map_or(0_u32, PointerAuthenticationKeys::bits); + prctl_2args(PR_PAC_RESET_KEYS, keys as usize as *mut _).map(|_r| ()) +} + +// +// PR_GET_TAGGED_ADDR_CTRL/PR_SET_TAGGED_ADDR_CTRL +// + +const PR_GET_TAGGED_ADDR_CTRL: c_int = 56; + +const PR_MTE_TAG_SHIFT: u32 = 3; +const PR_MTE_TAG_MASK: u32 = 0xffff_u32 << PR_MTE_TAG_SHIFT; + +bitflags! { + /// Zero means addresses that are passed for the purpose of being dereferenced by the kernel must be untagged. + pub struct TaggedAddressMode: u32 { + /// Addresses that are passed for the purpose of being dereferenced by the kernel may be tagged. + const ENABLED = 1_u32 << 0; + /// Synchronous tag check fault mode. + const TCF_SYNC = 1_u32 << 1; + /// Asynchronous tag check fault mode. + const TCF_ASYNC = 1_u32 << 2; + } +} + +/// Get the current tagged address mode for the calling thread. +/// +/// # References +/// - [`prctl(PR_GET_TAGGED_ADDR_CTRL,...)`] +/// +/// [`prctl(PR_GET_TAGGED_ADDR_CTRL,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub fn current_tagged_address_mode() -> io::Result<(Option, u32)> { + let r = unsafe { prctl_1arg(PR_GET_TAGGED_ADDR_CTRL)? } as c_uint; + let mode = r & 0b111_u32; + let mte_tag = (r & PR_MTE_TAG_MASK) >> PR_MTE_TAG_SHIFT; + Ok((TaggedAddressMode::from_bits(mode), mte_tag)) +} + +const PR_SET_TAGGED_ADDR_CTRL: c_int = 55; + +/// Controls support for passing tagged user-space addresses to the kernel. +/// +/// # References +/// - [`prctl(PR_SET_TAGGED_ADDR_CTRL,...)`] +/// +/// # Safety +/// +/// Please ensure the conditions necessary to safely call this function, +/// as detailed in the references above. +/// +/// [`prctl(PR_SET_TAGGED_ADDR_CTRL,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub unsafe fn set_current_tagged_address_mode( + mode: Option, + mte_tag: u32, +) -> io::Result<()> { + let config = mode.as_ref().map_or(0_u32, TaggedAddressMode::bits) + | ((mte_tag << PR_MTE_TAG_SHIFT) & PR_MTE_TAG_MASK); + prctl_2args(PR_SET_TAGGED_ADDR_CTRL, config as usize as *mut _).map(|_r| ()) +} + +// +// PR_SET_SYSCALL_USER_DISPATCH +// + +const PR_SET_SYSCALL_USER_DISPATCH: c_int = 59; + +const PR_SYS_DISPATCH_OFF: usize = 0; + +/// Disable Syscall User Dispatch mechanism. +/// +/// # References +/// - [`prctl(PR_SET_SYSCALL_USER_DISPATCH,PR_SYS_DISPATCH_OFF,...)`] +/// +/// # Safety +/// +/// Please ensure the conditions necessary to safely call this function, +/// as detailed in the references above. +/// +/// [`prctl(PR_SET_SYSCALL_USER_DISPATCH,PR_SYS_DISPATCH_OFF,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub unsafe fn disable_syscall_user_dispatch() -> io::Result<()> { + prctl_2args(PR_SET_SYSCALL_USER_DISPATCH, PR_SYS_DISPATCH_OFF as *mut _).map(|_r| ()) +} + +const PR_SYS_DISPATCH_ON: usize = 1; + +/// Allow system calls to be executed. +const SYSCALL_DISPATCH_FILTER_ALLOW: u8 = 0; +/// Block system calls from executing. +const SYSCALL_DISPATCH_FILTER_BLOCK: u8 = 1; + +/// Value of the fast switch flag controlling system calls user dispatch mechanism without the need +/// to issue a syscall. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(u8)] +pub enum SysCallUserDispatchFastSwitch { + /// System calls are allowed to execute. + Allow = SYSCALL_DISPATCH_FILTER_ALLOW, + /// System calls are blocked from executing. + Block = SYSCALL_DISPATCH_FILTER_BLOCK, +} + +impl TryFrom for SysCallUserDispatchFastSwitch { + type Error = io::Errno; + + fn try_from(value: u8) -> Result { + match value { + SYSCALL_DISPATCH_FILTER_ALLOW => Ok(Self::Allow), + SYSCALL_DISPATCH_FILTER_BLOCK => Ok(Self::Block), + _ => Err(io::Errno::RANGE), + } + } +} + +/// Enable Syscall User Dispatch mechanism. +/// +/// # References +/// - [`prctl(PR_SET_SYSCALL_USER_DISPATCH,PR_SYS_DISPATCH_ON,...)`] +/// +/// # Safety +/// +/// Please ensure the conditions necessary to safely call this function, +/// as detailed in the references above. +/// +/// [`prctl(PR_SET_SYSCALL_USER_DISPATCH,PR_SYS_DISPATCH_ON,...)`]: https://man7.org/linux/man-pages/man2/prctl.2.html +#[inline] +pub unsafe fn enable_syscall_user_dispatch( + always_allowed_region: &[u8], + fast_switch_flag: &AtomicU8, +) -> io::Result<()> { + syscalls::prctl( + PR_SET_SYSCALL_USER_DISPATCH, + PR_SYS_DISPATCH_ON as *mut _, + always_allowed_region.as_ptr() as *mut _, + always_allowed_region.len() as *mut _, + fast_switch_flag as *const AtomicU8 as *mut _, + ) + .map(|_r| ()) +} + +// +// PR_SCHED_CORE +// + +const PR_SCHED_CORE: c_int = 62; + +const PR_SCHED_CORE_GET: usize = 0; + +const PR_SCHED_CORE_SCOPE_THREAD: u32 = 0; +const PR_SCHED_CORE_SCOPE_THREAD_GROUP: u32 = 1; +const PR_SCHED_CORE_SCOPE_PROCESS_GROUP: u32 = 2; + +/// `PR_SCHED_CORE_SCOPE_*`. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(u32)] +pub enum CoreSchedulingScope { + /// Operation will be performed for the thread. + Thread = PR_SCHED_CORE_SCOPE_THREAD, + /// Operation will be performed for all tasks in the task group of the process. + ThreadGroup = PR_SCHED_CORE_SCOPE_THREAD_GROUP, + /// Operation will be performed for all processes in the process group. + ProcessGroup = PR_SCHED_CORE_SCOPE_PROCESS_GROUP, +} + +impl TryFrom for CoreSchedulingScope { + type Error = io::Errno; + + fn try_from(value: u32) -> Result { + match value { + PR_SCHED_CORE_SCOPE_THREAD => Ok(Self::Thread), + PR_SCHED_CORE_SCOPE_THREAD_GROUP => Ok(Self::ThreadGroup), + PR_SCHED_CORE_SCOPE_PROCESS_GROUP => Ok(Self::ProcessGroup), + _ => Err(io::Errno::RANGE), + } + } +} + +/// Get core scheduling cookie of a process. +/// +/// # References +/// - [`prctl(PR_SCHED_CORE,PR_SCHED_CORE_GET,...)`] +/// +/// [`prctl(PR_SCHED_CORE,PR_SCHED_CORE_GET,...)`]: https://www.kernel.org/doc/html/v5.18/admin-guide/hw-vuln/core-scheduling.html +#[inline] +pub fn core_scheduling_cookie(pid: Pid, scope: CoreSchedulingScope) -> io::Result { + let mut value: MaybeUninit = MaybeUninit::uninit(); + unsafe { + syscalls::prctl( + PR_SCHED_CORE, + PR_SCHED_CORE_GET as *mut _, + pid.as_raw_nonzero().get() as usize as *mut _, + scope as usize as *mut _, + value.as_mut_ptr().cast(), + )?; + Ok(value.assume_init()) + } +} + +const PR_SCHED_CORE_CREATE: usize = 1; + +/// Create unique core scheduling cookie. +/// +/// # References +/// - [`prctl(PR_SCHED_CORE,PR_SCHED_CORE_CREATE,...)`] +/// +/// [`prctl(PR_SCHED_CORE,PR_SCHED_CORE_CREATE,...)`]: https://www.kernel.org/doc/html/v5.18/admin-guide/hw-vuln/core-scheduling.html +#[inline] +pub fn create_core_scheduling_cookie(pid: Pid, scope: CoreSchedulingScope) -> io::Result<()> { + unsafe { + syscalls::prctl( + PR_SCHED_CORE, + PR_SCHED_CORE_CREATE as *mut _, + pid.as_raw_nonzero().get() as usize as *mut _, + scope as usize as *mut _, + ptr::null_mut(), + ) + .map(|_r| ()) + } +} + +const PR_SCHED_CORE_SHARE_TO: usize = 2; + +/// Push core scheduling cookie to a process. +/// +/// # References +/// - [`prctl(PR_SCHED_CORE,PR_SCHED_CORE_SHARE_TO,...)`] +/// +/// [`prctl(PR_SCHED_CORE,PR_SCHED_CORE_SHARE_TO,...)`]: https://www.kernel.org/doc/html/v5.18/admin-guide/hw-vuln/core-scheduling.html +#[inline] +pub fn push_core_scheduling_cookie(pid: Pid, scope: CoreSchedulingScope) -> io::Result<()> { + unsafe { + syscalls::prctl( + PR_SCHED_CORE, + PR_SCHED_CORE_SHARE_TO as *mut _, + pid.as_raw_nonzero().get() as usize as *mut _, + scope as usize as *mut _, + ptr::null_mut(), + ) + .map(|_r| ()) + } +} + +const PR_SCHED_CORE_SHARE_FROM: usize = 3; + +/// Pull core scheduling cookie from a process. +/// +/// # References +/// - [`prctl(PR_SCHED_CORE,PR_SCHED_CORE_SHARE_FROM,...)`] +/// +/// [`prctl(PR_SCHED_CORE,PR_SCHED_CORE_SHARE_FROM,...)`]: https://www.kernel.org/doc/html/v5.18/admin-guide/hw-vuln/core-scheduling.html +#[inline] +pub fn pull_core_scheduling_cookie(pid: Pid, scope: CoreSchedulingScope) -> io::Result<()> { + unsafe { + syscalls::prctl( + PR_SCHED_CORE, + PR_SCHED_CORE_SHARE_FROM as *mut _, + pid.as_raw_nonzero().get() as usize as *mut _, + scope as usize as *mut _, + ptr::null_mut(), + ) + .map(|_r| ()) + } +} diff --git a/tests/process/main.rs b/tests/process/main.rs index 90aa34c5..ea9276b4 100644 --- a/tests/process/main.rs +++ b/tests/process/main.rs @@ -15,6 +15,8 @@ mod cpu_set; mod id; #[cfg(any(target_os = "android", target_os = "linux"))] mod membarrier; +#[cfg(any(target_os = "android", target_os = "linux"))] +mod prctl; #[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))] // WASI doesn't have [gs]etpriority. mod priority; #[cfg(not(any(target_os = "fuchsia", target_os = "redox", target_os = "wasi")))] diff --git a/tests/process/prctl.rs b/tests/process/prctl.rs new file mode 100644 index 00000000..50be36cf --- /dev/null +++ b/tests/process/prctl.rs @@ -0,0 +1,221 @@ +use std::io::{BufRead, Read}; +use std::os::raw::c_int; +use std::{fs, io}; + +use rustix::process::*; +use rustix::thread::Capability; + +#[test] +fn test_parent_process_death_signal() { + dbg!(parent_process_death_signal().unwrap()); +} + +#[test] +fn test_dumpable_behavior() { + dbg!(dumpable_behavior().unwrap()); +} + +#[test] +fn test_timing_method() { + dbg!(timing_method().unwrap()); +} + +#[test] +fn test_machine_check_memory_corruption_kill_policy() { + dbg!(machine_check_memory_corruption_kill_policy().unwrap()); +} + +#[cfg(any(target_arch = "x86"))] +#[test] +fn test_time_stamp_counter_readability() { + dbg!(time_stamp_counter_readability().unwrap()); +} + +#[cfg(any(target_arch = "powerpc"))] +#[test] +fn test_unaligned_access_control() { + dbg!(unaligned_access_control().unwrap()); +} + +#[cfg(any(target_arch = "powerpc"))] +#[test] +fn test_floating_point_exception_mode() { + dbg!(floating_point_exception_mode().unwrap()); +} + +#[cfg(any(target_arch = "powerpc"))] +#[test] +fn test_endian_mode() { + dbg!(endian_mode().unwrap()); +} + +#[cfg(any(target_arch = "mips"))] +#[test] +fn test_floating_point_mode() { + dbg!(floating_point_mode().unwrap()); +} + +#[cfg(any(target_arch = "aarch64"))] +#[test] +#[ignore = "Only on ARMv8.3 and later"] +fn test_enabled_pointer_authentication_keys() { + dbg!(enabled_pointer_authentication_keys().unwrap()); +} + +#[test] +#[ignore = "?"] +fn test_child_subreaper() { + dbg!(child_subreaper().unwrap()); +} + +#[test] +#[ignore = "?"] +fn test_speculative_feature_state() { + dbg!(speculative_feature_state(SpeculationFeature::SpeculativeStoreBypass).unwrap()); + dbg!(speculative_feature_state(SpeculationFeature::IndirectBranchSpeculation).unwrap()); + dbg!( + speculative_feature_state(SpeculationFeature::FlushL1DCacheOnContextSwitchOutOfTask) + .unwrap() + ); +} + +#[test] +fn test_is_io_flusher() { + if !thread_has_capability(Capability::SystemResource).unwrap() { + eprintln!("test_is_io_flusher: Test skipped due to missing capability: CAP_SYS_RESOURCE."); + return; + } + + dbg!(is_io_flusher().unwrap()); +} + +#[test] +fn test_virtual_memory_map_config_struct_size() { + if !thread_has_capability(Capability::SystemResource).unwrap() { + eprintln!( + "test_virtual_memory_map_config_struct_size: Test skipped due to missing capability: CAP_SYS_RESOURCE." + ); + return; + } + + if !linux_kernel_config_item_is_enabled("CONFIG_CHECKPOINT_RESTORE").unwrap_or(false) { + eprintln!("test_virtual_memory_map_config_struct_size: Test skipped due to missing kernel feature: CONFIG_CHECKPOINT_RESTORE."); + return; + } + + dbg!(virtual_memory_map_config_struct_size().unwrap()); +} + +#[test] +#[ignore = "Only on ia64"] +fn test_floating_point_emulation_control() { + dbg!(floating_point_emulation_control().unwrap()); +} + +/* + * Helper functions. + */ + +pub(crate) fn thread_has_capability(capability: Capability) -> io::Result { + const _LINUX_CAPABILITY_VERSION_3: u32 = 0x20080522; + + #[repr(C)] + struct cap_user_header_t { + version: u32, + pid: c_int, + } + + #[repr(C)] + struct cap_user_data_t { + effective: u32, + permitted: u32, + inheritable: u32, + } + + let header = cap_user_header_t { + version: _LINUX_CAPABILITY_VERSION_3, + pid: 0, + }; + + let mut data: [cap_user_data_t; 2] = [ + cap_user_data_t { + effective: 0, + permitted: 0, + inheritable: 0, + }, + cap_user_data_t { + effective: 0, + permitted: 0, + inheritable: 0, + }, + ]; + + let r = unsafe { + libc::syscall( + libc::SYS_capget, + &header as *const cap_user_header_t, + data.as_mut_ptr() as *mut cap_user_data_t, + ) + }; + + if r == -1 { + return Err(io::Error::last_os_error()); + } + + let cap_index = capability as u32; + let (data_index, cap_index) = if cap_index < 32 { + (0, cap_index) + } else { + (1, cap_index - 32) + }; + let flag = 1_u32 << cap_index; + Ok((flag & data[data_index].effective) != 0) +} + +fn load_linux_kernel_config() -> io::Result> { + if let Ok(compressed_bytes) = fs::read("/proc/config.gz") { + let mut decoder = flate2::bufread::GzDecoder::new(compressed_bytes.as_slice()); + let mut bytes = Vec::default(); + decoder.read_to_end(&mut bytes)?; + return Ok(bytes); + } + + let info = rustix::process::uname(); + let release = info + .release() + .to_str() + .map_err(|_r| io::Error::from(io::ErrorKind::InvalidData))?; + + fs::read(format!("/boot/config-{}", release)) +} + +fn is_linux_kernel_config_item_enabled(config: &[u8], name: &str) -> io::Result { + for line in io::Cursor::new(config).lines() { + let line = line?; + let line = line.trim(); + if line.is_empty() || line.starts_with('#') { + continue; + } + + let mut iter = line.splitn(2, '='); + if let Some(current_name) = iter.next().map(str::trim) { + if current_name == name { + if let Some(mut value) = iter.next().map(str::trim) { + if value.starts_with('"') && value.ends_with('"') { + // Just remove the quotes, but don't bother unescaping the inner string + // because we are only trying to find out if the option is an true boolean. + value = &value[1..(value.len() - 2)]; + } + + return Ok(value == "y" || value == "m"); + } + } + } + } + Ok(false) +} + +pub(crate) fn linux_kernel_config_item_is_enabled(name: &str) -> io::Result { + let config = load_linux_kernel_config()?; + is_linux_kernel_config_item_enabled(&config, name) +} diff --git a/tests/thread/main.rs b/tests/thread/main.rs index 0fc9b42c..db8238d8 100644 --- a/tests/thread/main.rs +++ b/tests/thread/main.rs @@ -7,3 +7,5 @@ mod clocks; #[cfg(any(target_os = "android", target_os = "linux"))] mod id; +#[cfg(any(target_os = "android", target_os = "linux"))] +mod prctl; diff --git a/tests/thread/prctl.rs b/tests/thread/prctl.rs new file mode 100644 index 00000000..4de8814a --- /dev/null +++ b/tests/thread/prctl.rs @@ -0,0 +1,158 @@ +use std::io::{BufRead, Read}; +use std::{fs, io}; + +use rustix::thread::*; + +#[test] +fn test_get_keep_capabilities() { + dbg!(get_keep_capabilities().unwrap()); +} + +#[test] +fn test_name() { + dbg!(name().unwrap()); +} + +#[test] +fn test_is_in_capability_bounding_set() { + dbg!(is_in_capability_bounding_set(Capability::ChangeOwnership).unwrap()); +} + +#[test] +fn test_capabilities_secure_bits() { + dbg!(capabilities_secure_bits().unwrap()); +} + +#[test] +fn test_current_timer_slack() { + dbg!(current_timer_slack().unwrap()); +} + +#[test] +fn test_no_new_privs() { + dbg!(no_new_privs().unwrap()); +} + +#[test] +fn test_capability_is_in_ambient_capability_set() { + dbg!(capability_is_in_ambient_capability_set(Capability::ChangeOwnership).unwrap()); +} + +#[cfg(any(target_arch = "aarch64"))] +#[test] +fn test_sve_vector_length_configuration() { + dbg!(sve_vector_length_configuration().unwrap()); +} + +#[cfg(any(target_arch = "aarch64"))] +#[test] +fn test_current_tagged_address_mode() { + dbg!(current_tagged_address_mode().unwrap()); +} + +#[test] +#[ignore = "?"] +fn test_transparent_huge_pages_are_disabled() { + dbg!(transparent_huge_pages_are_disabled().unwrap()); +} + +/* +#[test] +#[ignore = "Might result in SIGKILL"] +fn test_secure_computing_mode() { + if !linux_kernel_config_item_is_enabled("CONFIG_SECCOMP").unwrap_or(false) { + eprintln!("test_secure_computing_mode: Test skipped due to missing kernel feature: CONFIG_SECCOMP."); + return; + } + + dbg!(secure_computing_mode().unwrap()); +} +*/ + +#[test] +fn test_get_clear_child_tid_address() { + if !linux_kernel_config_item_is_enabled("CONFIG_CHECKPOINT_RESTORE").unwrap_or(false) { + eprintln!("test_get_clear_child_tid_address: Test skipped due to missing kernel feature: CONFIG_CHECKPOINT_RESTORE."); + return; + } + + match get_clear_child_tid_address() { + Ok(address) => println!("get_clear_child_tid_address = {:?}", address), + + Err(r) => { + let errno = r.raw_os_error(); + assert!(errno == libc::ENODEV || errno == libc::EINVAL); + eprintln!("test_get_clear_child_tid_address: Test unsupported: {}", r); + } + } +} + +#[test] +fn test_core_scheduling_cookie() { + if !linux_kernel_config_item_is_enabled("CONFIG_SCHED_CORE").unwrap_or(false) { + eprintln!("test_core_scheduling_cookie: Test skipped due to missing kernel feature: CONFIG_SCHED_CORE."); + return; + } + + match core_scheduling_cookie(rustix::thread::gettid(), CoreSchedulingScope::Thread) { + Ok(cookie) => println!("core_scheduling_cookie = {:?}", cookie), + + Err(r) => { + let errno = r.raw_os_error(); + assert!(errno == libc::ENODEV || errno == libc::EINVAL); + eprintln!("test_core_scheduling_cookie: Test unsupported: {}", r); + } + } +} + +/* + * Helper functions. + */ + +fn load_linux_kernel_config() -> io::Result> { + if let Ok(compressed_bytes) = fs::read("/proc/config.gz") { + let mut decoder = flate2::bufread::GzDecoder::new(compressed_bytes.as_slice()); + let mut bytes = Vec::default(); + decoder.read_to_end(&mut bytes)?; + return Ok(bytes); + } + + let info = rustix::process::uname(); + let release = info + .release() + .to_str() + .map_err(|_r| io::Error::from(io::ErrorKind::InvalidData))?; + + fs::read(format!("/boot/config-{}", release)) +} + +fn is_linux_kernel_config_item_enabled(config: &[u8], name: &str) -> io::Result { + for line in io::Cursor::new(config).lines() { + let line = line?; + let line = line.trim(); + if line.is_empty() || line.starts_with('#') { + continue; + } + + let mut iter = line.splitn(2, '='); + if let Some(current_name) = iter.next().map(str::trim) { + if current_name == name { + if let Some(mut value) = iter.next().map(str::trim) { + if value.starts_with('"') && value.ends_with('"') { + // Just remove the quotes, but don't bother unescaping the inner string + // because we are only trying to find out if the option is an true boolean. + value = &value[1..(value.len() - 2)]; + } + + return Ok(value == "y" || value == "m"); + } + } + } + } + Ok(false) +} + +pub(crate) fn linux_kernel_config_item_is_enabled(name: &str) -> io::Result { + let config = load_linux_kernel_config()?; + is_linux_kernel_config_item_enabled(&config, name) +}