From 91296dbd7b14afa8d74fe116a1aa9be939763f5c Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 24 Oct 2026 20:45:04 +0200 Subject: [PATCH] Use 32-bit UID/GID syscalls on arm, sparc, and x86 On 32-bit Linux arm, sparc, and x86, the unsuffixed getgroups, setgroups, setuid, setgid, chown, and fchown syscalls take 16-bit UIDs and GIDs, while rustix uses 32-bit Uid and Gid values. Calling the 16-bit syscalls truncates UIDs and GIDs at 65536 and causes getgroups and setgroups_thread to read and write pairs of 16-bit GIDs in each 32-bit Gid slot. Use the *32 syscall variants for getgroups, setuid_thread, setgid_thread, setgroups_thread, chown, and fchown on arm, sparc, and x86 in both the linux_raw and libc backends, matching getuid and setresuid_thread. Add a getgroups test comparing against libc. --- src/backend/libc/fs/syscalls.rs | 7 +++++- src/backend/libc/thread/syscalls.rs | 21 +++++++++++++++--- src/backend/linux_raw/fs/syscalls.rs | 25 ++++++++++++++++++++- src/backend/linux_raw/process/syscalls.rs | 9 ++++++++ src/backend/linux_raw/thread/syscalls.rs | 27 ++++++++++++++++++++--- tests/process/id.rs | 17 ++++++++++++++ 6 files changed, 98 insertions(+), 8 deletions(-) diff --git a/src/backend/libc/fs/syscalls.rs b/src/backend/libc/fs/syscalls.rs index 07000f3dc..817921146 100644 --- a/src/backend/libc/fs/syscalls.rs +++ b/src/backend/libc/fs/syscalls.rs @@ -1478,12 +1478,17 @@ pub(crate) fn fchown(fd: BorrowedFd<'_>, owner: Option, group: Option) // implementations, such as musl, add extra logic to `fchown` to emulate // support for `O_PATH`, which uses `/proc` outside our control and // interferes with our own use of `O_PATH`. + #[cfg(any(target_arch = "x86", target_arch = "arm", target_arch = "sparc"))] + const SYS: c::c_long = c::SYS_fchown32 as c::c_long; + #[cfg(not(any(target_arch = "x86", target_arch = "arm", target_arch = "sparc")))] + const SYS: c::c_long = c::SYS_fchown as c::c_long; + syscall! { fn fchown( fd: c::c_int, owner: c::uid_t, group: c::gid_t - ) via SYS_fchown -> c::c_int + ) via SYS -> c::c_int } unsafe { let (ow, gr) = crate::ugid::translate_fchown_args(owner, group); diff --git a/src/backend/libc/thread/syscalls.rs b/src/backend/libc/thread/syscalls.rs index 81f60219a..328017af4 100644 --- a/src/backend/libc/thread/syscalls.rs +++ b/src/backend/libc/thread/syscalls.rs @@ -389,8 +389,13 @@ pub(crate) fn capset( #[cfg(linux_kernel)] #[inline] pub(crate) fn setuid_thread(uid: crate::ugid::Uid) -> io::Result<()> { + #[cfg(any(target_arch = "x86", target_arch = "arm", target_arch = "sparc"))] + const SYS: c::c_long = c::SYS_setuid32 as c::c_long; + #[cfg(not(any(target_arch = "x86", target_arch = "arm", target_arch = "sparc")))] + const SYS: c::c_long = c::SYS_setuid as c::c_long; + syscall! { - fn setuid(uid: c::uid_t) via SYS_setuid -> c::c_int + fn setuid(uid: c::uid_t) via SYS -> c::c_int } unsafe { ret(setuid(uid.as_raw())) } @@ -424,8 +429,13 @@ pub(crate) fn setresuid_thread( #[cfg(linux_kernel)] #[inline] pub(crate) fn setgid_thread(gid: crate::ugid::Gid) -> io::Result<()> { + #[cfg(any(target_arch = "x86", target_arch = "arm", target_arch = "sparc"))] + const SYS: c::c_long = c::SYS_setgid32 as c::c_long; + #[cfg(not(any(target_arch = "x86", target_arch = "arm", target_arch = "sparc")))] + const SYS: c::c_long = c::SYS_setgid as c::c_long; + syscall! { - fn setgid(gid: c::gid_t) via SYS_setgid -> c::c_int + fn setgid(gid: c::gid_t) via SYS -> c::c_int } unsafe { ret(setgid(gid.as_raw())) } @@ -696,8 +706,13 @@ pub(crate) fn futex_waitv( #[cfg(linux_kernel)] #[inline] pub(crate) fn setgroups_thread(groups: &[crate::ugid::Gid]) -> io::Result<()> { + #[cfg(any(target_arch = "x86", target_arch = "arm", target_arch = "sparc"))] + const SYS: c::c_long = c::SYS_setgroups32 as c::c_long; + #[cfg(not(any(target_arch = "x86", target_arch = "arm", target_arch = "sparc")))] + const SYS: c::c_long = c::SYS_setgroups as c::c_long; + syscall! { - fn setgroups(size: c::size_t, list: *const c::gid_t) via SYS_setgroups -> c::c_int + fn setgroups(size: c::size_t, list: *const c::gid_t) via SYS -> c::c_int } ret(unsafe { setgroups(groups.len(), groups.as_ptr().cast()) }) } diff --git a/src/backend/linux_raw/fs/syscalls.rs b/src/backend/linux_raw/fs/syscalls.rs index 297782d85..149ee078a 100644 --- a/src/backend/linux_raw/fs/syscalls.rs +++ b/src/backend/linux_raw/fs/syscalls.rs @@ -164,8 +164,25 @@ pub(crate) fn chownat( #[inline] pub(crate) fn chown(path: &CStr, owner: Option, group: Option) -> io::Result<()> { + #[cfg(any(target_arch = "arm", target_arch = "sparc", target_arch = "x86"))] + unsafe { + let (ow, gr) = crate::ugid::translate_fchown_args(owner, group); + ret(syscall_readonly!( + __NR_chown32, + path, + c_uint(ow), + c_uint(gr) + )) + } + // Most architectures have a `chown` syscall. - #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))] + #[cfg(not(any( + target_arch = "aarch64", + target_arch = "arm", + target_arch = "riscv64", + target_arch = "sparc", + target_arch = "x86" + )))] unsafe { let (ow, gr) = crate::ugid::translate_fchown_args(owner, group); ret(syscall_readonly!(__NR_chown, path, c_uint(ow), c_uint(gr))) @@ -188,6 +205,12 @@ pub(crate) fn chown(path: &CStr, owner: Option, group: Option) -> io:: #[inline] pub(crate) fn fchown(fd: BorrowedFd<'_>, owner: Option, group: Option) -> io::Result<()> { + #[cfg(any(target_arch = "arm", target_arch = "sparc", target_arch = "x86"))] + unsafe { + let (ow, gr) = crate::ugid::translate_fchown_args(owner, group); + ret(syscall_readonly!(__NR_fchown32, fd, c_uint(ow), c_uint(gr))) + } + #[cfg(not(any(target_arch = "arm", target_arch = "sparc", target_arch = "x86")))] unsafe { let (ow, gr) = crate::ugid::translate_fchown_args(owner, group); ret(syscall_readonly!(__NR_fchown, fd, c_uint(ow), c_uint(gr))) diff --git a/src/backend/linux_raw/process/syscalls.rs b/src/backend/linux_raw/process/syscalls.rs index c9c1fd824..109958c10 100644 --- a/src/backend/linux_raw/process/syscalls.rs +++ b/src/backend/linux_raw/process/syscalls.rs @@ -519,6 +519,15 @@ pub(crate) fn pivot_root(new_root: &CStr, put_old: &CStr) -> io::Result<()> { pub(crate) fn getgroups(buf: &mut [Gid]) -> io::Result { let len = buf.len().try_into().map_err(|_| io::Errno::NOMEM)?; + #[cfg(any(target_arch = "arm", target_arch = "sparc", target_arch = "x86"))] + unsafe { + ret_usize(syscall!( + __NR_getgroups32, + c_int(len), + slice_just_addr_mut(buf) + )) + } + #[cfg(not(any(target_arch = "arm", target_arch = "sparc", target_arch = "x86")))] unsafe { ret_usize(syscall!( __NR_getgroups, diff --git a/src/backend/linux_raw/thread/syscalls.rs b/src/backend/linux_raw/thread/syscalls.rs index c22e74a9f..5893cdf39 100644 --- a/src/backend/linux_raw/thread/syscalls.rs +++ b/src/backend/linux_raw/thread/syscalls.rs @@ -400,7 +400,14 @@ pub(crate) fn capset( #[inline] pub(crate) fn setuid_thread(uid: crate::ugid::Uid) -> io::Result<()> { - unsafe { ret(syscall_readonly!(__NR_setuid, uid)) } + #[cfg(any(target_arch = "x86", target_arch = "arm", target_arch = "sparc"))] + unsafe { + ret(syscall_readonly!(__NR_setuid32, uid)) + } + #[cfg(not(any(target_arch = "x86", target_arch = "arm", target_arch = "sparc")))] + unsafe { + ret(syscall_readonly!(__NR_setuid, uid)) + } } #[inline] @@ -421,7 +428,14 @@ pub(crate) fn setresuid_thread( #[inline] pub(crate) fn setgid_thread(gid: crate::ugid::Gid) -> io::Result<()> { - unsafe { ret(syscall_readonly!(__NR_setgid, gid)) } + #[cfg(any(target_arch = "x86", target_arch = "arm", target_arch = "sparc"))] + unsafe { + ret(syscall_readonly!(__NR_setgid32, gid)) + } + #[cfg(not(any(target_arch = "x86", target_arch = "arm", target_arch = "sparc")))] + unsafe { + ret(syscall_readonly!(__NR_setgid, gid)) + } } #[inline] @@ -443,7 +457,14 @@ pub(crate) fn setresgid_thread( #[inline] pub(crate) fn setgroups_thread(gids: &[crate::ugid::Gid]) -> io::Result<()> { let (addr, len) = slice(gids); - unsafe { ret(syscall_readonly!(__NR_setgroups, len, addr)) } + #[cfg(any(target_arch = "x86", target_arch = "arm", target_arch = "sparc"))] + unsafe { + ret(syscall_readonly!(__NR_setgroups32, len, addr)) + } + #[cfg(not(any(target_arch = "x86", target_arch = "arm", target_arch = "sparc")))] + unsafe { + ret(syscall_readonly!(__NR_setgroups, len, addr)) + } } // `sched_getcpu` has special optimizations via the vDSO on some architectures. diff --git a/tests/process/id.rs b/tests/process/id.rs index ff106f64d..136485e81 100644 --- a/tests/process/id.rs +++ b/tests/process/id.rs @@ -36,6 +36,23 @@ fn test_getegid() { } } +#[cfg(feature = "alloc")] +#[test] +fn test_getgroups() { + let groups = process::getgroups().unwrap(); + assert_eq!(groups, process::getgroups().unwrap()); + unsafe { + let ngroups = libc::getgroups(0, core::ptr::null_mut()); + assert!(ngroups >= 0); + let mut libc_groups = vec![0 as libc::gid_t; ngroups as usize]; + if ngroups > 0 { + assert_eq!(libc::getgroups(ngroups, libc_groups.as_mut_ptr()), ngroups); + } + let raw_groups: Vec = groups.iter().map(|g| g.as_raw()).collect(); + assert_eq!(raw_groups, libc_groups); + } +} + #[test] fn test_getpid() { assert_eq!(process::getpid(), process::getpid());