use std::{
ffi::OsStr,
fs::OpenOptions,
os::unix::{ffi::OsStrExt, fs::OpenOptionsExt},
path::Path,
process::ExitCode,
time::Instant,
};
use data_encoding::HEXLOWER;
use libseccomp::ScmpSyscall;
use nix::{errno::Errno, unistd::isatty};
use syd::{
compat::getdents64, config::DIRENT_BUF_SIZE, err::SydResult, hash::SydHashSet, path::mask_path,
};
#[cfg(all(
not(coverage),
not(feature = "prof"),
not(target_os = "android"),
not(target_arch = "riscv64"),
target_page_size_4k,
target_pointer_width = "64"
))]
#[global_allocator]
static GLOBAL: hardened_malloc::HardenedMalloc = hardened_malloc::HardenedMalloc;
#[cfg(feature = "prof")]
#[global_allocator]
static GLOBAL: tcmalloc::TCMalloc = tcmalloc::TCMalloc;
syd::main! {
syd::set_sigpipe_dfl()?;
let mut args = std::env::args();
match args.nth(1).as_deref() {
None => {
readdir_cwd()?;
return Ok(ExitCode::SUCCESS);
}
Some("-h") => {
println!("Usage: syd-ls [set]");
println!("Print the names of the system calls which belong to the given set and exit.");
println!("If set is drop, print the list of capabilities that are dropped at startup.");
println!("If set is env, print the list of unsafe environment variables.");
println!("If set is fs, print the list of known filesystem types.");
println!("If set is madvise, print the list of allowed madvise(2) advice.");
println!("If set is prctl, print the list of allowed prctl(2) options.");
println!("If set is personality, print the list of allowed personalities.");
println!("If set is setsockopt, print the list of denied setsockopt(2) options.");
println!("Available sets are:");
println!("- cpu");
println!("- dead");
println!("- deny");
println!("- deprecated");
println!("- ebpf");
println!("- futex");
println!("- getid");
println!("- hook");
println!("- keyring");
println!("- kill");
println!("- mount");
println!("- msgqueue");
println!("- nice");
println!("- page_cache");
println!("- perf");
println!("- pkey");
println!("- ptrace");
println!("- safe");
println!("- setid");
println!("- shm");
println!("- time");
println!("- uring");
println!("- uts");
println!("- wordexp");
println!("Given no set, list all files in the current working directory.");
println!("In this mode, getdents64(2) is used directly.");
println!("Use to list files in untrusted directories with huge number of files.");
println!("File names are printed hex-encoded, delimited by newline, use syd-hex(1) to decode.");
println!("See EXAMPLES section in syd-ls(1) manual page.");
}
Some("deny") => {
let mut syscall_set: SydHashSet<_> = syd::config::SAFE_SYSCALLS
.iter()
.map(|&s| String::from(s))
.collect();
for syscall in syd::config::HOOK_SYSCALLS {
syscall_set.insert(syscall.to_string());
}
let mut list = vec![];
for syscall_number in 0..=600 {
let syscall = ScmpSyscall::from(syscall_number);
if let Ok(name) = syscall.get_name() {
if !syscall_set.contains(&name) {
list.push(name);
}
}
}
list.sort_unstable();
for name in list {
println!("{name}");
}
}
Some("cpu") => {
for name in syd::config::CPU_SYSCALLS {
println!("{name}");
}
}
Some("dead") => {
for name in syd::config::DEAD_SYSCALLS {
println!("{name}");
}
}
Some("deprecated") => {
for name in syd::config::DEPRECATED_SYSCALLS {
println!("{name}");
}
}
Some("ebpf") => {
for name in syd::config::EBPF_SYSCALLS {
println!("{name}");
}
}
Some("futex") => {
for name in syd::config::FUTEX_SYSCALLS {
println!("{name}");
}
}
Some("getid") => {
for name in syd::config::GETID_SYSCALLS {
println!("{name}");
}
}
Some("hook") => {
for name in syd::config::HOOK_SYSCALLS {
println!("{name}");
}
}
Some("keyring") => {
for name in syd::config::KEYRING_SYSCALLS {
println!("{name}");
}
}
Some("kill") => {
for name in syd::config::KILL_SYSCALLS {
println!("{name}");
}
}
Some("mount") => {
for name in syd::config::MOUNT_SYSCALLS {
println!("{name}");
}
}
Some("msgqueue") => {
for name in syd::config::MSGQUEUE_SYSCALLS {
println!("{name}");
}
}
Some("nice") => {
for name in syd::config::NICE_SYSCALLS {
println!("{name}");
}
}
Some("page_cache") => {
for name in syd::config::PAGE_CACHE_SYSCALLS {
println!("{name}");
}
}
Some("perf") => {
for name in syd::config::PERF_SYSCALLS {
println!("{name}");
}
}
Some("pkey") => {
for name in syd::config::PKEY_SYSCALLS {
println!("{name}");
}
}
Some("ptrace") => {
for name in syd::config::PTRACE_SYSCALLS {
println!("{name}");
}
}
Some("safe") | Some("allow") => {
for name in syd::config::SAFE_SYSCALLS {
println!("{name}");
}
}
Some("setid") => {
for name in syd::config::SET_ID_SYSCALLS {
println!("{name}");
}
}
Some("shm") => {
for name in syd::config::SHM_SYSCALLS {
println!("{name}");
}
}
Some("time") => {
for name in syd::config::TIME_SYSCALLS {
println!("{name}");
}
}
Some("uring") => {
for name in syd::config::IOURING_SYSCALLS {
println!("{name}");
}
}
Some("uts") => {
for name in syd::config::UTS_SYSCALLS {
println!("{name}");
}
}
Some("wordexp") => {
for name in syd::config::WORDEXP_SYSCALLS {
println!("{name}");
}
}
Some("env") => {
for env in syd::config::UNSAFE_ENV {
let env = mask_path(Path::new(OsStr::from_bytes(env)));
println!("{env}");
}
}
Some("fs") => {
for (fstype, fstype_id) in syd::config::FS_MAGIC {
println!("{fstype_id:#x}\t{fstype}");
}
}
Some("personality") => {
for (name, _) in syd::config::SAFE_PERSONAS {
println!("{name}");
}
}
Some("madvise") => {
for (name, _) in syd::config::ALLOW_MADVISE {
println!("{name}");
}
}
Some("prctl") => {
for (name, _) in syd::config::ALLOW_PRCTL {
println!("{name}");
}
}
Some("setsockopt") => {
for (level, optname) in syd::config::DENY_SETSOCKOPT {
println!("{level:#x}:{optname:#x}");
}
}
Some("syd_emu") => {
for name in syd::config::EMU_SYSCALLS {
println!("{name}");
}
}
Some("syd_int") => {
for name in syd::config::INT_SYSCALLS {
println!("{name}");
}
}
Some("syd_out") => {
for name in syd::config::OUT_SYSCALLS {
println!("{name}");
}
}
Some("syd_ipc") => {
for name in syd::config::IPC_SYSCALLS {
println!("{name}");
}
}
Some("syd_aes") => {
for name in syd::config::AES_SYSCALLS {
println!("{name}");
}
}
Some("syd_main" | "syd_run") => {
for name in syd::config::MAIN_SYSCALLS {
println!("{name}");
}
}
Some("syd_oci") => {
for name in syd::config::OCI_SYSCALLS {
println!("{name}");
}
}
Some(set) => {
eprintln!("No such set: '{set}'");
return Ok(ExitCode::FAILURE);
}
}
Ok(ExitCode::SUCCESS)
}
fn readdir_cwd() -> SydResult<()> {
#[expect(clippy::disallowed_methods)]
let cwd = OpenOptions::new()
.read(true)
.custom_flags(libc::O_DIRECTORY)
.open(".")?;
let report_progress = isatty(std::io::stderr())?;
let epoch = if report_progress {
Some(Instant::now())
} else {
None
};
let mut count: u64 = 0;
loop {
let mut entries = match getdents64(&cwd, DIRENT_BUF_SIZE) {
Ok(entries) => entries,
Err(Errno::ECANCELED) => break, Err(errno) => return Err(errno.into()),
};
for entry in &mut entries {
let name = HEXLOWER.encode(entry.name_bytes());
println!("{name}");
if report_progress {
count = count.saturating_add(1);
if count % 25000 == 0 {
eprint!("\r\x1b[Ksyd-ls: {count} files");
}
}
}
}
if let Some(epoch) = epoch {
let dur = epoch.elapsed().as_secs_f64();
eprintln!("\r\x1b[Ksyd-ls: Listed {count} files in {dur} seconds.");
}
Ok(())
}