diff --git a/build.rs b/build.rs index df22d96d..8d7ec2b1 100644 --- a/build.rs +++ b/build.rs @@ -45,6 +45,8 @@ const INCLUDED_VARS: &[&str] = &[ "SEEK_CUR", "SEEK_END", "O_NONBLOCK", + "init_task", + "TASK_COMM_LEN", ]; const OPAQUE_TYPES: &[&str] = &[ // These need to be opaque because they're both packed and aligned, which rustc diff --git a/src/bindings_helper.h b/src/bindings_helper.h index fd8ba5e8..b99b43f6 100644 --- a/src/bindings_helper.h +++ b/src/bindings_helper.h @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include diff --git a/src/helpers.c b/src/helpers.c index 7c6e3120..e88989ac 100644 --- a/src/helpers.c +++ b/src/helpers.c @@ -1,7 +1,10 @@ #include #include +#include #include #include +#include +#include int printk_helper(const unsigned char *s, int len) @@ -23,6 +26,26 @@ int access_ok_helper(const void __user *addr, unsigned long n) #endif } +void rcu_read_lock_helper(void) { + rcu_read_lock(); +} + +void rcu_read_unlock_helper(void) { + rcu_read_unlock(); +} + +struct task_struct *next_task_helper(struct task_struct *p) { + return next_task(p); +} + +void task_lock_helper(struct task_struct *p) { + return task_lock(p); +} + +void task_unlock_helper(struct task_struct *p) { + return task_unlock(p); +} + /* see https://github.com/rust-lang/rust-bindgen/issues/1671 */ _Static_assert(__builtin_types_compatible_p(size_t, uintptr_t), "size_t must match uintptr_t, what architecture is this??"); diff --git a/src/lib.rs b/src/lib.rs index 33d5e669..21759a26 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,8 @@ pub mod filesystem; pub mod printk; #[cfg(kernel_4_13_0_or_greater)] pub mod random; +pub mod rcu; +pub mod sched; pub mod sysctl; mod types; pub mod user_ptr; diff --git a/src/rcu.rs b/src/rcu.rs new file mode 100644 index 00000000..e91758cb --- /dev/null +++ b/src/rcu.rs @@ -0,0 +1,42 @@ +//! Bindings to RCU (read-copy-update), a high-performance lockless +//! synchronization system used by many kernel data structures. At the +//! moment, only calling functions that perform RCU reads is supported. + +extern "C" { + fn rcu_read_lock_helper(); + fn rcu_read_unlock_helper(); +} + +/// A guard representing an RCU read-side critical section. Its +/// constructor calls `rcu_read_lock()` and its destructor calls +/// `rcu_read_unlock()`. +/// +/// Within a read-side critical section (i.e., while at least one +/// RcuReadGuard object is instantiated), objects behind RCU-protected +/// pointers are guaranteed not to change, and so reading from them +/// (after gaining a pointer with `rcu_dereference()`) is safe. +/// +/// It is an error (risk of deadlock, but not memory unsafety) to block +/// or schedule while holding an RcuReadGuard. It is also an error +/// (guaranteed deadlock) to call `synchronize_rcu()` while holding an +/// RcuReadGuard. Holding multiple guards (i.e., nesting read-side +/// critical sections) is safe. +pub struct RcuReadGuard(()); + +#[allow(clippy::new_without_default)] +impl RcuReadGuard { + pub fn new() -> Self { + unsafe { + rcu_read_lock_helper(); + } + RcuReadGuard(()) + } +} + +impl Drop for RcuReadGuard { + fn drop(&mut self) { + unsafe { + rcu_read_unlock_helper(); + } + } +} diff --git a/src/sched.rs b/src/sched.rs new file mode 100644 index 00000000..fad29598 --- /dev/null +++ b/src/sched.rs @@ -0,0 +1,89 @@ +//! APIs for interacting with the scheduler and with processes, +//! corresponding to and related header files. +#![allow(improper_ctypes)] + +use core::ptr; + +use crate::bindings; +use crate::rcu; + +extern "C" { + fn task_lock_helper(p: *mut bindings::task_struct); + fn task_unlock_helper(p: *mut bindings::task_struct); + fn next_task_helper(p: *mut bindings::task_struct) -> *mut bindings::task_struct; +} + +/// Represents a `struct task_struct *`. +pub struct TaskStruct<'a>(&'a mut bindings::task_struct); + +impl TaskStruct<'_> { + /// Returns the threadgroup ID (what userspace calls the process ID). + pub fn tgid(&self) -> i32 { + self.0.tgid + } + + /// Returns the command name / process title. This is a short name, + /// typically the base name of the command, and does not have the + /// full path or arguments. It's a fixed-sized set of bytes, but by + /// convention it's interpreted as NUL-terminated. + pub fn comm(&mut self) -> [u8; bindings::TASK_COMM_LEN as usize] { + let mut result = [0u8; bindings::TASK_COMM_LEN as usize]; + unsafe { + task_lock_helper(self.0); + } + // if only char were unsigned char + for (src, dst) in self.0.comm.iter().zip(result.iter_mut()) { + if *src == 0 { + break; + } + *dst = *src as _; + } + unsafe { + task_unlock_helper(self.0); + } + result + } +} + +/// Iterate over every process on the system. Returns only processes, +/// i.e., thread group leaders. +/// +/// ``` +/// let g = rcu::RcuReadGuard::new(); +/// for p in each_process(&g) { +/// println!("{:?}", p.comm()); +/// } +/// ``` +struct EachProcess<'g> { + p: *mut bindings::task_struct, + _g: &'g rcu::RcuReadGuard, +} + +pub fn each_process(g: &rcu::RcuReadGuard) -> impl Iterator { + // unsafe is bogus here because we don't read it + // https://github.com/rust-lang/rust/issues/74843 + EachProcess { + p: unsafe { &mut bindings::init_task }, + _g: g, + } +} + +impl<'g> Iterator for EachProcess<'g> { + type Item = TaskStruct<'g>; + + fn next(&mut self) -> Option> { + // Safety: + // - oldp is valid if not null, because it is either &init_task + // (a static location) or updated by this function. + // - next_task calls rcu_dereference internally, which is safe + // because we hold self._g. + // - The returned reference has lifetime 'g, which is valid + // because self._g lives at least that long. + let oldp = unsafe { self.p.as_mut()? }; + self.p = unsafe { next_task_helper(self.p) }; + if self.p == unsafe { &mut bindings::init_task } { + self.p = ptr::null_mut(); + } + Some(TaskStruct(oldp)) + } +} diff --git a/tests/for-each-process/Cargo.toml b/tests/for-each-process/Cargo.toml new file mode 100644 index 00000000..1ded677f --- /dev/null +++ b/tests/for-each-process/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "for-each-process-tests" +version = "0.1.0" +authors = ["Alex Gaynor ", "Geoffrey Thomas "] +edition = "2018" + +[lib] +crate-type = ["staticlib"] +test = false + +[features] +default = ["linux-kernel-module"] + +[dependencies] +linux-kernel-module = { path = "../..", optional = true } + +[dev-dependencies] +kernel-module-testlib = { path = "../../testlib" } +libc = "0.2.58" +tempfile = "3" diff --git a/tests/for-each-process/src/lib.rs b/tests/for-each-process/src/lib.rs new file mode 100644 index 00000000..22165b71 --- /dev/null +++ b/tests/for-each-process/src/lib.rs @@ -0,0 +1,28 @@ +#![no_std] + +use linux_kernel_module::{self, println, rcu, sched}; + +struct ForEachProcessTestModule; + +impl linux_kernel_module::KernelModule for ForEachProcessTestModule { + fn init() -> linux_kernel_module::KernelResult { + let g = rcu::RcuReadGuard::new(); + for mut p in sched::each_process(&g) { + let comm = p.comm(); + let comm_until_nul = comm.split(|c| *c == 0).next().unwrap(); + println!( + "for-each-process: {:8} {}", + p.tgid(), + core::str::from_utf8(comm_until_nul).unwrap_or("[invalid UTF-8]") + ); + } + Ok(ForEachProcessTestModule) + } +} + +linux_kernel_module::kernel_module!( + ForEachProcessTestModule, + author: b"Fish in a Barrel Contributors", + description: b"A module for testing EachProcess", + license: b"GPL" +); diff --git a/tests/for-each-process/tests/tests.rs b/tests/for-each-process/tests/tests.rs new file mode 100644 index 00000000..cb0136bc --- /dev/null +++ b/tests/for-each-process/tests/tests.rs @@ -0,0 +1,15 @@ +use std::fs::File; +use std::io::Write; + +use kernel_module_testlib::{assert_dmesg_contains, with_kernel_module}; + +#[test] +fn test_for_each_process() { + File::create("/proc/self/comm") + .unwrap() + .write_all(b"areyouthere") + .unwrap(); + with_kernel_module(|| { + assert_dmesg_contains(&[b"areyouthere"]); + }); +}