assay_common/
lib.rs

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