syd 3.58.0

rock-solid application kernel
Documentation
//
// Syd: rock-solid application kernel
// src/utils/syd-ls.rs: Print the names of the system calls which belong to the given set and exit
//                If set is fcntl, print the list of denied fcntl options
//                If set is prctl, print the list of allowed prctl options
//                If set is personality, print the list of allowed personalities.
//
// Copyright (c) 2024, 2025, 2026 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::{
    ffi::OsStr,
    fs::OpenOptions,
    os::unix::{ffi::OsStrExt, fs::OpenOptionsExt},
    path::Path,
    process::ExitCode,
    time::Instant,
};

use libseccomp::ScmpSyscall;
use nix::{errno::Errno, unistd::isatty};
use syd::{
    compat::getdents64,
    config::DIRENT_BUF_SIZE,
    eprintf, eprintfln,
    err::SydResult,
    hash::{hex_encode_lower, SydHashSet},
    path::mask_path,
    printfln,
};

// Set global allocator to GrapheneOS allocator.
#[cfg(all(
    not(target_os = "android"),
    not(target_arch = "loongarch64"),
    not(target_arch = "riscv64"),
    target_page_size_4k,
    target_pointer_width = "64"
))]
#[global_allocator]
static GLOBAL: hardened_malloc::HardenedMalloc = hardened_malloc::HardenedMalloc;

syd::main! {
    syd::set_sigpipe_dfl()?;

    let mut args = std::env::args();

    match args.nth(1).as_deref() {
        None => {
            // Given no arguments, list current directory using getdents64(2).
            readdir_cwd()?;
            return Ok(ExitCode::SUCCESS);
        }
        Some("-h") => {
            printfln!("Usage: syd-ls [set]")?;
            printfln!("Print the names of the system calls which belong to the given set and exit.")?;
            printfln!("If set is drop, print the list of capabilities that are dropped at startup.")?;
            printfln!("If set is env, print the list of unsafe environment variables.")?;
            printfln!("If set is fs, print the list of known filesystem types.")?;
            printfln!("If set is madvise, print the list of allowed madvise(2) advice.")?;
            printfln!("If set is fcntl, print the list of denied fcntl(2) options.")?;
            printfln!("If set is prctl, print the list of allowed prctl(2) options.")?;
            printfln!("If set is personality, print the list of allowed personalities.")?;
            printfln!("If set is setsockopt, print the list of denied setsockopt(2) options.")?;
            printfln!("Available sets are:")?;
            printfln!("- cpu")?;
            printfln!("- dead")?;
            printfln!("- deny")?;
            printfln!("- deprecated")?;
            printfln!("- ebpf")?;
            printfln!("- futex")?;
            printfln!("- getid")?;
            printfln!("- hook")?;
            printfln!("- keyring")?;
            printfln!("- kill")?;
            printfln!("- mount")?;
            printfln!("- msgqueue")?;
            printfln!("- nice")?;
            printfln!("- numa")?;
            printfln!("- page_cache")?;
            printfln!("- perf")?;
            printfln!("- pkey")?;
            printfln!("- ptrace")?;
            printfln!("- safe")?;
            printfln!("- setid")?;
            printfln!("- shm")?;
            printfln!("- time")?;
            printfln!("- uring")?;
            printfln!("- uts")?;
            printfln!("- wordexp")?;
            printfln!("Given no set, list all files in the current working directory.")?;
            printfln!("In this mode, getdents64(2) is used directly.")?;
            printfln!("Use to list files in untrusted directories with huge number of files.")?;
            printfln!("File names are printed hex-encoded, delimited by newline, use syd-hex(1) to decode.")?;
            printfln!("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 {
                printfln!("{name}")?;
            }
        }
        Some("cpu") => {
            for name in syd::config::CPU_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("dead") => {
            for name in syd::config::DEAD_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("deprecated") => {
            for name in syd::config::DEPRECATED_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("ebpf") => {
            for name in syd::config::EBPF_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("futex") => {
            for name in syd::config::FUTEX_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("getid") => {
            for name in syd::config::GETID_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("hook") => {
            for name in syd::config::HOOK_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("keyring") => {
            for name in syd::config::KEYRING_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("kill") => {
            for name in syd::config::KILL_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("mount") => {
            for name in syd::config::MOUNT_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("msgqueue") => {
            for name in syd::config::MSGQUEUE_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("nice") => {
            for name in syd::config::NICE_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("numa") => {
            for name in syd::config::NUMA_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("page_cache") => {
            for name in syd::config::PAGE_CACHE_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("perf") => {
            for name in syd::config::PERF_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("pkey") => {
            for name in syd::config::PKEY_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("ptrace") => {
            for name in syd::config::PTRACE_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("safe") | Some("allow") => {
            for name in syd::config::SAFE_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("setid") => {
            for name in syd::config::SET_ID_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("shm") => {
            for name in syd::config::SHM_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("time") => {
            for name in syd::config::TIME_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("uring") => {
            for name in syd::config::IOURING_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("uts") => {
            for name in syd::config::UTS_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("wordexp") => {
            for name in syd::config::WORDEXP_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("env") => {
            for env in syd::config::UNSAFE_ENV {
                let env = mask_path(Path::new(OsStr::from_bytes(env)));
                printfln!("{env}")?;
            }
        }
        Some("fs") => {
            for (fstype, fstype_id) in syd::config::FS_MAGIC {
                printfln!("{fstype_id:#x}\t{fstype}")?;
            }
        }
        Some("personality") => {
            for (name, _) in syd::config::SAFE_PERSONAS {
                printfln!("{name}")?;
            }
        }
        Some("madvise") => {
            for (name, _) in syd::config::ALLOW_MADVISE {
                printfln!("{name}")?;
            }
        }
        Some("fcntl") => {
            for (name, _) in syd::config::DENY_FCNTL {
                printfln!("{name}")?;
            }
        }
        Some("prctl") => {
            for (name, _) in syd::config::ALLOW_PRCTL {
                printfln!("{name}")?;
            }
        }
        Some("setsockopt") => {
            for level in [libc::SOL_ALG]
                .iter()
                .chain(syd::config::UNSAFE_SETSOCKOPT)
                .chain(syd::config::UNSUPP_SETSOCKOPT)
            {
                printfln!("{level:#x}:*")?;
            }
            for (level, optname) in syd::config::DENY_SETSOCKOPT {
                printfln!("{level:#x}:{optname:#x}")?;
            }
        }
        // Syscall sets for Syd threads, undocumented.
        Some("syd_emu") => {
            for name in syd::config::EMU_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("syd_int") => {
            for name in syd::config::INT_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("syd_out") => {
            for name in syd::config::OUT_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("syd_ipc") => {
            for name in syd::config::IPC_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("syd_aes") => {
            for name in syd::config::AES_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("syd_main" | "syd_run") => {
            for name in syd::config::MAIN_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some("syd_oci") => {
            for name in syd::config::OCI_SYSCALLS {
                printfln!("{name}")?;
            }
        }
        Some(set) => {
            eprintfln!("No such set: '{set}'")?;
            return Ok(ExitCode::FAILURE);
        }
    }

    Ok(ExitCode::SUCCESS)
}

fn readdir_cwd() -> SydResult<()> {
    // Open a file descriptor to the current directory.
    #[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, // EOF or empty directory
            Err(errno) => return Err(errno.into()),
        };

        for entry in &mut entries {
            // SAFETY: Hex-encode filename to mitigate terminal vulnerabilities.
            let name = hex_encode_lower(entry.name_bytes())?;
            printfln!("{name}")?;

            if report_progress {
                count = count.saturating_add(1);
                if count % 25000 == 0 {
                    eprintf!("\r\x1b[Ksyd-ls: {count} files")?;
                }
            }
        }
    }

    if let Some(epoch) = epoch {
        let dur = epoch.elapsed().as_secs_f64();
        eprintfln!("\r\x1b[Ksyd-ls: Listed {count} files in {dur} seconds.")?;
    }

    Ok(())
}