syd 3.52.0

rock-solid application kernel
Documentation
//
// Syd: rock-solid application kernel
// src/kernel/link.rs: symlink(2) and symlinkat(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, NixPath};

use crate::{
    cookie::safe_symlinkat,
    kernel::sandbox_path,
    lookup::FsFlags,
    req::{RemoteProcess, SysArg, UNotifyEventRequest},
    sandbox::Capability,
};

pub(crate) fn sys_symlink(request: UNotifyEventRequest) -> ScmpNotifResp {
    syscall_handler!(request, |request: UNotifyEventRequest| {
        let arg = SysArg {
            path: Some(1),
            fsflags: FsFlags::NO_FOLLOW_LAST | FsFlags::MISS_LAST | FsFlags::DOTLAST_EEXIST,
            ..Default::default()
        };
        syscall_symlink_handler(request, arg)
    })
}

pub(crate) fn sys_symlinkat(request: UNotifyEventRequest) -> ScmpNotifResp {
    syscall_handler!(request, |request: UNotifyEventRequest| {
        let arg = SysArg {
            dirfd: Some(1),
            path: Some(2),
            fsflags: FsFlags::NO_FOLLOW_LAST | FsFlags::MISS_LAST | FsFlags::DOTLAST_EEXIST,
            ..Default::default()
        };
        syscall_symlink_handler(request, arg)
    })
}

// A helper function to handle symlink{,at} syscalls.
fn syscall_symlink_handler(
    request: UNotifyEventRequest,
    arg: SysArg,
) -> Result<ScmpNotifResp, Errno> {
    let req = request.scmpreq;

    let process = RemoteProcess::new(request.scmpreq.pid());

    // Read remote path, request will be validated by remote_path.
    let target = process.remote_path(req.data.arch, req.data.args[0], Some(&request))?;

    // symlink() returns ENOENT if target is an empty string.
    if target.is_empty() {
        return Err(Errno::ENOENT);
    }

    // Read remote path.
    let sandbox = request.get_sandbox();
    let (path, _, _) = request.read_path(&sandbox, arg)?;

    // Check for access.
    let name = if arg.dirfd.is_some() {
        "symlinkat"
    } else {
        "symlink"
    };
    sandbox_path(
        Some(&request),
        &sandbox,
        request.scmpreq.pid(), // Unused when request.is_some()
        path.abs(),
        Capability::CAP_SYMLINK,
        name,
    )?;
    drop(sandbox); // release the read-lock.

    // symlink(2) doesn't follow dangling symlinks.
    // Return EEXIST if path already exists.
    if path.typ.is_some() {
        return Err(Errno::EEXIST);
    }

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

    // All done, call underlying system call.
    let result = safe_symlinkat(&target, path.dir(), path.base());

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

    result.map(|_| request.return_syscall(0))
}