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