syd/
cache.rs

1//
2// Syd: rock-solid application kernel
3// src/hash.rs: Utilities for caching
4//
5// Copyright (c) 2024, 2025 Ali Polatel <alip@chesswob.org>
6//
7// SPDX-License-Identifier: GPL-3.0
8
9// SAFETY: This module has been liberated from unsafe code!
10#![forbid(unsafe_code)]
11
12use std::{
13    fs::File,
14    os::fd::OwnedFd,
15    sync::{Arc, Condvar, Mutex, RwLock},
16};
17
18use ahash::{HashMapExt, HashSetExt};
19use libseccomp::ScmpSyscall;
20use nix::{errno::Errno, sys::socket::UnixAddr, unistd::Pid};
21use serde::{ser::SerializeMap, Serializer};
22
23use crate::{
24    confine::{ScmpNotifReq, SydArch},
25    elf::ExecutableFile,
26    hash::{SydHashMap, SydHashSet},
27    sigset::SydSigSet,
28};
29
30/// Metadata on a blocking syscall invocation
31#[derive(Debug)]
32pub(crate) struct SysInterrupt {
33    /// Syd handler thread ID
34    pub(crate) handler: Pid,
35    /// System call request
36    pub(crate) request: ScmpNotifReq,
37    /// proc_pid_status(5) file handle
38    pub(crate) status: Option<OwnedFd>,
39    /// Used by syd_emu to signal syd_int to delete the entry and close the file.
40    /// This is because the status file descriptor is not valid in syd_emu's fs space.
41    pub(crate) delete: bool,
42    /// Used by syd_mon to signal syd_int to signal stuck emulators manually,
43    /// when not enough resources are available to spawn new emulator threads.
44    /// This is because the status file descriptor is not valid in syd_mon's fs space.
45    pub(crate) signal: bool,
46    /// True if `SA_RESTART` is ignored
47    /// (e.g. due to a socket timeout).
48    pub(crate) ignore_restart: bool,
49}
50
51/// Map of metadata on blocking syscall invocations.
52pub(crate) type BlockVec = Vec<SysInterrupt>;
53
54/// Map of restarting signals by TGID.
55pub(crate) type RestartMap = SydHashMap<Pid, SydSigSet>;
56
57/// This is the data type used to handle syscall interrupts.
58#[derive(Debug)]
59pub(crate) struct SysInterruptMap {
60    /// Map of blocking syscalls by request id.
61    pub(crate) sys_block: Arc<(Mutex<BlockVec>, Condvar)>,
62    /// Map of restarting signals by TGID.
63    /// Used for SA_RESTART tracking.
64    pub(crate) sig_restart: Arc<Mutex<RestartMap>>,
65}
66
67/// Represents an exec(3) check result
68#[derive(Debug)]
69pub(crate) struct ExecResult {
70    pub(crate) exe: ExecutableFile,
71    pub(crate) file: File,
72}
73
74/// Syscall-agnostic error map.
75pub(crate) type ErrorMap = SydHashMap<Pid, Option<Errno>>;
76
77/// chdir(2) result set.
78pub(crate) type ChdirSet = SydHashSet<Pid>;
79
80/// exec(3) result map.
81pub(crate) type ExecvMap = SydHashMap<Pid, ExecResult>;
82
83/// mmap(2) pid set.
84pub(crate) type MmapSet = SydHashSet<Pid>;
85
86// [inode,(pid,path)] map of unix binds.
87// Path is only used for UNIX domain sockets.
88//
89// SAFETY:
90// 1. /proc/net/unix only gives inode information,
91//    and does not include information on device id
92//    or mount id so unfortunately we cannot check
93//    for that here.
94// 2. Pid is used for SO_PEERCRED getsockopt(2).
95#[derive(Copy, Clone)]
96pub(crate) struct UnixVal {
97    pub(crate) pid: Pid,
98    pub(crate) addr: Option<UnixAddr>,
99    pub(crate) peer: Option<UnixAddr>,
100}
101pub(crate) type UnixMap = Arc<RwLock<SydHashMap<u64, UnixVal>>>;
102
103// [tid, tgid] map for ptrace(PTRACE_TRACEME) calling tids.
104// This is used to prevent ptrace(2) detection efficiently.
105pub(crate) type PtraceMap = Arc<RwLock<SydHashMap<Pid, Pid>>>;
106
107/// Results map for ptrace(2) hooks chdir, execve, sigaction and sigreturn.
108#[derive(Debug)]
109pub(crate) struct SysResultMap {
110    /// syscall-agnostic error map
111    pub(crate) trace_error: Arc<Mutex<ErrorMap>>,
112    /// chdir(2) result map
113    pub(crate) trace_chdir: Arc<Mutex<ChdirSet>>,
114    /// exec(3) result map
115    pub(crate) trace_execv: Arc<Mutex<ExecvMap>>,
116    /// mmap(2) pid set.
117    pub(crate) trace_mmap: Arc<Mutex<MmapSet>>,
118}
119
120/// Map of TGIDs that have received count signals for handled signals.
121pub(crate) type SighandleMap = SydHashMap<Pid, u64>;
122
123/// Signal map, used by signal counting for SROP mitigation:
124/// If a TGID is not in sig_handle_map at the entry of sigreturn(2),
125/// we terminate the process because the sigreturn(2) is artificial.
126#[derive(Debug)]
127pub(crate) struct SignalMap {
128    /// Set of TGIDs that have received count signals for handled signals.
129    pub(crate) sig_handle: Arc<Mutex<SighandleMap>>,
130}
131
132impl SysInterrupt {
133    pub(crate) fn new(
134        request: ScmpNotifReq,
135        handler: Pid,
136        ignore_restart: bool,
137    ) -> Result<Self, Errno> {
138        Ok(Self {
139            handler,
140            request,
141            ignore_restart,
142            status: None,
143            delete: false,
144            signal: false,
145        })
146    }
147
148    // Marks the interrupt for deletion as needed.
149    //
150    // Returns true if drop should be handled by syd_int.
151    pub(crate) fn delete(&mut self) -> bool {
152        // interrupt.status is Some if syd_int thread
153        // has already opened proc_pid_status(5), in
154        // which case we let it close the file because
155        // the file descriptor is not valid in syd_emu's
156        // FS space.
157        if self.status.is_some() {
158            self.delete = true;
159            true // syd_int drops interrupt.
160        } else {
161            false // syd_emu drops interrupt.
162        }
163    }
164}
165
166impl serde::Serialize for SysInterrupt {
167    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
168    where
169        S: Serializer,
170    {
171        let mut map = serializer.serialize_map(Some(3))?;
172
173        let data = &self.request.data;
174        let syscall = ScmpSyscall::get_name_by_arch(data.syscall, data.arch)
175            .unwrap_or_else(|_| format!("{}", i32::from(data.syscall)));
176        let _ = map.serialize_entry("pid", &self.request.pid);
177        let _ = map.serialize_entry("sys", &syscall);
178        let _ = map.serialize_entry("arch", &SydArch::from(data.arch));
179        let _ = map.serialize_entry("args", &data.args);
180        let _ = map.serialize_entry("handler", &self.handler.as_raw());
181        let _ = map.serialize_entry("ignore_restart", &self.ignore_restart);
182
183        map.end()
184    }
185}
186
187/// Create a new UnixMap.
188pub(crate) fn unix_map_new() -> UnixMap {
189    Arc::new(RwLock::new(SydHashMap::default()))
190}
191
192/// Create a new PtraceMap.
193pub(crate) fn ptrace_map_new() -> PtraceMap {
194    Arc::new(RwLock::new(SydHashMap::default()))
195}
196
197/// Create a new SysInterruptMap.
198pub(crate) fn sys_interrupt_map_new() -> SysInterruptMap {
199    SysInterruptMap {
200        sys_block: Arc::new((Mutex::new(BlockVec::new()), Condvar::new())),
201        sig_restart: Arc::new(Mutex::new(RestartMap::new())),
202    }
203}
204
205/// Create a new SysResultMap.
206pub(crate) fn sys_result_map_new() -> SysResultMap {
207    SysResultMap {
208        trace_error: Arc::new(Mutex::new(ErrorMap::new())),
209        trace_chdir: Arc::new(Mutex::new(ChdirSet::new())),
210        trace_execv: Arc::new(Mutex::new(ExecvMap::new())),
211        trace_mmap: Arc::new(Mutex::new(MmapSet::new())),
212    }
213}
214
215/// Create a new SignalMap.
216pub(crate) fn signal_map_new() -> SignalMap {
217    SignalMap {
218        sig_handle: Arc::new(Mutex::new(SighandleMap::new())),
219    }
220}