syd 3.55.0

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

use libseccomp::{ScmpArch, ScmpNotifResp};
use nix::errno::Errno;

use crate::{
    compat::{fstatfs64, statfs32, statfs64, Statfs},
    confine::{
        is_valid_ptr, scmp_arch_is_compat_long32, SydSys,
        SydSys::{SysFstatfs, SysFstatfs64, SysStatfs, SysStatfs64},
    },
    fd::is_valid_fd,
    kernel::syscall_path_handler,
    lookup::FsFlags,
    req::{SysArg, SysFlags, UNotifyEventRequest},
};

pub(crate) fn sys_statfs(request: UNotifyEventRequest) -> ScmpNotifResp {
    let argv = &[SysArg {
        path: Some(0),
        fsflags: FsFlags::MUST_PATH,
        ..Default::default()
    }];

    syscall_statfs_handler(request, SysStatfs, argv, 1, false)
}

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

    // If second argument isn't a valid size, return EINVAL.
    let is32 = scmp_arch_is_compat_long32(req.data.arch);
    let size = if is32 {
        size_of::<statfs64>()
    } else {
        size_of::<Statfs>()
    };
    let sz = match usize::try_from(req.data.args[1]) {
        Ok(sz) => arm_oabi_statfs64_size_fixup(req.data.arch, sz),
        Err(_) => return request.fail_syscall(Errno::EINVAL),
    };
    if sz != size {
        return request.fail_syscall(Errno::EINVAL);
    }

    let argv = &[SysArg {
        path: Some(0),
        fsflags: FsFlags::MUST_PATH,
        ..Default::default()
    }];

    syscall_statfs_handler(request, SysStatfs64, argv, 2, true)
}

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

    // FD-only call:
    // 1. Assert valid fd before other arguments.
    // 2. AT_FDCWD is an invalid fd argument.
    if !is_valid_fd(req.data.args[0]) {
        return request.fail_syscall(Errno::EBADF);
    }

    let argv = &[SysArg {
        dirfd: Some(0),
        flags: SysFlags::PASS_DELETE,
        ..Default::default()
    }];

    syscall_statfs_handler(request, SysFstatfs, argv, 1, false)
}

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

    // FD-only call:
    // 1. Assert valid size before other arguments.
    // 2. Assert valid fd before other arguments.
    // 3. AT_FDCWD is an invalid fd argument.
    let is32 = scmp_arch_is_compat_long32(req.data.arch);
    let size = if is32 {
        size_of::<statfs64>()
    } else {
        size_of::<Statfs>()
    };
    let sz = match usize::try_from(req.data.args[1]) {
        Ok(sz) => arm_oabi_statfs64_size_fixup(req.data.arch, sz),
        Err(_) => return request.fail_syscall(Errno::EINVAL),
    };
    if sz != size {
        return request.fail_syscall(Errno::EINVAL);
    }

    if !is_valid_fd(req.data.args[0]) {
        return request.fail_syscall(Errno::EBADF);
    }

    let argv = &[SysArg {
        dirfd: Some(0),
        flags: SysFlags::PASS_DELETE,
        ..Default::default()
    }];

    syscall_statfs_handler(request, SysFstatfs64, argv, 2, true)
}

fn syscall_statfs_handler(
    request: UNotifyEventRequest,
    syscall: SydSys,
    argv: &[SysArg],
    arg_statfs: usize,
    compat64: bool,
) -> ScmpNotifResp {
    syscall_path_handler(request, syscall, argv, |path_args, request, sandbox| {
        let req = request.scmpreq;
        drop(sandbox); // release read lock.

        // SysArg has one element.
        #[expect(clippy::disallowed_methods)]
        let fd = path_args.0.as_ref().unwrap().path.dir();

        // Check for invalid buffer pointer after path lookup.
        let addr = req.data.args[arg_statfs];
        if !is_valid_ptr(addr, req.data.arch) {
            return Err(Errno::EFAULT);
        }

        // All done, call underlying system call.
        let result = fstatfs64(fd)?;

        let is32 = scmp_arch_is_compat_long32(req.data.arch);
        if is32 && compat64 {
            let statfs64: statfs64 = result.into();

            // SAFETY: statfs64 is repr(C, packed).
            let statfs = unsafe {
                std::slice::from_raw_parts(
                    std::ptr::addr_of!(statfs64).cast::<u8>(),
                    size_of_val(&statfs64),
                )
            };

            request.write_mem_all(statfs, addr)?;
        } else if is32 {
            let statfs32: statfs32 = result.try_into()?;

            // SAFETY: statfs32 is repr(C).
            let statfs = unsafe {
                std::slice::from_raw_parts(
                    std::ptr::addr_of!(statfs32).cast::<u8>(),
                    size_of_val(&statfs32),
                )
            };

            request.write_mem_all(statfs, addr)?;
        } else {
            // SAFETY: Statfs is repr(C).
            let statfs = unsafe {
                std::slice::from_raw_parts(
                    std::ptr::addr_of!(result).cast::<u8>(),
                    size_of_val(&result),
                )
            };

            request.write_mem_all(statfs, addr)?;
        }

        Ok(request.return_syscall(0))
    })
}

// Apply Linux's ARM "OABI compat fixup" to the size argument of statfs64/fstatfs64.
fn arm_oabi_statfs64_size_fixup(arch: ScmpArch, sz: usize) -> usize {
    if arch == ScmpArch::Arm && sz == 88 {
        84
    } else {
        sz
    }
}