Skip to main content

assay_common/
lib.rs

1#![no_std]
2#![allow(unsafe_code)]
3
4#[cfg(feature = "std")]
5extern crate std;
6
7#[cfg(feature = "std")]
8pub mod exports;
9
10pub const EVENT_OPENAT: u32 = 1;
11pub const EVENT_CONNECT: u32 = 2;
12pub const EVENT_FORK: u32 = 3;
13pub const EVENT_EXEC: u32 = 4;
14pub const EVENT_EXIT: u32 = 5;
15
16pub const KEY_MONITOR_ALL: u32 = 100;
17
18pub const EVENT_FILE_BLOCKED: u32 = 10;
19pub const EVENT_CONNECT_BLOCKED: u32 = 20;
20
21pub const DATA_LEN: usize = 512;
22
23#[repr(C)]
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25pub struct MonitorEvent {
26    pub pid: u32,
27    pub event_type: u32,
28    pub data: [u8; DATA_LEN],
29}
30
31/// Key used to identify an inode in BPF maps.
32///
33/// # ABI and kernel assumptions
34///
35/// This struct is `#[repr(C)]` and used across the eBPF/userspace boundary,
36/// so its layout must remain in sync with the corresponding kernel/BPF-side
37/// definition.
38///
39/// The `dev` field stores the kernel's encoded `dev_t` value (e.g. from
40/// `super_block.s_dev`) as a `u32`. On modern Linux kernels, this uses the
41/// `new_encode_dev()` format:
42///   (minor & 0xff) | ((major & 0xfff) << 8) | ((minor & 0xfffff00) << 12)
43/// This matches `sb->s_dev` seen by eBPF.
44#[repr(C)]
45#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
46pub struct InodeKey {
47    pub ino: u64,
48    pub dev: u32,
49    pub gen: u32,
50}
51
52/// Explicit 16-byte key for BPF Map Lookups (ino + dev + gen).
53/// Layout: | ino (8) | dev (4) | gen (4) | = 16 bytes.
54/// Guarantees dense packing without padding issues.
55#[repr(C)]
56#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
57pub struct InodeKeyMap {
58    pub ino: u64,
59    pub dev: u32,
60    pub gen: u32,
61}
62
63/// Encode userspace dev_t into Linux kernel's `new_encode_dev()` format (sb->s_dev).
64/// This matches include/linux/kdev_t.h:
65///   new_encode_dev(MKDEV(major,minor)) for 32-bit dev_t encoding used in-kernel.
66#[cfg(target_os = "linux")]
67pub fn encode_kernel_dev(dev: u64) -> u32 {
68    // Replicate Linux/glibc major()/minor() from <sys/sysmacros.h>
69    // major(dev) = ((dev >> 8) & 0xfff)
70    // minor(dev) = (dev & 0xff) | ((dev >> 12) & 0xfff00)
71    let major = ((dev >> 8) & 0xfff) as u32;
72    let minor = ((dev & 0xff) | ((dev >> 12) & 0xfff00)) as u32;
73
74    // new_encode_dev:
75    // (minor & 0xff) | ((major & 0xfff) << 8) | ((minor & 0xfffff00) << 12)
76    (minor & 0xff) | ((major & 0xfff) << 8) | ((minor & 0xfffff00) << 12)
77}
78
79// Event ID for Inode Resolution telemetry
80pub const EVENT_INODE_RESOLVED: u32 = 112;
81
82// Shared ABI struct for Event 112
83#[repr(C)]
84#[derive(Clone, Copy, Debug)]
85pub struct InodeResolved {
86    pub dev: u64, // s_dev cast to u64
87    pub ino: u64, // i_ino
88    pub gen: u32, // i_generation
89    pub _pad: u32,
90}
91
92#[cfg(all(target_os = "linux", feature = "user"))]
93unsafe impl aya::Pod for InodeResolved {}
94
95#[cfg(all(target_os = "linux", feature = "user"))]
96unsafe impl aya::Pod for InodeKeyMap {}
97
98#[cfg(all(target_os = "linux", feature = "user"))]
99unsafe impl aya::Pod for InodeKey {}
100
101#[cfg(all(target_os = "linux", feature = "user"))]
102const _: () = {
103    fn _assert_pod<T: aya::Pod>() {}
104    fn _check() {
105        _assert_pod::<InodeKeyMap>();
106    }
107};
108
109impl MonitorEvent {
110    pub const fn zeroed() -> Self {
111        Self {
112            pid: 0,
113            event_type: 0,
114            data: [0u8; DATA_LEN],
115        }
116    }
117}
118
119impl Default for MonitorEvent {
120    fn default() -> Self {
121        Self::zeroed()
122    }
123}
124
125// -----------------------------
126// Compile-time ABI/layout checks
127// -----------------------------
128
129// Exact size: 4 + 4 + 512 = 520 bytes
130const _: [(); 520] = [(); core::mem::size_of::<MonitorEvent>()];
131
132// Alignment should be 4 on all sane targets; if this fails, your ABI is different.
133const _: [(); 4] = [(); core::mem::align_of::<MonitorEvent>()];
134
135#[cfg(all(target_os = "linux", feature = "std"))]
136pub fn get_inode_generation(fd: std::os::fd::RawFd) -> std::io::Result<u32> {
137    use nix::libc;
138    use nix::request_code_read;
139
140    // FS_IOC_GETVERSION is defined as _IOR('v', 1, long) in uapi/linux/fs.h
141    // Kernel returns i_generation (32-bit value).
142    // Best-effort: read into libc::c_long and cast to u32.
143
144    // build ioctl request code: _IOR('v', 1, long)
145    const fn fs_ioc_getversion() -> libc::c_ulong {
146        request_code_read!(b'v', 1, core::mem::size_of::<libc::c_long>()) as libc::c_ulong
147    }
148
149    let mut out: libc::c_long = 0;
150    // Safety: ioctl is unsafe, passing valid fd and pointer to initialized memory (0)
151    // Cast request code to whatever the platform libc expects (c_ulong or c_int)
152    let rc = unsafe { libc::ioctl(fd, fs_ioc_getversion() as _, &mut out) };
153    if rc < 0 {
154        return Err(std::io::Error::last_os_error());
155    }
156    Ok(out as u32)
157}
158
159#[cfg(all(target_os = "linux", feature = "std"))]
160pub mod strict_open {
161    use libc::c_long;
162    use std::{ffi::CStr, mem::size_of};
163
164    #[repr(C)]
165    pub struct OpenHow {
166        pub flags: u64,
167        pub mode: u64,
168        pub resolve: u64,
169    }
170
171    // UAPI openat2 resolve flags (uapi/linux/openat2.h)
172    pub const RESOLVE_NO_SYMLINKS: u64 = 0x04;
173    // pub const RESOLVE_BENEATH: u64 = 0x08; // Too strict for some CI setups (cross-device /tmp)
174
175    pub fn openat2_strict(path: &CStr) -> std::io::Result<i32> {
176        let how = OpenHow {
177            flags: (libc::O_RDONLY | libc::O_NONBLOCK | libc::O_CLOEXEC) as u64,
178            mode: 0,
179            resolve: RESOLVE_NO_SYMLINKS, // Removed RESOLVE_BENEATH to avoid EXDEV in CI
180        };
181
182        let fd = unsafe {
183            libc::syscall(
184                libc::SYS_openat2,
185                libc::AT_FDCWD,
186                path.as_ptr(),
187                &how as *const OpenHow,
188                size_of::<OpenHow>(),
189            ) as c_long
190        };
191
192        if fd < 0 {
193            return Err(std::io::Error::last_os_error());
194        }
195        Ok(fd as i32)
196    }
197}