use std::{io::Seek, os::fd::AsRawFd, sync::Arc};
use nix::{
errno::Errno,
fcntl::{AtFlags, OFlag},
unistd::Pid,
};
use crate::{
compat::ResolveFlag,
confine::{scmp_arch, SydArch, SydSys},
elf::{ElfFileType, ElfType, ExecutableFile, LinkingType},
err::err2no,
error,
fd::{check_executable, AT_EXECVE_CHECK, PROC_FILE},
kernel::{ptrace::enforce_action, sandbox_path, to_atflags},
lookup::{safe_open_msym, CanonicalPath, FileType, FsFlags, MaybeFd},
path::XPathBuf,
ptrace::ptrace_syscall_info,
req::{RemoteProcess, SysArg, SysFlags},
sandbox::{Action, Capability, IntegrityError, SandboxGuard},
warn,
workers::WorkerCache,
xfmt,
};
#[expect(clippy::cognitive_complexity)]
pub(crate) fn sysenter_exec(
pid: Pid,
info: ptrace_syscall_info,
cache: &Arc<WorkerCache>,
sandbox: &SandboxGuard,
) -> Result<(), Errno> {
let data = if let Some(data) = info.seccomp() {
data
} else {
unreachable!("BUG: Invalid system call information returned by kernel!");
};
#[expect(clippy::cast_possible_truncation)]
let (syscall, arg) = match SydSys::try_from(data.ret_data as u16) {
Ok(SydSys::SysExecve) => (
SydSys::SysExecve,
SysArg {
path: Some(0),
fsflags: FsFlags::MUST_PATH,
..Default::default()
},
),
Ok(SydSys::SysExecveat) => {
let flags = to_atflags(
data.args[4],
AtFlags::AT_SYMLINK_NOFOLLOW | AtFlags::AT_EMPTY_PATH | AT_EXECVE_CHECK,
)?;
let mut fsflags = FsFlags::MUST_PATH;
if flags.contains(AtFlags::AT_SYMLINK_NOFOLLOW) {
fsflags.insert(FsFlags::NO_FOLLOW_LAST);
}
let empty_path = flags.contains(AtFlags::AT_EMPTY_PATH);
(
SydSys::SysExecveat,
SysArg {
dirfd: Some(0),
path: Some(1),
flags: if empty_path {
SysFlags::EMPTY_PATH | SysFlags::PASS_DELETE
} else {
SysFlags::empty()
},
fsflags,
},
)
}
_ => unreachable!("BUG: invalid syscall data {}!", data.ret_data as u16),
};
let process = RemoteProcess::new(pid);
#[expect(clippy::disallowed_methods)]
let arch: SydArch = scmp_arch(info.arch).unwrap().into();
let (path, _, empty_path) = process.read_path(sandbox, arch.into(), data.args, arg, None)?;
if !empty_path && sandbox.is_chroot() {
return Err(Errno::ENOENT);
}
let caps = sandbox.getcaps(Capability::CAP_EXEC | Capability::CAP_TPE);
if caps.contains(Capability::CAP_EXEC) {
sandbox_path(
None,
sandbox,
pid,
path.tgid,
path.abs(),
Capability::CAP_EXEC,
syscall.name(),
)?;
}
if !arg.fsflags.follow_last()
&& path
.typ
.as_ref()
.map(|typ| typ.is_symlink() || typ.is_magic_link())
.unwrap_or(false)
{
return Err(Errno::ELOOP);
}
match path.typ.as_ref() {
Some(FileType::Reg) => {}
Some(FileType::Mfd) if sandbox.options.allow_unsafe_memfd() => {}
_ => return Err(Errno::EACCES),
};
check_executable(path.dir())?;
if let Some(action) = cache.check_segvguard(
path.abs(),
sandbox.segvguard_act(),
sandbox.get_segvguard_expiry(),
) {
if action != Action::Filter {
let (_, bin) = path.abs().split();
error!("ctx": "exec", "op": "segvguard",
"msg": xfmt!("max crashes {} exceeded, execution of `{bin}' denied",
sandbox.segvguard_maxcrashes),
"tip": "increase `segvguard/maxcrashes'",
"pid": pid.as_raw(), "path": path.abs());
}
enforce_action(action, Errno::EACCES, pid)?;
}
if caps.contains(Capability::CAP_TPE) {
let (action, msg) = sandbox.check_tpe(path.dir(), path.abs())?;
if !matches!(action, Action::Allow | Action::Filter) {
let msg = msg.as_deref().unwrap_or("?");
error!("ctx": "exec", "op": "check_tpe",
"msg": xfmt!("exec from untrusted path blocked: {msg}"),
"pid": pid.as_raw(), "path": path.abs(),
"sys": syscall, "arch": info.arch, "args": data.args,
"tip": "move the binary to a safe location or use `sandbox/tpe:off'");
}
enforce_action(action, Errno::EACCES, pid)?;
}
if sandbox.options.deny_exec_script()
|| sandbox.options.deny_exec_elf32()
|| sandbox.options.deny_exec_elf_dynamic()
|| sandbox.options.deny_exec_elf_static()
|| !sandbox.options.allow_unsafe_exec_ldso()
|| !sandbox.options.allow_unsafe_exec_nopie()
|| !sandbox.options.allow_unsafe_exec_stack()
|| sandbox.enabled(Capability::CAP_FORCE)
{
verify_exec(sandbox, path, pid)?;
}
Ok(())
}
#[expect(clippy::cognitive_complexity)]
fn verify_exec(sandbox: &SandboxGuard, mut path: CanonicalPath, pid: Pid) -> Result<(), Errno> {
let mut fd = match path.take_dir() {
MaybeFd::Owned(fd) => {
let pfd = XPathBuf::from_self_fd(fd.as_raw_fd())?;
let pfl = OFlag::O_RDONLY | OFlag::O_NOCTTY;
safe_open_msym(PROC_FILE(), &pfd, pfl, ResolveFlag::empty())?
}
_ => return Err(Errno::ENOEXEC),
};
let deny_script = sandbox.options.deny_exec_script();
let restrict_32 = sandbox.options.deny_exec_elf32();
let restrict_dyn = sandbox.options.deny_exec_elf_dynamic();
let restrict_sta = sandbox.options.deny_exec_elf_static();
let restrict_ldd = !sandbox.options.allow_unsafe_exec_ldso();
let restrict_pie = !sandbox.options.allow_unsafe_exec_nopie();
let restrict_xs = !sandbox.options.allow_unsafe_exec_stack();
let check_linking = restrict_ldd || restrict_dyn || restrict_sta || restrict_pie || restrict_xs;
let exe = ExecutableFile::parse(&mut fd, check_linking)?;
let is_script = exe == ExecutableFile::Script;
if is_script && deny_script {
warn!("ctx": "exec", "op": "deny_exec_script", "pid": pid.as_raw(),
"path": path.abs(), "exe": xfmt!("{exe}"),
"msg": "script execution denied",
"tip": "configure `trace/deny_exec_script:0'");
return Err(Errno::EACCES);
}
if !is_script
&& restrict_ldd
&& !matches!(
exe,
ExecutableFile::Elf {
file_type: ElfFileType::Executable,
..
}
)
{
warn!("ctx": "exec", "op": "check_elf", "pid": pid.as_raw(),
"path": path.abs(), "exe": xfmt!("{exe}"),
"msg": "ld.so(8) exec-indirection prevented",
"tip": "fix your program or configure `trace/allow_unsafe_exec_ldso:1'");
return Err(Errno::EACCES);
}
if !is_script && restrict_pie && matches!(exe, ExecutableFile::Elf { pie: false, .. }) {
warn!("ctx": "exec", "op": "check_elf", "pid": pid.as_raw(),
"path": path.abs(), "exe": xfmt!("{exe}"),
"msg": "ELF is not Position Independent Executable (PIE)",
"tip": "fix your program or configure `trace/allow_unsafe_exec_nopie:1'");
return Err(Errno::EACCES);
}
if !is_script && restrict_xs && matches!(exe, ExecutableFile::Elf { xs: true, .. }) {
error!("ctx": "exec", "op": "check_elf", "pid": pid.as_raw(),
"path": path.abs(), "exe": xfmt!("{exe}"),
"msg": "ELF has executable stack",
"tip": "fix your program or configure `trace/allow_unsafe_exec_stack:1'");
return Err(Errno::EACCES);
}
if !is_script
&& restrict_32
&& matches!(
exe,
ExecutableFile::Elf {
elf_type: ElfType::Elf32,
..
}
)
{
error!("ctx": "exec", "op": "check_elf", "pid": pid.as_raw(),
"path": path.abs(), "exe": xfmt!("{exe}"),
"msg": "32-bit ELF execution prevented",
"tip": "configure `trace/deny_exec_elf32:0'");
return Err(Errno::EACCES);
}
if !is_script
&& restrict_dyn
&& matches!(
exe,
ExecutableFile::Elf {
linking_type: Some(LinkingType::Dynamic),
..
}
)
{
warn!("ctx": "exec", "op": "check_elf", "pid": pid.as_raw(),
"path": path.abs(), "exe": xfmt!("{exe}"),
"msg": "dynamic linked ELF execution prevented",
"tip": "configure `trace/deny_exec_elf_dynamic:0'");
return Err(Errno::EACCES);
}
if !is_script
&& restrict_sta
&& matches!(
exe,
ExecutableFile::Elf {
linking_type: Some(LinkingType::Static),
..
}
)
{
warn!("ctx": "exec", "op": "check_elf", "pid": pid.as_raw(),
"path": path, "exe": xfmt!("{exe}"),
"msg": "static linked ELF execution prevented",
"tip": "configure `trace/deny_exec_elf_static:0'");
return Err(Errno::EACCES);
}
if sandbox.enabled(Capability::CAP_FORCE) {
fd.rewind().map_err(|err| err2no(&err))?;
match sandbox.check_force2(fd, path.abs()) {
Ok(action) => {
match action {
Action::Allow | Action::Filter => {}
Action::Exit => {
error!("ctx": "exec", "op": "verify_elf", "act": action,
"pid": pid.as_raw(), "path": path.abs(),
"tip": xfmt!("configure `force+{path}:<algorithm>:<checksum>'"));
}
_ => {
warn!("ctx": "exec", "op": "verify_elf", "act": action,
"pid": pid.as_raw(), "path": path.abs(),
"tip": xfmt!("configure `force+{path}:<algorithm>:<checksum>'"));
}
}
enforce_action(action, Errno::EACCES, pid)?;
}
Err(IntegrityError::Sys(errno)) => {
error!("ctx": "exec", "op": "verify_elf", "act": Action::Deny,
"pid": pid.as_raw(), "path": path.abs(),
"msg": xfmt!("system error during ELF checksum calculation: {errno}"),
"tip": xfmt!("configure `force+{path}:<algorithm>:<checksum>'"));
return Err(Errno::EACCES);
}
Err(IntegrityError::Hash {
action,
expected,
found,
}) => {
if !matches!(action, Action::Allow | Action::Filter) {
warn!("ctx": "exec", "op": "verify_elf", "act": action,
"pid": pid.as_raw(), "path": path.abs(),
"msg": xfmt!("ELF checksum mismatch: {found} is not {expected}"),
"tip": xfmt!("configure `force+{path}:<algorithm>:<checksum>'"));
}
enforce_action(action, Errno::EACCES, pid)?;
}
}
}
Ok(())
}