use std::{io::Seek, os::fd::AsRawFd};
use data_encoding::HEXLOWER;
use nix::{
errno::Errno,
fcntl::{AtFlags, OFlag},
sys::signal::{kill, Signal},
unistd::Pid,
};
use crate::{
compat::ResolveFlag,
config::{PTRACE_DATA_EXECVE, PTRACE_DATA_EXECVEAT},
confine::{is_valid_ptr, scmp_arch, SydArch},
debug,
elf::{ElfError, ElfFileType, ElfType, ExecutableFile, LinkingType},
err::err2no,
error,
fd::{is_executable, AT_EXECVE_CHECK, PROC_FILE},
kernel::{sandbox_path, to_atflags},
log_enabled,
lookup::{safe_open_msym, FileType, FsFlags, MaybeFd},
path::XPathBuf,
proc::proc_get_vma,
ptrace::ptrace_syscall_info,
req::{RemoteProcess, SysArg, SysFlags},
sandbox::{Action, Capability, IntegrityError, SandboxGuard},
syslog::LogLevel,
warn,
};
#[expect(clippy::cognitive_complexity)]
pub(crate) fn sysenter_exec(
pid: Pid,
sandbox: &SandboxGuard,
info: ptrace_syscall_info,
) -> 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_name, arg) = match data.ret_data as u16 {
PTRACE_DATA_EXECVE => (
"execve",
SysArg {
path: Some(0),
fsflags: FsFlags::MUST_PATH,
..Default::default()
},
),
PTRACE_DATA_EXECVEAT => {
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);
(
"execveat",
SysArg {
dirfd: Some(0),
path: Some(1),
flags: if empty_path {
SysFlags::EMPTY_PATH
} else {
SysFlags::empty()
},
fsflags,
},
)
}
data => unreachable!("BUG: invalid syscall data {data}!"),
};
let process = RemoteProcess::new(pid);
#[expect(clippy::disallowed_methods)]
let arch: SydArch = scmp_arch(info.arch).unwrap().into();
let (mut path, _, _) = process.read_path(sandbox, arch.into(), data.args, arg, None)?;
let caps = sandbox.getcaps(Capability::CAP_EXEC | Capability::CAP_TPE);
if caps.contains(Capability::CAP_EXEC) {
sandbox_path(
None,
sandbox,
pid,
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.flags.allow_unsafe_memfd() => {}
_ => return Err(Errno::EACCES),
};
if !is_executable(path.dir()) {
return Err(Errno::EACCES);
}
if let Some(action) = sandbox.check_segvguard(path.abs()) {
if action != Action::Filter {
let (_, bin) = path.abs().split();
error!("ctx": "exec", "op": "segvguard",
"msg": format!("max crashes {} exceeded, execution of `{bin}' denied",
sandbox.segvguard_maxcrashes),
"tip": "increase `segvguard/maxcrashes'",
"pid": pid.as_raw(), "path": path.abs());
}
match action {
Action::Allow | Action::Warn => {}
Action::Panic | Action::Deny | Action::Filter => return Err(Errno::EACCES),
Action::Exit => std::process::exit(libc::EACCES),
Action::Stop => {
let _ = kill(process.pid, Some(Signal::SIGSTOP));
return Err(Errno::EACCES);
}
Action::Abort => {
let _ = kill(process.pid, Some(Signal::SIGABRT));
return Err(Errno::EACCES);
}
Action::Kill => {
let _ = kill(process.pid, Some(Signal::SIGKILL));
return Err(Errno::EACCES);
}
}
}
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": "trusted_path_execution",
"msg": format!("exec from untrusted path blocked: {msg}"),
"pid": pid.as_raw(), "path": path.abs(),
"sys": syscall_name, "arch": info.arch, "args": data.args,
"tip": "move the binary to a safe location or use `sandbox/tpe:off'");
}
match action {
Action::Allow | Action::Warn => {}
Action::Panic | Action::Deny | Action::Filter => return Err(Errno::EACCES),
Action::Exit => std::process::exit(libc::EACCES),
Action::Stop => {
let _ = kill(pid, Some(Signal::SIGSTOP));
return Err(Errno::EACCES);
}
Action::Abort => {
let _ = kill(pid, Some(Signal::SIGABRT));
return Err(Errno::EACCES);
}
Action::Kill => {
let _ = kill(pid, Some(Signal::SIGKILL));
return Err(Errno::EACCES);
}
}
}
let mut fd = match path.dir.take() {
Some(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.flags.deny_exec_script();
let restrict_32 = sandbox.flags.deny_exec_elf32();
let restrict_dyn = sandbox.flags.deny_exec_elf_dynamic();
let restrict_sta = sandbox.flags.deny_exec_elf_static();
let restrict_ldd = !sandbox.flags.allow_unsafe_exec_ldso();
let restrict_pie = !sandbox.flags.allow_unsafe_exec_nopie();
let restrict_xs = !sandbox.flags.allow_unsafe_exec_stack();
let check_linking = restrict_ldd || restrict_dyn || restrict_sta || restrict_pie || restrict_xs;
let exe = match ExecutableFile::parse(&mut fd, check_linking) {
Ok(exe) => exe,
Err(ElfError::IoError(err)) => return Err(err2no(&err)),
Err(ElfError::BadMagic | ElfError::Malformed) => return Err(Errno::ENOEXEC),
};
let is_script = exe == ExecutableFile::Script;
if is_script && deny_script {
error!("ctx": "exec", "op": "deny_exec_script",
"msg": "script execution denied",
"pid": pid.as_raw(), "path": path.abs(),
"tip": "configure `trace/deny_exec_script:0'",
"exe": format!("{exe}"));
return Err(Errno::EACCES);
}
if !is_script
&& restrict_ldd
&& !matches!(
exe,
ExecutableFile::Elf {
file_type: ElfFileType::Executable,
..
}
)
{
error!("ctx": "exec", "op": "check_elf",
"msg": "ld.so(8) exec-indirection prevented",
"pid": pid.as_raw(), "path": path.abs(),
"tip": "configure `trace/allow_unsafe_exec_ldso:1'",
"exe": format!("{exe}"));
return Err(Errno::EACCES);
}
if !is_script && restrict_pie && matches!(exe, ExecutableFile::Elf { pie: false, .. }) {
error!("ctx": "check_elf", "msg": "ELF is not Position Independent Executable (PIE)",
"pid": pid.as_raw(), "path": path.abs(),
"tip": "configure `trace/allow_unsafe_exec_nopie:1'",
"exe": format!("{exe}"));
return Err(Errno::EACCES);
}
if !is_script && restrict_xs && matches!(exe, ExecutableFile::Elf { xs: true, .. }) {
error!("ctx": "exec", "op": "check_elf",
"msg": "ELF has executable stack",
"pid": pid.as_raw(), "path": path.abs(),
"tip": "configure `trace/allow_unsafe_exec_stack:1'",
"exe": format!("{exe}"));
return Err(Errno::EACCES);
}
if !is_script
&& restrict_32
&& matches!(
exe,
ExecutableFile::Elf {
elf_type: ElfType::Elf32,
..
}
)
{
error!("ctx": "exec", "op": "check_elf",
"msg": "32-bit ELF execution prevented",
"pid": pid.as_raw(), "path": path.abs(),
"tip": "configure `trace/deny_exec_elf32:0'",
"exe": format!("{exe}"));
return Err(Errno::EACCES);
}
if !is_script
&& restrict_dyn
&& matches!(
exe,
ExecutableFile::Elf {
linking_type: Some(LinkingType::Dynamic),
..
}
)
{
error!("ctx": "exec", "op": "check_elf",
"msg": "dynamic linked ELF execution prevented",
"pid": pid.as_raw(), "path": path.abs(),
"tip": "configure `trace/deny_exec_elf_dynamic:0'",
"exe": format!("{exe}"));
return Err(Errno::EACCES);
}
if !is_script
&& restrict_sta
&& matches!(
exe,
ExecutableFile::Elf {
linking_type: Some(LinkingType::Static),
..
}
)
{
error!("ctx": "exec", "op": "check_elf",
"msg": "static linked ELF execution prevented",
"pid": pid.as_raw(), "path": path,
"tip": "configure `trace/deny_exec_elf_static:0'",
"exe": format!("{exe}"));
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::Allow) => {}
Ok(Action::Warn) => {
warn!("ctx": "exec", "op": "verify_elf", "act": Action::Warn,
"pid": pid.as_raw(), "path": path.abs(),
"tip": format!("configure `force+{path}:<algorithm>:<checksum>'"));
}
Ok(Action::Filter) => return Err(Errno::EACCES),
Ok(act @ (Action::Deny | Action::Panic)) => {
warn!("ctx": "exec", "op": "verify_elf", "act": act,
"pid": pid.as_raw(), "path": path.abs(),
"tip": format!("configure `force+{path}:<algorithm>:<checksum>'"));
return Err(Errno::EACCES);
}
Ok(Action::Stop) => {
warn!("ctx": "exec", "op": "verify_elf", "act": Action::Stop,
"pid": pid.as_raw(), "path": path.abs(),
"tip": format!("configure `force+{path}:<algorithm>:<checksum>'"));
let _ = kill(pid, Some(Signal::SIGSTOP));
return Err(Errno::EACCES);
}
Ok(Action::Abort) => {
warn!("ctx": "exec", "op": "verify_elf", "act": Action::Abort,
"pid": pid.as_raw(), "path": path.abs(),
"tip": format!("configure `force+{path}:<algorithm>:<checksum>'"));
let _ = kill(pid, Some(Signal::SIGABRT));
return Err(Errno::EACCES);
}
Ok(Action::Kill) => {
warn!("ctx": "exec", "op": "verify_elf", "act": Action::Kill,
"pid": pid.as_raw(), "path": path.abs(),
"tip": format!("configure `force+{path}:<algorithm>:<checksum>'"));
let _ = kill(pid, Some(Signal::SIGKILL));
return Err(Errno::EACCES);
}
Ok(Action::Exit) => {
error!("ctx": "exec", "op": "verify_elf", "act": Action::Exit,
"pid": pid.as_raw(), "path": path.abs(),
"tip": format!("configure `force+{path}:<algorithm>:<checksum>'"));
std::process::exit(libc::EACCES);
}
Err(IntegrityError::Sys(errno)) => {
error!("ctx": "exec", "op": "verify_elf",
"msg": format!("system error during ELF checksum calculation: {errno}"),
"pid": pid.as_raw(), "path": path.abs(),
"tip": format!("configure `force+{path}:<algorithm>:<checksum>'"));
return Err(Errno::EACCES);
}
Err(IntegrityError::Hash {
action,
expected,
found,
}) => {
if !matches!(action, Action::Allow | Action::Filter) {
error!("ctx": "exec", "op": "verify_elf", "act": action,
"msg": format!("ELF checksum mismatch: {found} is not {expected}"),
"pid": pid.as_raw(), "path": path.abs(),
"tip": format!("configure `force+{path}:<algorithm>:<checksum>'"));
}
match action {
Action::Allow | Action::Warn => {}
Action::Panic | Action::Deny | Action::Filter => return Err(Errno::EACCES),
Action::Stop | Action::Abort | Action::Kill => {
let _ = kill(
pid,
Some(
Signal::try_from(
action
.signal()
.map(|sig| sig as i32)
.unwrap_or(libc::SIGKILL),
)
.unwrap_or(Signal::SIGKILL),
),
);
return Err(Errno::EACCES);
}
Action::Exit => std::process::exit(libc::EACCES),
};
}
}
}
if log_enabled!(LogLevel::Debug) {
let exe = exe.to_string();
let path = path.abs();
let ip_vma = proc_get_vma(pid, info.instruction_pointer).ok();
let sp_vma = proc_get_vma(pid, info.stack_pointer).ok();
let ip = info.instruction_pointer;
let sp = info.stack_pointer;
let process = RemoteProcess::new(pid);
let ip_mem = if is_valid_ptr(ip, arch.into()) {
let mut ip_mem = [0u8; 64];
match unsafe { process.read_mem(arch.into(), &mut ip_mem, ip, 64) } {
Ok(_) => Some(ip_mem),
Err(Errno::EFAULT) => None,
Err(Errno::ESRCH) => return Err(Errno::ESRCH),
Err(_) => {
let _ = kill(pid, Some(Signal::SIGKILL));
return Err(Errno::ESRCH);
}
}
} else {
None
};
let sp_mem = if is_valid_ptr(sp, arch.into()) {
let mut sp_mem = [0u8; 64];
match unsafe { process.read_mem(arch.into(), &mut sp_mem, sp, 64) } {
Ok(_) => Some(sp_mem),
Err(Errno::EFAULT) => None,
Err(Errno::ESRCH) => return Err(Errno::ESRCH),
Err(_) => {
let _ = kill(process.pid, Some(Signal::SIGKILL));
return Err(Errno::ESRCH);
}
}
} else {
None
};
let ip_mem = ip_mem.map(|ip_mem| HEXLOWER.encode(&ip_mem));
let sp_mem = sp_mem.map(|sp_mem| HEXLOWER.encode(&sp_mem));
debug!("ctx": "exec", "op": "verify_exec",
"msg": format!("execution of `{path}' of type {exe} resumed"),
"pid": pid.as_raw(), "path": &path, "exe": &exe, "args": data.args,
"ip": info.instruction_pointer, "sp": info.stack_pointer,
"ip_mem": ip_mem, "sp_mem": sp_mem,
"ip_vma": ip_vma, "sp_vma": sp_vma);
}
Ok(())
}