use caps::Capability::{CAP_SETGID, CAP_SETUID};
use libseccomp::ScmpNotifResp;
use nix::{
errno::Errno,
unistd::{getresgid, getresuid, Gid, Uid},
};
use crate::{
caps,
compat::setgroups_none,
config::{GID_MIN, NGROUPS_MAX, UID_MIN},
confine::{is_valid_ptr, safe_drop_cap, scmp_arch_has_uid16},
kernel::{to_id16, to_id16_val},
req::UNotifyEventRequest,
warn,
};
pub(crate) fn sys_setuid(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_handler!(request, |request: UNotifyEventRequest| {
let req = request.scmpreq;
let arg0 = if scmp_arch_has_uid16(req.data.arch) {
to_id16_val(req.data.args[0])?
} else {
req.data.args[0]
};
#[expect(clippy::cast_possible_truncation)]
let target_uid = Uid::from_raw(arg0 as u32);
syscall_setuid_handler(&request, target_uid)
})
}
pub(crate) fn sys_setuid32(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_handler!(request, |request: UNotifyEventRequest| {
let req = request.scmpreq;
#[expect(clippy::cast_possible_truncation)]
let target_uid = Uid::from_raw(req.data.args[0] as u32);
syscall_setuid_handler(&request, target_uid)
})
}
pub(crate) fn sys_setgid(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_handler!(request, |request: UNotifyEventRequest| {
let req = request.scmpreq;
let arg0 = if scmp_arch_has_uid16(req.data.arch) {
to_id16_val(req.data.args[0])?
} else {
req.data.args[0]
};
#[expect(clippy::cast_possible_truncation)]
let target_gid = Gid::from_raw(arg0 as u32);
syscall_setgid_handler(&request, target_gid)
})
}
pub(crate) fn sys_setgid32(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_handler!(request, |request: UNotifyEventRequest| {
let req = request.scmpreq;
#[expect(clippy::cast_possible_truncation)]
let target_gid = Gid::from_raw(req.data.args[0] as u32);
syscall_setgid_handler(&request, target_gid)
})
}
pub(crate) fn sys_setreuid(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_handler!(request, |request: UNotifyEventRequest| {
let req = request.scmpreq;
let (arg0, arg1) = if scmp_arch_has_uid16(req.data.arch) {
(to_id16(req.data.args[0]), to_id16(req.data.args[1]))
} else {
(req.data.args[0], req.data.args[1])
};
#[expect(clippy::cast_possible_truncation)]
let target_ruid = match arg0 as u32 {
u32::MAX => None,
n => Some(Uid::from_raw(n)),
};
#[expect(clippy::cast_possible_truncation)]
let target_euid = match arg1 as u32 {
u32::MAX => None,
n => Some(Uid::from_raw(n)),
};
syscall_setreuid_handler(&request, target_ruid, target_euid)
})
}
pub(crate) fn sys_setreuid32(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_handler!(request, |request: UNotifyEventRequest| {
let req = request.scmpreq;
#[expect(clippy::cast_possible_truncation)]
let target_ruid = match req.data.args[0] as u32 {
u32::MAX => None,
n => Some(Uid::from_raw(n)),
};
#[expect(clippy::cast_possible_truncation)]
let target_euid = match req.data.args[1] as u32 {
u32::MAX => None,
n => Some(Uid::from_raw(n)),
};
syscall_setreuid_handler(&request, target_ruid, target_euid)
})
}
pub(crate) fn sys_setregid(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_handler!(request, |request: UNotifyEventRequest| {
let req = request.scmpreq;
let (arg0, arg1) = if scmp_arch_has_uid16(req.data.arch) {
(to_id16(req.data.args[0]), to_id16(req.data.args[1]))
} else {
(req.data.args[0], req.data.args[1])
};
#[expect(clippy::cast_possible_truncation)]
let target_rgid = match arg0 as u32 {
u32::MAX => None,
n => Some(Gid::from_raw(n)),
};
#[expect(clippy::cast_possible_truncation)]
let target_egid = match arg1 as u32 {
u32::MAX => None,
n => Some(Gid::from_raw(n)),
};
syscall_setregid_handler(&request, target_rgid, target_egid)
})
}
pub(crate) fn sys_setregid32(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_handler!(request, |request: UNotifyEventRequest| {
let req = request.scmpreq;
#[expect(clippy::cast_possible_truncation)]
let target_rgid = match req.data.args[0] as u32 {
u32::MAX => None,
n => Some(Gid::from_raw(n)),
};
#[expect(clippy::cast_possible_truncation)]
let target_egid = match req.data.args[1] as u32 {
u32::MAX => None,
n => Some(Gid::from_raw(n)),
};
syscall_setregid_handler(&request, target_rgid, target_egid)
})
}
pub(crate) fn sys_setresuid(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_handler!(request, |request: UNotifyEventRequest| {
let req = request.scmpreq;
let (arg0, arg1, arg2) = if scmp_arch_has_uid16(req.data.arch) {
(
to_id16(req.data.args[0]),
to_id16(req.data.args[1]),
to_id16(req.data.args[2]),
)
} else {
(req.data.args[0], req.data.args[1], req.data.args[2])
};
#[expect(clippy::cast_possible_truncation)]
let target_ruid = match arg0 as u32 {
u32::MAX => None,
n => Some(Uid::from_raw(n)),
};
#[expect(clippy::cast_possible_truncation)]
let target_euid = match arg1 as u32 {
u32::MAX => None,
n => Some(Uid::from_raw(n)),
};
#[expect(clippy::cast_possible_truncation)]
let target_suid = match arg2 as u32 {
u32::MAX => None,
n => Some(Uid::from_raw(n)),
};
syscall_setresuid_handler(&request, target_ruid, target_euid, target_suid)
})
}
pub(crate) fn sys_setresuid32(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_handler!(request, |request: UNotifyEventRequest| {
let req = request.scmpreq;
#[expect(clippy::cast_possible_truncation)]
let target_ruid = match req.data.args[0] as u32 {
u32::MAX => None,
n => Some(Uid::from_raw(n)),
};
#[expect(clippy::cast_possible_truncation)]
let target_euid = match req.data.args[1] as u32 {
u32::MAX => None,
n => Some(Uid::from_raw(n)),
};
#[expect(clippy::cast_possible_truncation)]
let target_suid = match req.data.args[2] as u32 {
u32::MAX => None,
n => Some(Uid::from_raw(n)),
};
syscall_setresuid_handler(&request, target_ruid, target_euid, target_suid)
})
}
pub(crate) fn sys_setresgid(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_handler!(request, |request: UNotifyEventRequest| {
let req = request.scmpreq;
let (arg0, arg1, arg2) = if scmp_arch_has_uid16(req.data.arch) {
(
to_id16(req.data.args[0]),
to_id16(req.data.args[1]),
to_id16(req.data.args[2]),
)
} else {
(req.data.args[0], req.data.args[1], req.data.args[2])
};
#[expect(clippy::cast_possible_truncation)]
let target_rgid = match arg0 as u32 {
u32::MAX => None,
n => Some(Gid::from_raw(n)),
};
#[expect(clippy::cast_possible_truncation)]
let target_egid = match arg1 as u32 {
u32::MAX => None,
n => Some(Gid::from_raw(n)),
};
#[expect(clippy::cast_possible_truncation)]
let target_sgid = match arg2 as u32 {
u32::MAX => None,
n => Some(Gid::from_raw(n)),
};
syscall_setresgid_handler(&request, target_rgid, target_egid, target_sgid)
})
}
pub(crate) fn sys_setresgid32(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_handler!(request, |request: UNotifyEventRequest| {
let req = request.scmpreq;
#[expect(clippy::cast_possible_truncation)]
let target_rgid = match req.data.args[0] as u32 {
u32::MAX => None,
n => Some(Gid::from_raw(n)),
};
#[expect(clippy::cast_possible_truncation)]
let target_egid = match req.data.args[1] as u32 {
u32::MAX => None,
n => Some(Gid::from_raw(n)),
};
#[expect(clippy::cast_possible_truncation)]
let target_sgid = match req.data.args[2] as u32 {
u32::MAX => None,
n => Some(Gid::from_raw(n)),
};
syscall_setresgid_handler(&request, target_rgid, target_egid, target_sgid)
})
}
pub(crate) fn sys_setgroups(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_handler!(request, |request: UNotifyEventRequest| {
let req = request.scmpreq;
let is_16 = scmp_arch_has_uid16(req.data.arch);
syscall_setgroups_handler(&request, is_16)
})
}
pub(crate) fn sys_setgroups32(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_handler!(request, |request: UNotifyEventRequest| {
syscall_setgroups_handler(&request, false )
})
}
#[expect(clippy::cognitive_complexity)]
fn syscall_setuid_handler(
request: &UNotifyEventRequest,
target_uid: Uid,
) -> Result<ScmpNotifResp, Errno> {
let source_uid = Uid::current();
if target_uid.as_raw() <= UID_MIN.as_raw() {
return Err(Errno::EPERM);
} else if source_uid == target_uid {
return Ok(unsafe { request.continue_syscall() });
}
let sandbox = request.get_sandbox();
let allowed = sandbox.chk_uid_transit(source_uid, target_uid);
let log_scmp = sandbox.log_scmp();
drop(sandbox);
if !allowed {
if log_scmp {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "req": request,
"target_uid": target_uid.as_raw(), "source_uid": source_uid.as_raw(),
"msg": "UID change without UID transit blocked",
"tip": format!("define UID transit `setuid+{}:{}'",
source_uid.as_raw(), target_uid.as_raw()));
} else {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "pid": request.scmpreq.pid,
"target_uid": target_uid.as_raw(), "source_uid": source_uid.as_raw(),
"msg": "UID change without UID transit blocked",
"tip": format!("define UID transit `setuid+{}:{}'",
source_uid.as_raw(), target_uid.as_raw()));
}
return Err(Errno::EPERM);
}
if let Err(errno) = Errno::result(unsafe { libc::setuid(target_uid.as_raw()) }) {
if log_scmp {
warn!("ctx": "safesetid", "err": errno as i32,
"sys": request.syscall, "req": request,
"target_uid": target_uid.as_raw(), "source_uid": source_uid.as_raw(),
"msg": format!("UID change {}->{} failed: {errno}",
source_uid.as_raw(), target_uid.as_raw()),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
} else {
warn!("ctx": "safesetid", "err": errno as i32,
"sys": request.syscall, "pid": request.scmpreq.pid,
"target_uid": target_uid.as_raw(), "source_uid": source_uid.as_raw(),
"msg": format!("UID change {}->{} failed: {errno}",
source_uid.as_raw(), target_uid.as_raw()),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
}
return Err(errno);
}
safe_drop_cap(CAP_SETUID).or(Err(Errno::EOWNERDEAD))?;
Ok(unsafe { request.continue_syscall() })
}
#[expect(clippy::cognitive_complexity)]
fn syscall_setgid_handler(
request: &UNotifyEventRequest,
target_gid: Gid,
) -> Result<ScmpNotifResp, Errno> {
let source_gid = Gid::current();
if target_gid.as_raw() <= GID_MIN.as_raw() {
return Err(Errno::EPERM);
} else if source_gid == target_gid {
return Ok(unsafe { request.continue_syscall() });
}
let sandbox = request.get_sandbox();
let allowed = sandbox.chk_gid_transit(source_gid, target_gid);
let log_scmp = sandbox.log_scmp();
drop(sandbox);
if !allowed {
if log_scmp {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "req": request,
"target_gid": target_gid.as_raw(), "source_gid": source_gid.as_raw(),
"msg": "GID change without GID transit blocked",
"tip": format!("define GID transit `setgid+{}:{}'",
source_gid.as_raw(), target_gid.as_raw()));
} else {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "pid": request.scmpreq.pid,
"target_gid": target_gid.as_raw(), "source_gid": source_gid.as_raw(),
"msg": "GID change without GID transit blocked",
"tip": format!("define GID transit `setgid+{}:{}'",
source_gid.as_raw(), target_gid.as_raw()));
}
return Err(Errno::EPERM);
}
if let Err(errno) = Errno::result(unsafe { libc::setgid(target_gid.as_raw()) }) {
if log_scmp {
warn!("ctx": "safesetid", "err": errno as i32,
"sys": request.syscall, "req": request,
"target_gid": target_gid.as_raw(), "source_gid": source_gid.as_raw(),
"msg": format!("GID change {}->{} failed: {errno}",
source_gid.as_raw(), target_gid.as_raw()),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
} else {
warn!("ctx": "safesetid", "err": errno as i32,
"sys": request.syscall, "pid": request.scmpreq.pid,
"target_gid": target_gid.as_raw(), "source_gid": source_gid.as_raw(),
"msg": format!("GID change {}->{} failed: {errno}",
source_gid.as_raw(), target_gid.as_raw()),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
}
return Err(errno);
}
setgroups_none()
.ok()
.and_then(|_| safe_drop_cap(CAP_SETGID).ok())
.ok_or(Errno::EOWNERDEAD)?;
Ok(unsafe { request.continue_syscall() })
}
#[expect(clippy::cognitive_complexity)]
fn syscall_setreuid_handler(
request: &UNotifyEventRequest,
target_ruid: Option<Uid>,
target_euid: Option<Uid>,
) -> Result<ScmpNotifResp, Errno> {
if target_ruid.is_none() && target_euid.is_none() {
return Ok(request.return_syscall(0));
}
let resuid = getresuid()?;
let source_ruid = resuid.real;
let source_euid = resuid.effective;
let mut change = false;
if let Some(target_ruid) = target_ruid {
if target_ruid.as_raw() <= UID_MIN.as_raw() {
return Err(Errno::EPERM);
} else if source_ruid != target_ruid {
change = true;
}
}
if let Some(target_euid) = target_euid {
if target_euid.as_raw() <= UID_MIN.as_raw() {
return Err(Errno::EPERM);
} else if source_euid != target_euid {
change = true;
}
}
if !change {
return Ok(unsafe { request.continue_syscall() });
}
let sandbox = request.get_sandbox();
let log_scmp = sandbox.log_scmp();
if let Some(target_ruid) = target_ruid {
if let Some(target_euid) = target_euid {
if target_ruid != target_euid {
if log_scmp {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "req": request,
"target_euid": target_euid.as_raw(), "target_ruid": target_ruid.as_raw(),
"source_euid": source_euid.as_raw(), "source_ruid": source_ruid.as_raw(),
"msg": format!("unsafe UID change with real-UID:{} != effective-UID:{} blocked",
target_ruid, target_euid),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
} else {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "pid": request.scmpreq.pid,
"target_euid": target_euid.as_raw(), "target_ruid": target_ruid.as_raw(),
"source_euid": source_euid.as_raw(), "source_ruid": source_ruid.as_raw(),
"msg": format!("unsafe UID change with real-UID:{} != effective-UID:{} blocked",
target_ruid, target_euid),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
}
return Err(Errno::EPERM);
}
}
}
let mut allowed = true;
if let Some(target_ruid) = target_ruid {
if !sandbox.chk_uid_transit(source_ruid, target_ruid) {
allowed = false;
}
}
if allowed {
if let Some(target_euid) = target_euid {
if !sandbox.chk_uid_transit(source_euid, target_euid) {
allowed = false;
}
}
}
drop(sandbox);
let target_ruid = target_ruid.map(|uid| i64::from(uid.as_raw())).unwrap_or(-1);
let target_euid = target_euid.map(|uid| i64::from(uid.as_raw())).unwrap_or(-1);
if !allowed {
if log_scmp {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "req": request,
"target_euid": target_euid, "target_ruid": target_ruid,
"source_euid": source_euid.as_raw(), "source_ruid": source_ruid.as_raw(),
"msg": "UID change without UID transit blocked",
"tip": format!("define UID transit `setuid+{}:{}'",
source_euid.as_raw(), target_euid));
} else {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "pid": request.scmpreq.pid,
"target_euid": target_euid, "target_ruid": target_ruid,
"source_euid": source_euid.as_raw(), "source_ruid": source_ruid.as_raw(),
"msg": "UID change without UID transit blocked",
"tip": format!("define UID transit `setuid+{}:{}'",
source_euid.as_raw(), target_euid));
}
return Err(Errno::EPERM);
}
if let Err(errno) =
Errno::result(unsafe { libc::syscall(libc::SYS_setreuid, target_ruid, target_euid) })
{
if log_scmp {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "req": request,
"target_euid": target_euid, "target_ruid": target_ruid,
"source_euid": source_euid.as_raw(), "source_ruid": source_ruid.as_raw(),
"msg": format!("UID change {}->{} failed: {errno}",
source_euid.as_raw(), target_euid),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
} else {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "pid": request.scmpreq.pid,
"target_euid": target_euid, "target_ruid": target_ruid,
"source_euid": source_euid.as_raw(), "source_ruid": source_ruid.as_raw(),
"msg": format!("UID change {}->{} failed: {errno}",
source_euid.as_raw(), target_euid),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
}
return Err(errno);
}
safe_drop_cap(CAP_SETUID).or(Err(Errno::EOWNERDEAD))?;
Ok(unsafe { request.continue_syscall() })
}
#[expect(clippy::cognitive_complexity)]
fn syscall_setregid_handler(
request: &UNotifyEventRequest,
target_rgid: Option<Gid>,
target_egid: Option<Gid>,
) -> Result<ScmpNotifResp, Errno> {
if target_rgid.is_none() && target_egid.is_none() {
return Ok(request.return_syscall(0));
}
let resgid = getresgid()?;
let source_rgid = resgid.real;
let source_egid = resgid.effective;
let mut change = false;
if let Some(target_rgid) = target_rgid {
if target_rgid.as_raw() <= GID_MIN.as_raw() {
return Err(Errno::EPERM);
} else if source_rgid != target_rgid {
change = true;
}
}
if let Some(target_egid) = target_egid {
if target_egid.as_raw() <= GID_MIN.as_raw() {
return Err(Errno::EPERM);
} else if source_egid != target_egid {
change = true;
}
}
if !change {
return Ok(unsafe { request.continue_syscall() });
}
let sandbox = request.get_sandbox();
let log_scmp = sandbox.log_scmp();
if let Some(target_rgid) = target_rgid {
if let Some(target_egid) = target_egid {
if target_rgid != target_egid {
if log_scmp {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "req": request,
"target_egid": target_egid.as_raw(), "target_rgid": target_rgid.as_raw(),
"source_egid": source_egid.as_raw(), "source_rgid": source_rgid.as_raw(),
"msg": format!("unsafe GID change with real-GID:{} != effective-GID:{} blocked",
target_rgid.as_raw(), target_egid.as_raw()),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
} else {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "pid": request.scmpreq.pid,
"target_egid": target_egid.as_raw(), "target_rgid": target_rgid.as_raw(),
"source_egid": source_egid.as_raw(), "source_rgid": source_rgid.as_raw(),
"msg": format!("unsafe GID change with real-GID:{} != effective-GID:{} blocked",
target_rgid.as_raw(), target_egid.as_raw()),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
}
return Err(Errno::EPERM);
}
}
}
let mut allowed = true;
if let Some(target_rgid) = target_rgid {
if !sandbox.chk_gid_transit(source_rgid, target_rgid) {
allowed = false;
}
}
if allowed {
if let Some(target_egid) = target_egid {
if !sandbox.chk_gid_transit(source_egid, target_egid) {
allowed = false;
}
}
}
drop(sandbox);
let target_rgid = target_rgid.map(|gid| i64::from(gid.as_raw())).unwrap_or(-1);
let target_egid = target_egid.map(|gid| i64::from(gid.as_raw())).unwrap_or(-1);
if !allowed {
if log_scmp {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "req": request,
"target_egid": target_egid, "target_rgid": target_rgid,
"source_egid": source_egid.as_raw(), "source_rgid": source_rgid.as_raw(),
"msg": "GID change without GID transit blocked",
"tip": format!("define GID transit `setgid+{}:{}'",
source_egid.as_raw(), target_egid));
} else {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "pid": request.scmpreq.pid,
"target_egid": target_egid, "target_rgid": target_rgid,
"source_egid": source_egid.as_raw(), "source_rgid": source_rgid.as_raw(),
"msg": "GID change without GID transit blocked",
"tip": format!("define GID transit `setgid+{}:{}'",
source_egid.as_raw(), target_egid));
}
return Err(Errno::EPERM);
}
if let Err(errno) =
Errno::result(unsafe { libc::syscall(libc::SYS_setregid, target_rgid, target_egid) })
{
if log_scmp {
warn!("ctx": "safesetid", "err": errno as i32,
"sys": request.syscall, "req": request,
"target_egid": target_egid, "target_rgid": target_rgid,
"source_egid": source_egid.as_raw(), "source_rgid": source_rgid.as_raw(),
"msg": format!("GID change {}->{} failed: {errno}",
source_egid.as_raw(), target_egid),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
} else {
warn!("ctx": "safesetid", "err": errno as i32,
"sys": request.syscall, "pid": request.scmpreq.pid,
"target_egid": target_egid, "target_rgid": target_rgid,
"source_egid": source_egid.as_raw(), "source_rgid": source_rgid.as_raw(),
"msg": format!("GID change {}->{} failed: {errno}",
source_egid.as_raw(), target_egid),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
}
return Err(errno);
}
setgroups_none()
.ok()
.and_then(|_| safe_drop_cap(CAP_SETGID).ok())
.ok_or(Errno::EOWNERDEAD)?;
Ok(unsafe { request.continue_syscall() })
}
#[expect(clippy::cognitive_complexity)]
fn syscall_setresuid_handler(
request: &UNotifyEventRequest,
target_ruid: Option<Uid>,
target_euid: Option<Uid>,
target_suid: Option<Uid>,
) -> Result<ScmpNotifResp, Errno> {
if target_ruid.is_none() && target_euid.is_none() && target_suid.is_none() {
return Ok(request.return_syscall(0));
}
let resuid = getresuid()?;
let source_ruid = resuid.real;
let source_euid = resuid.effective;
let source_suid = resuid.saved;
let mut change = false;
if let Some(target_ruid) = target_ruid {
if target_ruid.as_raw() <= UID_MIN.as_raw() {
return Err(Errno::EPERM);
} else if source_ruid != target_ruid {
change = true;
}
}
if let Some(target_euid) = target_euid {
if target_euid.as_raw() <= UID_MIN.as_raw() {
return Err(Errno::EPERM);
} else if source_euid != target_euid {
change = true;
}
}
if let Some(target_suid) = target_suid {
if target_suid.as_raw() <= UID_MIN.as_raw() {
return Err(Errno::EPERM);
} else if source_suid != target_suid {
change = true;
}
}
if !change {
return Ok(unsafe { request.continue_syscall() });
}
let sandbox = request.get_sandbox();
let log_scmp = sandbox.log_scmp();
if let Some(target_ruid) = target_ruid {
if let Some(target_euid) = target_euid {
if target_ruid != target_euid {
if log_scmp {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "req": request,
"target_suid": target_suid.map(|u| u.as_raw()),
"target_euid": target_euid.as_raw(),
"target_ruid": target_ruid.as_raw(),
"source_euid": source_euid.as_raw(),
"source_ruid": source_ruid.as_raw(),
"source_suid": source_suid.as_raw(),
"msg": format!("unsafe UID change with real-UID:{} != effective-UID:{} blocked",
target_ruid, target_euid),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
} else {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "pid": request.scmpreq.pid,
"target_suid": target_suid.map(|u| u.as_raw()),
"target_euid": target_euid.as_raw(),
"target_ruid": target_ruid.as_raw(),
"source_euid": source_euid.as_raw(),
"source_ruid": source_ruid.as_raw(),
"source_suid": source_suid.as_raw(),
"msg": format!("unsafe UID change with real-UID:{} != effective-UID:{} blocked",
target_ruid, target_euid),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
}
return Err(Errno::EPERM);
}
}
}
if let Some(target_ruid) = target_ruid {
if let Some(target_suid) = target_suid {
if target_ruid != target_suid {
if log_scmp {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "req": request,
"target_suid": target_suid.as_raw(),
"target_euid": target_euid.map(|u| u.as_raw()),
"target_ruid": target_ruid.as_raw(),
"source_euid": source_euid.as_raw(),
"source_ruid": source_ruid.as_raw(),
"source_suid": source_suid.as_raw(),
"msg": format!("unsafe UID change with real-UID:{} != saved-UID:{} blocked",
target_ruid, target_suid),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
} else {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "pid": request.scmpreq.pid,
"target_suid": target_suid.as_raw(),
"target_euid": target_euid.map(|u| u.as_raw()),
"target_ruid": target_ruid.as_raw(),
"source_euid": source_euid.as_raw(),
"source_ruid": source_ruid.as_raw(),
"source_suid": source_suid.as_raw(),
"msg": format!("unsafe UID change with real-UID:{} != saved-UID:{} blocked",
target_ruid, target_suid),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
}
return Err(Errno::EPERM);
}
}
}
if let Some(target_euid) = target_euid {
if let Some(target_suid) = target_suid {
if target_euid != target_suid {
if log_scmp {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "req": request,
"target_suid": target_suid.as_raw(),
"target_euid": target_euid.as_raw(),
"target_ruid": target_ruid.map(|u| u.as_raw()),
"source_euid": source_euid.as_raw(),
"source_ruid": source_ruid.as_raw(),
"source_suid": source_suid.as_raw(),
"msg": format!("unsafe UID change with effective-UID:{} != saved-UID:{} blocked",
target_euid, target_suid),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
} else {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "pid": request.scmpreq.pid,
"target_suid": target_suid.as_raw(),
"target_euid": target_euid.as_raw(),
"target_ruid": target_ruid.map(|u| u.as_raw()),
"source_euid": source_euid.as_raw(),
"source_ruid": source_ruid.as_raw(),
"source_suid": source_suid.as_raw(),
"msg": format!("unsafe UID change with effective-UID:{} != saved-UID:{} blocked",
target_euid, target_suid),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
}
return Err(Errno::EPERM);
}
}
}
let mut allowed = true;
if let Some(target_ruid) = target_ruid {
if !sandbox.chk_uid_transit(source_ruid, target_ruid) {
allowed = false;
}
}
if allowed {
if let Some(target_euid) = target_euid {
if !sandbox.chk_uid_transit(source_euid, target_euid) {
allowed = false;
}
}
}
if allowed {
if let Some(target_suid) = target_suid {
if !sandbox.chk_uid_transit(source_suid, target_suid) {
allowed = false;
}
}
}
drop(sandbox);
let target_ruid = target_ruid.map(|uid| i64::from(uid.as_raw())).unwrap_or(-1);
let target_euid = target_euid.map(|uid| i64::from(uid.as_raw())).unwrap_or(-1);
let target_suid = target_suid.map(|uid| i64::from(uid.as_raw())).unwrap_or(-1);
if !allowed {
if log_scmp {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "req": request,
"target_suid": target_suid,
"target_euid": target_euid,
"target_ruid": target_ruid,
"source_euid": source_euid.as_raw(),
"source_ruid": source_ruid.as_raw(),
"source_suid": source_suid.as_raw(),
"msg": "UID change without UID transit blocked",
"tip": format!("define UID transit `setuid+{}:{}'",
source_euid.as_raw(), target_euid));
} else {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "pid": request.scmpreq.pid,
"target_suid": target_suid,
"target_euid": target_euid,
"target_ruid": target_ruid,
"source_euid": source_euid.as_raw(),
"source_ruid": source_ruid.as_raw(),
"source_suid": source_suid.as_raw(),
"msg": "UID change without UID transit blocked",
"tip": format!("define UID transit `setuid+{}:{}'",
source_euid.as_raw(), target_euid));
}
return Err(Errno::EPERM);
}
if let Err(errno) = Errno::result(unsafe {
libc::syscall(libc::SYS_setresuid, target_ruid, target_euid, target_suid)
}) {
if log_scmp {
warn!("ctx": "safesetid", "err": errno as i32,
"sys": request.syscall, "req": request,
"target_suid": target_suid,
"target_euid": target_euid,
"target_ruid": target_ruid,
"source_euid": source_euid.as_raw(),
"source_ruid": source_ruid.as_raw(),
"source_suid": source_suid.as_raw(),
"msg": format!("UID change {}->{} failed: {errno}",
source_euid.as_raw(), target_euid),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
} else {
warn!("ctx": "safesetid", "err": errno as i32,
"sys": request.syscall, "pid": request.scmpreq.pid,
"target_suid": target_suid,
"target_euid": target_euid,
"target_ruid": target_ruid,
"source_euid": source_euid.as_raw(),
"source_ruid": source_ruid.as_raw(),
"source_suid": source_suid.as_raw(),
"msg": format!("UID change {}->{} failed: {errno}",
source_euid.as_raw(), target_euid),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
}
return Err(errno);
}
safe_drop_cap(CAP_SETUID).or(Err(Errno::EOWNERDEAD))?;
Ok(unsafe { request.continue_syscall() })
}
#[expect(clippy::cognitive_complexity)]
fn syscall_setresgid_handler(
request: &UNotifyEventRequest,
target_rgid: Option<Gid>,
target_egid: Option<Gid>,
target_sgid: Option<Gid>,
) -> Result<ScmpNotifResp, Errno> {
if target_rgid.is_none() && target_egid.is_none() && target_sgid.is_none() {
return Ok(request.return_syscall(0));
}
let resgid = getresgid()?;
let source_rgid = resgid.real;
let source_egid = resgid.effective;
let source_sgid = resgid.saved;
let mut change = false;
if let Some(target_rgid) = target_rgid {
if target_rgid.as_raw() <= GID_MIN.as_raw() {
return Err(Errno::EPERM);
} else if source_rgid != target_rgid {
change = true;
}
}
if let Some(target_egid) = target_egid {
if target_egid.as_raw() <= GID_MIN.as_raw() {
return Err(Errno::EPERM);
} else if source_egid != target_egid {
change = true;
}
}
if let Some(target_sgid) = target_sgid {
if target_sgid.as_raw() <= GID_MIN.as_raw() {
return Err(Errno::EPERM);
} else if source_sgid != target_sgid {
change = true;
}
}
if !change {
return Ok(unsafe { request.continue_syscall() });
}
let sandbox = request.get_sandbox();
let log_scmp = sandbox.log_scmp();
if let Some(target_rgid) = target_rgid {
if let Some(target_egid) = target_egid {
if target_rgid != target_egid {
if log_scmp {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "req": request,
"target_sgid": target_sgid.map(|u| u.as_raw()),
"target_egid": target_egid.as_raw(),
"target_rgid": target_rgid.as_raw(),
"source_egid": source_egid.as_raw(),
"source_rgid": source_rgid.as_raw(),
"source_sgid": source_sgid.as_raw(),
"msg": format!("unsafe GID change with real-GID:{} != effective-GID:{} blocked",
target_rgid.as_raw(), target_egid.as_raw()),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
} else {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "pid": request.scmpreq.pid,
"target_sgid": target_sgid.map(|u| u.as_raw()),
"target_egid": target_egid.as_raw(),
"target_rgid": target_rgid.as_raw(),
"source_egid": source_egid.as_raw(),
"source_rgid": source_rgid.as_raw(),
"source_sgid": source_sgid.as_raw(),
"msg": format!("unsafe GID change with real-GID:{} != effective-GID:{} blocked",
target_rgid.as_raw(), target_egid.as_raw()),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
}
return Err(Errno::EPERM);
}
}
}
if let Some(target_rgid) = target_rgid {
if let Some(target_sgid) = target_sgid {
if target_rgid != target_sgid {
if log_scmp {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "req": request,
"target_sgid": target_sgid.as_raw(),
"target_egid": target_egid.map(|u| u.as_raw()),
"target_rgid": target_rgid.as_raw(),
"source_egid": source_egid.as_raw(),
"source_rgid": source_rgid.as_raw(),
"source_sgid": source_sgid.as_raw(),
"msg": format!("unsafe GID change with real-GID:{} != saved-GID:{} blocked",
target_rgid.as_raw(), target_sgid.as_raw()),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
} else {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "pid": request.scmpreq.pid,
"target_sgid": target_sgid.as_raw(),
"target_egid": target_egid.map(|u| u.as_raw()),
"target_rgid": target_rgid.as_raw(),
"source_egid": source_egid.as_raw(),
"source_rgid": source_rgid.as_raw(),
"source_sgid": source_sgid.as_raw(),
"msg": format!("unsafe GID change with real-GID:{} != saved-GID:{} blocked",
target_rgid.as_raw(), target_sgid.as_raw()),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
}
return Err(Errno::EPERM);
}
}
}
if let Some(target_egid) = target_egid {
if let Some(target_sgid) = target_sgid {
if target_egid != target_sgid {
if log_scmp {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "req": request,
"target_sgid": target_sgid.as_raw(),
"target_egid": target_egid.as_raw(),
"target_rgid": target_rgid.map(|u| u.as_raw()),
"source_egid": source_egid.as_raw(),
"source_rgid": source_rgid.as_raw(),
"source_sgid": source_sgid.as_raw(),
"msg": format!("unsafe GID change with effective-GID:{} != saved-GID:{} blocked",
target_egid.as_raw(), target_sgid.as_raw()),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
} else {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "pid": request.scmpreq.pid,
"target_sgid": target_sgid.as_raw(),
"target_egid": target_egid.as_raw(),
"target_rgid": target_rgid.map(|u| u.as_raw()),
"source_egid": source_egid.as_raw(),
"source_rgid": source_rgid.as_raw(),
"source_sgid": source_sgid.as_raw(),
"msg": format!("unsafe GID change with effective-GID:{} != saved-GID:{} blocked",
target_egid.as_raw(), target_sgid.as_raw()),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
}
return Err(Errno::EPERM);
}
}
}
let mut allowed = true;
if let Some(target_rgid) = target_rgid {
if !sandbox.chk_gid_transit(source_rgid, target_rgid) {
allowed = false;
}
}
if allowed {
if let Some(target_egid) = target_egid {
if !sandbox.chk_gid_transit(source_egid, target_egid) {
allowed = false;
}
}
}
if allowed {
if let Some(target_sgid) = target_sgid {
if !sandbox.chk_gid_transit(source_sgid, target_sgid) {
allowed = false;
}
}
}
drop(sandbox);
let target_rgid = target_rgid.map(|gid| i64::from(gid.as_raw())).unwrap_or(-1);
let target_egid = target_egid.map(|gid| i64::from(gid.as_raw())).unwrap_or(-1);
let target_sgid = target_sgid.map(|gid| i64::from(gid.as_raw())).unwrap_or(-1);
if !allowed {
if log_scmp {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "req": request,
"target_sgid": target_sgid,
"target_egid": target_egid,
"target_rgid": target_rgid,
"source_egid": source_egid.as_raw(),
"source_rgid": source_rgid.as_raw(),
"source_sgid": source_sgid.as_raw(),
"msg": "GID change without GID transit blocked",
"tip": format!("define GID transit `setgid+{}:{}'",
source_egid.as_raw(), target_egid));
} else {
warn!("ctx": "safesetid", "err": libc::EPERM,
"sys": request.syscall, "pid": request.scmpreq.pid,
"target_sgid": target_sgid,
"target_egid": target_egid,
"target_rgid": target_rgid,
"source_egid": source_egid.as_raw(),
"source_rgid": source_rgid.as_raw(),
"source_sgid": source_sgid.as_raw(),
"msg": "GID change without GID transit blocked",
"tip": format!("define GID transit `setgid+{}:{}'",
source_egid.as_raw(), target_egid));
}
return Err(Errno::EPERM);
}
if let Err(errno) = Errno::result(unsafe {
libc::syscall(libc::SYS_setresgid, target_rgid, target_egid, target_sgid)
}) {
if log_scmp {
warn!("ctx": "safesetid", "err": errno as i32,
"sys": request.syscall, "req": request,
"target_sgid": target_sgid,
"target_egid": target_egid,
"target_rgid": target_rgid,
"source_egid": source_egid.as_raw(),
"source_rgid": source_rgid.as_raw(),
"source_sgid": source_sgid.as_raw(),
"msg": format!("GID change {}->{} failed: {errno}",
source_egid.as_raw(), target_egid),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
} else {
warn!("ctx": "safesetid", "err": errno as i32,
"sys": request.syscall, "pid": request.scmpreq.pid,
"target_sgid": target_sgid,
"target_egid": target_egid,
"target_rgid": target_rgid,
"source_egid": source_egid.as_raw(),
"source_rgid": source_rgid.as_raw(),
"source_sgid": source_sgid.as_raw(),
"msg": format!("GID change {}->{} failed: {errno}",
source_egid.as_raw(), target_egid),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
}
return Err(errno);
}
setgroups_none()
.ok()
.and_then(|_| safe_drop_cap(CAP_SETGID).ok())
.ok_or(Errno::EOWNERDEAD)?;
Ok(unsafe { request.continue_syscall() })
}
#[expect(clippy::cognitive_complexity)]
fn syscall_setgroups_handler(
request: &UNotifyEventRequest,
is_16: bool,
) -> Result<ScmpNotifResp, Errno> {
let req = request.scmpreq;
#[expect(clippy::cast_possible_truncation)]
let count = req.data.args[0] as u32;
if count > NGROUPS_MAX {
return Err(Errno::EINVAL);
}
let count = count as usize;
if count > 0 {
let list = req.data.args[1];
if !is_valid_ptr(list, req.data.arch) {
return Err(Errno::EFAULT);
}
let gids = request.remote_gidlist(list, count, is_16)?;
for gid in &gids {
if *gid == u32::MAX {
return Err(Errno::EINVAL);
}
}
}
if let Err(errno) = setgroups_none() {
let sandbox = request.get_sandbox();
let log_scmp = sandbox.log_scmp();
drop(sandbox);
if log_scmp {
warn!("ctx": "safesetid", "op": "syd_nogroup",
"err": errno as i32, "sys": request.syscall, "req": request,
"msg": format!("drop additional groups for Syd failed: {errno}"),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
} else {
warn!("ctx": "safesetid", "op": "syd_nogroup",
"err": errno as i32, "sys": request.syscall, "pid": request.scmpreq.pid,
"msg": format!("drop additional groups for Syd failed: {errno}"),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
}
}
Ok(request.return_syscall(0))
}