syd/kernel/
utime.rs

1//
2// Syd: rock-solid application kernel
3// src/kernel/utime.rs: utime handlers
4//
5// Copyright (c) 2023, 2024, 2025 Ali Polatel <alip@chesswob.org>
6//
7// SPDX-License-Identifier: GPL-3.0
8
9use std::os::fd::AsRawFd;
10
11use libseccomp::ScmpNotifResp;
12use nix::{errno::Errno, fcntl::AtFlags, sys::time::TimeSpec};
13
14use crate::{
15    confine::scmp_arch_bits,
16    kernel::{syscall_path_handler, to_atflags},
17    lookup::FsFlags,
18    req::{PathArgs, SysArg, SysFlags, UNotifyEventRequest},
19};
20
21pub(crate) fn sys_utime(request: UNotifyEventRequest) -> ScmpNotifResp {
22    let argv = &[SysArg {
23        path: Some(0),
24        fsflags: FsFlags::MUST_PATH,
25        ..Default::default()
26    }];
27
28    syscall_path_handler(request, "utime", argv, |path_args, request, sandbox| {
29        drop(sandbox); // release the read-lock.
30
31        let req = request.scmpreq;
32        let (atime, mtime) = request.remote_utimbuf(req.data.args[1])?;
33        syscall_utime_handler(request, path_args, &atime, &mtime)
34    })
35}
36
37pub(crate) fn sys_utimes(request: UNotifyEventRequest) -> ScmpNotifResp {
38    let argv = &[SysArg {
39        path: Some(0),
40        fsflags: FsFlags::MUST_PATH,
41        ..Default::default()
42    }];
43
44    syscall_path_handler(request, "utime", argv, |path_args, request, sandbox| {
45        drop(sandbox); // release the read-lock.
46
47        let req = request.scmpreq;
48        let (atime, mtime) = request.remote_utimbuf(req.data.args[1])?;
49        syscall_utime_handler(request, path_args, &atime, &mtime)
50    })
51}
52
53pub(crate) fn sys_futimesat(request: UNotifyEventRequest) -> ScmpNotifResp {
54    let req = request.scmpreq;
55
56    let fsflags = FsFlags::MUST_PATH;
57    let path = if req.data.args[1] != 0 { Some(1) } else { None };
58
59    let argv = &[SysArg {
60        dirfd: Some(0),
61        path,
62        fsflags,
63        ..Default::default()
64    }];
65
66    syscall_path_handler(request, "futimesat", argv, |path_args, request, sandbox| {
67        drop(sandbox); // release the read-lock.
68
69        let (atime, mtime) = request.remote_timeval(req.data.args[2])?;
70        syscall_utime_handler(request, path_args, &atime, &mtime)
71    })
72}
73
74pub(crate) fn sys_utimensat(request: UNotifyEventRequest) -> ScmpNotifResp {
75    let req = request.scmpreq;
76
77    // SAFETY: Reject undefined/invalid flags.
78    let atflags = match to_atflags(
79        req.data.args[3],
80        AtFlags::AT_EMPTY_PATH | AtFlags::AT_SYMLINK_NOFOLLOW,
81    ) {
82        Ok(atflags) => atflags,
83        Err(errno) => return request.fail_syscall(errno),
84    };
85
86    let mut flags = SysFlags::empty();
87    let mut fsflags = FsFlags::MUST_PATH;
88    if atflags.contains(AtFlags::AT_EMPTY_PATH) {
89        flags |= SysFlags::EMPTY_PATH;
90    }
91    if atflags.contains(AtFlags::AT_SYMLINK_NOFOLLOW) {
92        fsflags |= FsFlags::NO_FOLLOW_LAST;
93    }
94
95    let argv = &[SysArg {
96        dirfd: Some(0),
97        path: if req.data.args[1] != 0 { Some(1) } else { None },
98        flags,
99        fsflags,
100        ..Default::default()
101    }];
102
103    syscall_path_handler(request, "utimensat", argv, |path_args, request, sandbox| {
104        drop(sandbox); // release the read-lock.
105
106        let addr = req.data.args[2];
107        let is32 = scmp_arch_bits(req.data.arch) == 32;
108
109        let (atime, mtime) = if is32 {
110            request.remote_timespec32_2(addr)
111        } else {
112            request.remote_timespec64_2(addr)
113        }?;
114
115        syscall_utime_handler(request, path_args, &atime, &mtime)
116    })
117}
118
119pub(crate) fn sys_utimensat64(request: UNotifyEventRequest) -> ScmpNotifResp {
120    let req = request.scmpreq;
121
122    // SAFETY: Reject undefined/invalid flags.
123    let atflags = match to_atflags(
124        req.data.args[3],
125        AtFlags::AT_EMPTY_PATH | AtFlags::AT_SYMLINK_NOFOLLOW,
126    ) {
127        Ok(atflags) => atflags,
128        Err(errno) => return request.fail_syscall(errno),
129    };
130
131    let mut flags = SysFlags::empty();
132    let mut fsflags = FsFlags::MUST_PATH;
133    if atflags.contains(AtFlags::AT_EMPTY_PATH) {
134        flags |= SysFlags::EMPTY_PATH;
135    }
136    if atflags.contains(AtFlags::AT_SYMLINK_NOFOLLOW) {
137        fsflags |= FsFlags::NO_FOLLOW_LAST;
138    }
139
140    let argv = &[SysArg {
141        dirfd: Some(0),
142        path: if req.data.args[1] != 0 { Some(1) } else { None },
143        flags,
144        fsflags,
145        ..Default::default()
146    }];
147    syscall_path_handler(
148        request,
149        "utimensat_time64",
150        argv,
151        |path_args, request, sandbox| {
152            drop(sandbox); // release the read-lock.
153
154            let (atime, mtime) = request.remote_timespec64_2(req.data.args[2])?;
155            syscall_utime_handler(request, path_args, &atime, &mtime)
156        },
157    )
158}
159
160/// A helper function to handle utime* syscalls.
161fn syscall_utime_handler(
162    request: &UNotifyEventRequest,
163    args: PathArgs,
164    atime: &TimeSpec,
165    mtime: &TimeSpec,
166) -> Result<ScmpNotifResp, Errno> {
167    // SAFETY: SysArg has one element.
168    #[expect(clippy::disallowed_methods)]
169    let path = args.0.as_ref().unwrap();
170
171    let fd = path
172        .dir
173        .as_ref()
174        .map(|fd| fd.as_raw_fd())
175        .ok_or(Errno::EBADF)?;
176    let times: [libc::timespec; 2] = [*atime.as_ref(), *mtime.as_ref()];
177
178    // SAFETY:
179    // 1. After this point we are not permitted to resolve
180    //    symbolic links any longer or else we risk TOCTOU.
181    // 2. nix does not define AT_EMPTY_PATH in `UtimensatFlags`,
182    //    so we have to use libc instead.
183    Errno::result(unsafe {
184        libc::utimensat(
185            fd,
186            c"".as_ptr().cast(),
187            &raw const times[0],
188            libc::AT_EMPTY_PATH,
189        )
190    })
191    .map(|_| request.return_syscall(0))
192}