syd 3.52.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},
    fd::is_valid_fd,
    kernel::syscall_path_handler,
    lookup::FsFlags,
    req::{SysArg, 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, "statfs", 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, "statfs64", 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),
        ..Default::default()
    }];

    syscall_statfs_handler(request, "fstatfs", 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),
        ..Default::default()
    }];

    syscall_statfs_handler(request, "fstatfs64", argv, 2, true)
}

fn syscall_statfs_handler(
    request: UNotifyEventRequest,
    syscall_name: &str,
    argv: &[SysArg],
    arg_statfs: usize,
    compat64: bool,
) -> ScmpNotifResp {
    syscall_path_handler(
        request,
        syscall_name,
        argv,
        |path_args, request, sandbox| {
            let req = request.scmpreq;
            drop(sandbox); // release the 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);
            }

            // Record blocking call so it can get invalidated.
            request.cache.add_sys_block(req, false)?;

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

            // Remove invalidation record.
            request.cache.del_sys_block(req.id)?;

            // Check for errors after critical section.
            let result = result?;

            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
    }
}