#![allow(clippy::needless_return)]
use crate::utils::zwarnnam;
#[cfg(unix)]
pub unsafe fn setresgid(rgid: libc::gid_t, egid: libc::gid_t, sgid: libc::gid_t) -> libc::c_int {
let mut ret: libc::c_int = 0;
let mut saved_errno: libc::c_int;
if rgid != sgid {
errno_set(libc::ENOSYS);
return -1;
}
if have_native_setregid() && !broken_setregid() {
if libc::setregid(rgid, egid) < 0 {
saved_errno = errno_get();
zwarnnam(
"setregid",
&format!("to gid {}: {}", rgid as i64, errno_str(saved_errno)),
);
errno_set(saved_errno);
ret = -1;
}
} else {
if libc::setegid(egid) < 0 {
saved_errno = errno_get();
zwarnnam(
"setegid",
&format!("to gid {}: {}", egid as i64, errno_str(saved_errno)),
);
errno_set(saved_errno);
ret = -1;
}
if libc::setgid(rgid) < 0 {
saved_errno = errno_get();
zwarnnam(
"setgid",
&format!("to gid {}: {}", rgid as i64, errno_str(saved_errno)),
);
errno_set(saved_errno);
ret = -1;
}
}
ret
}
#[cfg(unix)]
pub unsafe fn setresuid(ruid: libc::uid_t, euid: libc::uid_t, suid: libc::uid_t) -> libc::c_int {
let mut ret: libc::c_int = 0;
let mut saved_errno: libc::c_int;
if ruid != suid {
errno_set(libc::ENOSYS);
return -1;
}
if have_native_setreuid() && !broken_setreuid() {
if libc::setreuid(ruid, euid) < 0 {
saved_errno = errno_get();
zwarnnam(
"setreuid",
&format!("to uid {}: {}", ruid as i64, errno_str(saved_errno)),
);
errno_set(saved_errno);
ret = -1;
}
} else {
if !seteuid_breaks_setuid() {
if libc::seteuid(euid) < 0 {
saved_errno = errno_get();
zwarnnam(
"seteuid",
&format!("to uid {}: {}", euid as i64, errno_str(saved_errno)),
);
errno_set(saved_errno);
ret = -1;
}
}
if libc::setuid(ruid) < 0 {
saved_errno = errno_get();
zwarnnam(
"setuid",
&format!("to uid {}: {}", ruid as i64, errno_str(saved_errno)),
);
errno_set(saved_errno);
ret = -1;
}
}
ret
}
#[inline]
const fn broken_setregid() -> bool {
cfg!(target_os = "netbsd")
}
#[inline]
const fn broken_setreuid() -> bool {
cfg!(target_os = "netbsd")
}
#[inline]
const fn have_native_setregid() -> bool {
cfg!(unix)
}
#[inline]
const fn have_native_setreuid() -> bool {
cfg!(unix)
}
#[inline]
const fn seteuid_breaks_setuid() -> bool {
false
}
#[cfg(unix)]
#[inline]
fn errno_get() -> libc::c_int {
std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
}
#[cfg(unix)]
#[inline]
fn errno_set(e: libc::c_int) {
unsafe {
let p: *mut libc::c_int = {
#[cfg(any(target_os = "linux", target_os = "android"))]
{
libc::__errno_location()
}
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "dragonfly"
))]
{
libc::__error()
}
#[cfg(any(target_os = "openbsd", target_os = "netbsd"))]
{
extern "C" {
fn __errno() -> *mut libc::c_int;
}
__errno()
}
#[cfg(not(any(
target_os = "linux",
target_os = "android",
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "dragonfly",
target_os = "openbsd",
target_os = "netbsd",
)))]
{
thread_local! {
static ERRNO: UnsafeCell<libc::c_int> = const { UnsafeCell::new(0) };
}
ERRNO.with(|c| c.get())
}
};
*p = e;
}
}
#[cfg(unix)]
#[inline]
fn errno_str(e: libc::c_int) -> String {
std::io::Error::from_raw_os_error(e).to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(unix)]
fn setresgid_rejects_split_real_saved() {
let _g = crate::test_util::global_state_lock();
unsafe {
let r = setresgid(1, 2, 3);
assert_eq!(r, -1);
assert_eq!(errno_get(), libc::ENOSYS);
}
}
#[test]
#[cfg(unix)]
fn setresuid_rejects_split_real_saved() {
let _g = crate::test_util::global_state_lock();
unsafe {
let r = setresuid(1, 2, 3);
assert_eq!(r, -1);
assert_eq!(errno_get(), libc::ENOSYS);
}
}
#[test]
#[cfg(unix)]
fn setresuid_noop_succeeds() {
let _g = crate::test_util::global_state_lock();
unsafe {
let me = libc::getuid();
let r = setresuid(me, me, me);
assert_eq!(r, 0);
}
}
#[test]
#[cfg(unix)]
fn setresgid_noop_succeeds() {
let _g = crate::test_util::global_state_lock();
unsafe {
let me = libc::getgid();
let r = setresgid(me, me, me);
assert_eq!(r, 0);
}
}
#[test]
#[cfg(unix)]
fn setresgid_noop_does_not_clobber_existing_errno() {
let _g = crate::test_util::global_state_lock();
unsafe {
errno_set(libc::EILSEQ);
let me = libc::getgid();
let r = setresgid(me, me, me);
assert_eq!(r, 0);
assert_eq!(
errno_get(),
libc::EILSEQ,
"no-op short-circuit must not reset errno on success"
);
}
}
#[test]
#[cfg(unix)]
fn setresgid_real_matches_effective_but_saved_differs() {
let _g = crate::test_util::global_state_lock();
unsafe {
let me = libc::getgid();
let other: libc::gid_t = if me == 0 { 1 } else { 0 };
let r = setresgid(me, me, other);
assert_eq!(r, -1);
assert_eq!(errno_get(), libc::ENOSYS);
}
}
#[test]
#[cfg(unix)]
fn setresuid_effective_differs_from_saved_does_not_get_enosys() {
let _g = crate::test_util::global_state_lock();
unsafe {
let me = libc::getuid();
if me == 0 {
return;
}
let r = setresuid(me, 0, me);
assert_eq!(r, -1, "non-root cannot seteuid(0)");
assert_ne!(
errno_get(),
libc::ENOSYS,
"ENOSYS is reserved for the c:91 ruid!=suid pre-check"
);
}
}
#[test]
fn errno_str_returns_nonempty_for_zero() {
let _g = crate::test_util::global_state_lock();
let s = errno_str(0);
assert!(!s.is_empty(), "errno_str(0) returned empty string");
}
#[test]
fn errno_str_for_einval_contains_recognizable_phrase() {
let _g = crate::test_util::global_state_lock();
let s = errno_str(libc::EINVAL);
let l = s.to_lowercase();
assert!(
l.contains("invalid") || l.contains("argument") || l.contains("inval"),
"errno_str(EINVAL) = {:?} — must contain readable text",
s
);
}
#[test]
fn errno_str_eacces_returns_readable_text() {
let _g = crate::test_util::global_state_lock();
let s = errno_str(libc::EACCES);
assert!(!s.is_empty(), "EACCES must format to non-empty");
assert!(!s.contains("{}"), "format placeholder must be expanded");
}
#[test]
fn errno_str_eperm_returns_readable_text() {
let _g = crate::test_util::global_state_lock();
let s = errno_str(libc::EPERM);
assert!(!s.is_empty());
}
#[test]
fn errno_str_zero_returns_nonempty() {
let _g = crate::test_util::global_state_lock();
let s = errno_str(0);
assert!(!s.is_empty());
}
#[test]
fn errno_str_is_deterministic() {
let _g = crate::test_util::global_state_lock();
for e in [0, libc::EACCES, libc::EPERM, libc::EINVAL, libc::ENOMEM] {
let first = errno_str(e);
for _ in 0..5 {
assert_eq!(errno_str(e), first, "errno {} must be pure", e);
}
}
}
#[test]
fn errno_str_negative_errno_no_panic() {
let _g = crate::test_util::global_state_lock();
let s = errno_str(-1);
assert!(!s.is_empty());
}
#[test]
fn build_config_const_fns_are_callable() {
let _br: bool = broken_setregid();
let _bu: bool = broken_setreuid();
let _hr: bool = have_native_setregid();
let _hu: bool = have_native_setreuid();
let _sb: bool = seteuid_breaks_setuid();
}
#[cfg(unix)]
#[test]
fn setresgid_returns_c_int_type() {
let _g = crate::test_util::global_state_lock();
let _: libc::c_int = unsafe {
let gid = libc::getgid();
setresgid(gid, gid, gid)
};
}
#[cfg(unix)]
#[test]
fn setresuid_returns_c_int_type() {
let _g = crate::test_util::global_state_lock();
let _: libc::c_int = unsafe {
let uid = libc::getuid();
setresuid(uid, uid, uid)
};
}
#[cfg(unix)]
#[test]
fn setresgid_split_rgid_sgid_sets_enosys() {
let _g = crate::test_util::global_state_lock();
let r = unsafe { setresgid(0, 1, 999) };
assert_eq!(r, -1, "rgid != sgid → -1");
let e = errno_get();
assert_eq!(e, libc::ENOSYS, "rgid != sgid sets errno=ENOSYS");
}
#[cfg(unix)]
#[test]
fn setresuid_split_ruid_suid_sets_enosys() {
let _g = crate::test_util::global_state_lock();
let r = unsafe { setresuid(0, 1, 999) };
assert_eq!(r, -1, "ruid != suid → -1");
let e = errno_get();
assert_eq!(e, libc::ENOSYS, "ruid != suid sets errno=ENOSYS");
}
#[test]
fn build_config_flags_deterministic() {
for _ in 0..5 {
assert_eq!(broken_setregid(), broken_setregid());
assert_eq!(broken_setreuid(), broken_setreuid());
assert_eq!(have_native_setregid(), have_native_setregid());
assert_eq!(have_native_setreuid(), have_native_setreuid());
assert_eq!(seteuid_breaks_setuid(), seteuid_breaks_setuid());
}
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
#[test]
fn modern_platforms_have_native_set_res_id() {
assert!(
have_native_setregid(),
"modern target should have native setregid"
);
assert!(
have_native_setreuid(),
"modern target should have native setreuid"
);
}
#[cfg(unix)]
#[test]
fn errno_str_full_sweep_deterministic() {
for e in [
0,
libc::EPERM,
libc::ENOENT,
libc::EACCES,
libc::EINVAL,
99999,
] {
let first = errno_str(e);
for _ in 0..3 {
assert_eq!(
errno_str(e),
first,
"errno_str({}) must be deterministic",
e
);
}
}
}
#[cfg(unix)]
#[test]
fn errno_str_common_codes_non_empty() {
for e in [
libc::EPERM,
libc::ENOENT,
libc::EACCES,
libc::EINVAL,
libc::EBUSY,
libc::EIO,
] {
assert!(
!errno_str(e).is_empty(),
"errno_str({}) must return non-empty",
e
);
}
}
#[cfg(unix)]
#[test]
fn errno_get_set_round_trip() {
let _g = crate::test_util::global_state_lock();
let saved = errno_get();
errno_set(libc::EACCES);
assert_eq!(errno_get(), libc::EACCES, "errno round-trips");
errno_set(saved);
}
#[cfg(unix)]
#[test]
fn setresgid_all_equal_current_gid_is_noop() {
let _g = crate::test_util::global_state_lock();
let g = unsafe { libc::getgid() };
let r = unsafe { setresgid(g, g, g) };
assert_eq!(r, 0, "current gid all-equal triple is a no-op success");
}
#[cfg(unix)]
#[test]
fn setresuid_all_equal_current_uid_is_noop() {
let _g = crate::test_util::global_state_lock();
let u = unsafe { libc::getuid() };
let r = unsafe { setresuid(u, u, u) };
assert_eq!(r, 0, "current uid all-equal triple is a no-op success");
}
#[cfg(unix)]
#[test]
fn setresgid_returns_c_int_pin_alt() {
let _g = crate::test_util::global_state_lock();
let _: libc::c_int = unsafe { setresgid(0, 1, 999) };
}
#[cfg(unix)]
#[test]
fn setresuid_returns_c_int_pin_alt() {
let _g = crate::test_util::global_state_lock();
let _: libc::c_int = unsafe { setresuid(0, 1, 999) };
}
#[test]
fn build_config_flags_return_bool_type() {
let _: bool = broken_setregid();
let _: bool = broken_setreuid();
let _: bool = have_native_setregid();
let _: bool = have_native_setreuid();
let _: bool = seteuid_breaks_setuid();
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
#[test]
fn broken_and_native_mutually_exclusive_setregid() {
if have_native_setregid() {
assert!(
!broken_setregid(),
"having native setregid implies not broken"
);
}
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
#[test]
fn broken_and_native_mutually_exclusive_setreuid() {
if have_native_setreuid() {
assert!(
!broken_setreuid(),
"having native setreuid implies not broken"
);
}
}
#[cfg(unix)]
#[test]
fn errno_str_returns_string_type() {
let _: String = errno_str(0);
}
#[cfg(unix)]
#[test]
fn errno_str_zero_returns_non_empty() {
let s = errno_str(0);
assert!(!s.is_empty(), "errno_str(0) must be non-empty");
}
#[cfg(unix)]
#[test]
fn errno_get_returns_c_int_type() {
let _g = crate::test_util::global_state_lock();
let _: libc::c_int = errno_get();
}
#[cfg(unix)]
#[test]
fn errno_set_returns_void_type() {
let _g = crate::test_util::global_state_lock();
let saved = errno_get();
let _: () = errno_set(0);
errno_set(saved);
}
#[cfg(unix)]
#[test]
fn setresgid_rgid_neq_sgid_sets_errno_enosys() {
let _g = crate::test_util::global_state_lock();
let saved = errno_get();
errno_set(0);
let r = unsafe { setresgid(0 as libc::gid_t, 0 as libc::gid_t, 99 as libc::gid_t) };
assert_eq!(r, -1, "rgid != sgid must return -1");
assert_eq!(
errno_get(),
libc::ENOSYS,
"errno must be set to ENOSYS per c:70"
);
errno_set(saved);
}
#[cfg(unix)]
#[test]
fn setresuid_ruid_neq_suid_sets_errno_enosys() {
let _g = crate::test_util::global_state_lock();
let saved = errno_get();
errno_set(0);
let r = unsafe { setresuid(0 as libc::uid_t, 0 as libc::uid_t, 99 as libc::uid_t) };
assert_eq!(r, -1, "ruid != suid must return -1");
assert_eq!(
errno_get(),
libc::ENOSYS,
"errno must be set to ENOSYS per c:104"
);
errno_set(saved);
}
#[cfg(unix)]
#[test]
fn config_flags_at_least_one_path_viable_setregid() {
let usable = (have_native_setregid() && !broken_setregid())
|| (!have_native_setregid() || broken_setregid());
assert!(usable, "at least one execution path must be viable");
}
#[cfg(unix)]
#[test]
fn config_flags_at_least_one_path_viable_setreuid() {
let usable = (have_native_setreuid() && !broken_setreuid())
|| (!have_native_setreuid() || broken_setreuid());
assert!(usable, "at least one execution path must be viable");
}
#[cfg(unix)]
#[test]
fn errno_str_enosys_non_empty_deterministic() {
let _g = crate::test_util::global_state_lock();
let a = errno_str(libc::ENOSYS);
let b = errno_str(libc::ENOSYS);
assert!(!a.is_empty(), "errno_str(ENOSYS) must not be empty");
assert_eq!(a, b, "errno_str(ENOSYS) must be pure");
}
#[cfg(unix)]
#[test]
fn errno_str_invalid_code_no_panic() {
let _g = crate::test_util::global_state_lock();
let _ = errno_str(-1);
let _ = errno_str(99999);
}
#[cfg(unix)]
#[test]
fn errno_get_set_round_trip_all_common_codes() {
let _g = crate::test_util::global_state_lock();
let saved = errno_get();
for code in [
0,
libc::EAGAIN,
libc::ENOSYS,
libc::EINVAL,
libc::EACCES,
libc::EPERM,
libc::ENOMEM,
] {
errno_set(code);
assert_eq!(errno_get(), code, "errno round-trip must preserve {}", code);
}
errno_set(saved);
}
#[cfg(unix)]
#[test]
fn setresgid_early_return_does_not_alter_gid() {
let _g = crate::test_util::global_state_lock();
let saved = errno_get();
let before = unsafe { libc::getgid() };
let _ = unsafe { setresgid(0 as libc::gid_t, 0 as libc::gid_t, 99 as libc::gid_t) };
let after = unsafe { libc::getgid() };
assert_eq!(before, after, "early-return must not mutate gid");
errno_set(saved);
}
#[cfg(unix)]
#[test]
fn setresuid_early_return_does_not_alter_uid() {
let _g = crate::test_util::global_state_lock();
let saved = errno_get();
let before = unsafe { libc::getuid() };
let _ = unsafe { setresuid(0 as libc::uid_t, 0 as libc::uid_t, 99 as libc::uid_t) };
let after = unsafe { libc::getuid() };
assert_eq!(before, after, "early-return must not mutate uid");
errno_set(saved);
}
#[cfg(unix)]
#[test]
fn broken_flags_are_const_fns() {
const _BR: bool = broken_setregid();
const _BU: bool = broken_setreuid();
const _HR: bool = have_native_setregid();
const _HU: bool = have_native_setreuid();
}
#[cfg(unix)]
#[test]
fn setresgid_equal_args_path_not_enosys() {
let _g = crate::test_util::global_state_lock();
let saved = errno_get();
errno_set(0);
let cur = unsafe { libc::getgid() };
let _ = unsafe { setresgid(cur, cur, cur) };
let err = errno_get();
assert!(
err != libc::ENOSYS,
"equal args must NOT trigger ENOSYS branch; got errno={}",
err
);
errno_set(saved);
}
}