syd 3.52.0

rock-solid application kernel
Documentation
//
// Syd: rock-solid application kernel
// src/kernel/access.rs: access(2), faccessat(2) and faccessat2(2) handlers
//
// Copyright (c) 2023, 2024, 2025, 2026 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

// SAFETY: This module has been liberated from unsafe code!
#![forbid(unsafe_code)]

use libseccomp::ScmpNotifResp;
use nix::{errno::Errno, fcntl::AtFlags, unistd::AccessFlags, NixPath};

use crate::{
    cookie::safe_faccess,
    kernel::{syscall_path_handler, to_atflags},
    lookup::FsFlags,
    req::{PathArgs, SysArg, SysFlags, UNotifyEventRequest},
};

pub(crate) fn sys_access(request: UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.scmpreq;

    // Reject undefined/invalid/unused mode.
    let mode = match to_accessflags(req.data.args[1]) {
        Ok(mode) => mode,
        Err(errno) => return request.fail_syscall(errno),
    };

    let argv = &[SysArg {
        path: Some(0),
        flags: SysFlags::CHECK_MAGIC,
        ..Default::default()
    }];
    syscall_path_handler(request, "access", argv, |path_args, request, sandbox| {
        drop(sandbox); // release the read-lock.
        syscall_access_handler(request, path_args, mode, AtFlags::empty())
    })
}

pub(crate) fn sys_faccessat(request: UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.scmpreq;

    // Reject undefined/invalid/unused mode.
    let mode = match to_accessflags(req.data.args[2]) {
        Ok(mode) => mode,
        Err(errno) => return request.fail_syscall(errno),
    };

    let argv = &[SysArg {
        dirfd: Some(0),
        path: Some(1),
        flags: SysFlags::CHECK_MAGIC,
        ..Default::default()
    }];
    syscall_path_handler(request, "faccessat", argv, |path_args, request, sandbox| {
        drop(sandbox); // release the read-lock.
        syscall_access_handler(request, path_args, mode, AtFlags::empty())
    })
}

pub(crate) fn sys_faccessat2(request: UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.scmpreq;

    // Reject undefined/invalid/unused flags.
    let flags = match to_atflags(
        req.data.args[3],
        crate::compat::AT_EACCESS | AtFlags::AT_EMPTY_PATH | AtFlags::AT_SYMLINK_NOFOLLOW,
    ) {
        Ok(flags) => flags,
        Err(errno) => return request.fail_syscall(errno),
    };

    // Reject undefined/invalid/unused mode.
    let mode = match to_accessflags(req.data.args[2]) {
        Ok(mode) => mode,
        Err(errno) => return request.fail_syscall(errno),
    };

    let mut fsflags = FsFlags::MUST_PATH;
    if flags.contains(AtFlags::AT_SYMLINK_NOFOLLOW) {
        fsflags.insert(FsFlags::NO_FOLLOW_LAST);
    }

    let argv = &[SysArg {
        dirfd: Some(0),
        path: Some(1),
        flags: if flags.contains(AtFlags::AT_EMPTY_PATH) {
            SysFlags::EMPTY_PATH
        } else {
            SysFlags::CHECK_MAGIC
        },
        fsflags,
    }];
    syscall_path_handler(
        request,
        "faccessat2",
        argv,
        |path_args, request, sandbox| {
            drop(sandbox); // release the read-lock.
            syscall_access_handler(request, path_args, mode, flags)
        },
    )
}

// Helper to handle access(2), faccessat(2), and faccessat2(2) syscalls.
fn syscall_access_handler(
    request: &UNotifyEventRequest,
    args: PathArgs,
    mode: AccessFlags,
    flags: AtFlags,
) -> Result<ScmpNotifResp, Errno> {
    // SysArg has one element.
    // We use MUST_PATH, dir refers to the file.
    #[expect(clippy::disallowed_methods)]
    let path = &args.0.as_ref().unwrap().path;
    assert!(path.base().is_empty()); // MUST_PATH!

    safe_faccess(path.dir(), mode, flags).map(|_| request.return_syscall(0))
}

fn to_accessflags(arg: u64) -> Result<AccessFlags, Errno> {
    // Linux kernel truncates upper bits.
    #[expect(clippy::cast_possible_truncation)]
    AccessFlags::from_bits(arg as i32).ok_or(Errno::EINVAL)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_to_accessflags_zero_1() {
        // F_OK = 0: check for file existence
        let flags = to_accessflags(0).unwrap();
        assert_eq!(flags, AccessFlags::empty());
    }

    #[test]
    fn test_to_accessflags_r_ok_1() {
        let flags = to_accessflags(libc::R_OK as u64).unwrap();
        assert!(flags.contains(AccessFlags::R_OK));
    }

    #[test]
    fn test_to_accessflags_w_ok_1() {
        let flags = to_accessflags(libc::W_OK as u64).unwrap();
        assert!(flags.contains(AccessFlags::W_OK));
    }

    #[test]
    fn test_to_accessflags_x_ok_1() {
        let flags = to_accessflags(libc::X_OK as u64).unwrap();
        assert!(flags.contains(AccessFlags::X_OK));
    }

    #[test]
    fn test_to_accessflags_rwx_1() {
        let arg = (libc::R_OK | libc::W_OK | libc::X_OK) as u64;
        let flags = to_accessflags(arg).unwrap();
        assert!(flags.contains(AccessFlags::R_OK));
        assert!(flags.contains(AccessFlags::W_OK));
        assert!(flags.contains(AccessFlags::X_OK));
    }

    #[test]
    fn test_to_accessflags_invalid_1() {
        // Unknown bit => EINVAL
        let result = to_accessflags(0x80);
        assert_eq!(result, Err(Errno::EINVAL));
    }
}