diff options
-rw-r--r-- | CHANGELOG.md | 2 | ||||
-rw-r--r-- | src/kmod.rs | 123 | ||||
-rw-r--r-- | src/lib.rs | 9 | ||||
-rw-r--r-- | test/sys/test_select.rs | 1 | ||||
-rw-r--r-- | test/test.rs | 3 | ||||
-rw-r--r-- | test/test_kmod/hello_mod/Makefile | 7 | ||||
-rw-r--r-- | test/test_kmod/hello_mod/hello.c | 26 | ||||
-rw-r--r-- | test/test_kmod/mod.rs | 152 |
8 files changed, 320 insertions, 3 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 52bd2f24..b63ea5b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ([#923](https://github.com/nix-rust/nix/pull/923)) - Added a `dir` module for reading directories (wraps `fdopendir`, `readdir`, and `rewinddir`). ([#916](https://github.com/nix-rust/nix/pull/916)) +- Added `kmod` module that allows loading and unloading kernel modules on Linux. + ([#930](https://github.com/nix-rust/nix/pull/930)) ### Changed - Increased required Rust version to 1.22.1/ diff --git a/src/kmod.rs b/src/kmod.rs new file mode 100644 index 00000000..e853261b --- /dev/null +++ b/src/kmod.rs @@ -0,0 +1,123 @@ +//! Load and unload kernel modules. +//! +//! For more details see + +use libc; +use std::ffi::CStr; +use std::os::unix::io::AsRawFd; + +use errno::Errno; +use Result; + +/// Loads a kernel module from a buffer. +/// +/// It loads an ELF image into kernel space, +/// performs any necessary symbol relocations, +/// initializes module parameters to values provided by the caller, +/// and then runs the module's init function. +/// +/// This function requires `CAP_SYS_MODULE` privilege. +/// +/// The `module_image` argument points to a buffer containing the binary image +/// to be loaded. The buffer should contain a valid ELF image +/// built for the running kernel. +/// +/// The `param_values` argument is a string containing space-delimited specifications +/// of the values for module parameters. +/// Each of the parameter specifications has the form: +/// +/// `name[=value[,value...]]` +/// +/// # Example +/// +/// ```no_run +/// use std::fs::File; +/// use std::io::Read; +/// use std::ffi::CString; +/// use nix::kmod::init_module; +/// +/// let mut f = File::open("mykernel.ko").unwrap(); +/// let mut contents: Vec<u8> = Vec::new(); +/// f.read_to_end(&mut contents).unwrap(); +/// init_module(&mut contents, &CString::new("who=Rust when=Now,12").unwrap()).unwrap(); +/// ``` +/// +/// See [`man init_module(2)`](http://man7.org/linux/man-pages/man2/init_module.2.html) for more information. +pub fn init_module(module_image: &[u8], param_values: &CStr) -> Result<()> { + let res = unsafe { + libc::syscall( + libc::SYS_init_module, + module_image.as_ptr(), + module_image.len(), + param_values.as_ptr(), + ) + }; + + Errno::result(res).map(drop) +} + +libc_bitflags!( + /// Flags used by the `finit_module` function. + pub struct ModuleInitFlags: libc::c_uint { + /// Ignore symbol version hashes. + MODULE_INIT_IGNORE_MODVERSIONS; + /// Ignore kernel version magic. + MODULE_INIT_IGNORE_VERMAGIC; + } +); + +/// Loads a kernel module from a given file descriptor. +/// +/// # Example +/// +/// ```no_run +/// use std::fs::File; +/// use std::ffi::CString; +/// use nix::kmod::{finit_module, ModuleInitFlags}; +/// +/// let f = File::open("mymod.ko").unwrap(); +/// finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty()).unwrap(); +/// ``` +/// +/// See [`man init_module(2)`](http://man7.org/linux/man-pages/man2/init_module.2.html) for more information. +pub fn finit_module<T: AsRawFd>(fd: &T, param_values: &CStr, flags: ModuleInitFlags) -> Result<()> { + let res = unsafe { + libc::syscall( + libc::SYS_finit_module, + fd.as_raw_fd(), + param_values.as_ptr(), + flags.bits(), + ) + }; + + Errno::result(res).map(drop) +} + +libc_bitflags!( + /// Flags used by `delete_module`. + /// + /// See [`man delete_module(2)`](http://man7.org/linux/man-pages/man2/delete_module.2.html) + /// for a detailed description how these flags work. + pub struct DeleteModuleFlags: libc::c_int { + O_NONBLOCK; + O_TRUNC; + } +); + +/// Unloads the kernel module with the given name. +/// +/// # Example +/// +/// ```no_run +/// use std::ffi::CString; +/// use nix::kmod::{delete_module, DeleteModuleFlags}; +/// +/// delete_module(&CString::new("mymod").unwrap(), DeleteModuleFlags::O_NONBLOCK).unwrap(); +/// ``` +/// +/// See [`man delete_module(2)`](http://man7.org/linux/man-pages/man2/delete_module.2.html) for more information. +pub fn delete_module(name: &CStr, flags: DeleteModuleFlags) -> Result<()> { + let res = unsafe { libc::syscall(libc::SYS_delete_module, name.as_ptr(), flags.bits()) }; + + Errno::result(res).map(drop) +} @@ -43,7 +43,11 @@ pub mod fcntl; target_os = "netbsd", target_os = "openbsd"))] pub mod ifaddrs; -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(any(target_os = "android", + target_os = "linux"))] +pub mod kmod; +#[cfg(any(target_os = "android", + target_os = "linux"))] pub mod mount; #[cfg(any(target_os = "dragonfly", target_os = "freebsd", @@ -57,7 +61,8 @@ pub mod net; pub mod poll; #[deny(missing_docs)] pub mod pty; -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(any(target_os = "android", + target_os = "linux"))] pub mod sched; pub mod sys; // This can be implemented for other platforms as soon as libc diff --git a/test/sys/test_select.rs b/test/sys/test_select.rs index 19d12fba..cf68700c 100644 --- a/test/sys/test_select.rs +++ b/test/sys/test_select.rs @@ -2,7 +2,6 @@ use nix::sys::select::*; use nix::unistd::{pipe, write}; use nix::sys::signal::SigSet; use nix::sys::time::{TimeSpec, TimeValLike}; -use std::os::unix::io::RawFd; #[test] pub fn test_pselect() { diff --git a/test/test.rs b/test/test.rs index 14f63e26..a91b6348 100644 --- a/test/test.rs +++ b/test/test.rs @@ -27,6 +27,9 @@ macro_rules! skip_if_not_root { mod sys; mod test_dir; mod test_fcntl; +#[cfg(any(target_os = "android", + target_os = "linux"))] +mod test_kmod; #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "fushsia", diff --git a/test/test_kmod/hello_mod/Makefile b/test/test_kmod/hello_mod/Makefile new file mode 100644 index 00000000..74c99b77 --- /dev/null +++ b/test/test_kmod/hello_mod/Makefile @@ -0,0 +1,7 @@ +obj-m += hello.o + +all: + make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules + +clean: + make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean diff --git a/test/test_kmod/hello_mod/hello.c b/test/test_kmod/hello_mod/hello.c new file mode 100644 index 00000000..1c34987d --- /dev/null +++ b/test/test_kmod/hello_mod/hello.c @@ -0,0 +1,26 @@ +/* + * SPDX-License-Identifier: GPL-2.0+ or MIT + */ +#include <linux/module.h> +#include <linux/kernel.h> + +static int number= 1; +static char *who = "World"; + +module_param(number, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); +MODULE_PARM_DESC(myint, "Just some number"); +module_param(who, charp, 0000); +MODULE_PARM_DESC(who, "Whot to greet"); + +int init_module(void) +{ + printk(KERN_INFO "Hello %s (%d)!\n", who, number); + return 0; +} + +void cleanup_module(void) +{ + printk(KERN_INFO "Goodbye %s (%d)!\n", who, number); +} + +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/test/test_kmod/mod.rs b/test/test_kmod/mod.rs new file mode 100644 index 00000000..e0cb0df5 --- /dev/null +++ b/test/test_kmod/mod.rs @@ -0,0 +1,152 @@ +use std::fs::copy; +use std::path::PathBuf; +use std::process::Command; +use tempfile::{tempdir, TempDir}; + +fn compile_kernel_module() -> (PathBuf, String, TempDir) { + let _m = ::FORK_MTX + .lock() + .expect("Mutex got poisoned by another test"); + + let tmp_dir = tempdir().expect("unable to create temporary build directory"); + + copy( + "test/test_kmod/hello_mod/hello.c", + &tmp_dir.path().join("hello.c"), + ).expect("unable to copy hello.c to temporary build directory"); + copy( + "test/test_kmod/hello_mod/Makefile", + &tmp_dir.path().join("Makefile"), + ).expect("unable to copy Makefile to temporary build directory"); + + let status = Command::new("make") + .current_dir(tmp_dir.path()) + .status() + .expect("failed to run make"); + + assert!(status.success()); + + // Return the relative path of the build kernel module + (tmp_dir.path().join("hello.ko"), "hello".to_owned(), tmp_dir) +} + +use nix::errno::Errno; +use nix::kmod::{delete_module, DeleteModuleFlags}; +use nix::kmod::{finit_module, init_module, ModuleInitFlags}; +use nix::Error; +use std::ffi::CString; +use std::fs::File; +use std::io::Read; + +#[test] +fn test_finit_and_delete_module() { + skip_if_not_root!("test_finit_module"); + + let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module(); + + let f = File::open(kmod_path).expect("unable to open kernel module"); + finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty()) + .expect("unable to load kernel module"); + + delete_module( + &CString::new(kmod_name).unwrap(), + DeleteModuleFlags::empty(), + ).expect("unable to unload kernel module"); +} + +#[test] +fn test_finit_and_delete_modul_with_params() { + skip_if_not_root!("test_finit_module_with_params"); + + let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module(); + + let f = File::open(kmod_path).expect("unable to open kernel module"); + finit_module( + &f, + &CString::new("who=Rust number=2018").unwrap(), + ModuleInitFlags::empty(), + ).expect("unable to load kernel module"); + + delete_module( + &CString::new(kmod_name).unwrap(), + DeleteModuleFlags::empty(), + ).expect("unable to unload kernel module"); +} + +#[test] +fn test_init_and_delete_module() { + skip_if_not_root!("test_init_module"); + + let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module(); + + let mut f = File::open(kmod_path).expect("unable to open kernel module"); + let mut contents: Vec<u8> = Vec::new(); + f.read_to_end(&mut contents) + .expect("unable to read kernel module content to buffer"); + init_module(&mut contents, &CString::new("").unwrap()).expect("unable to load kernel module"); + + delete_module( + &CString::new(kmod_name).unwrap(), + DeleteModuleFlags::empty(), + ).expect("unable to unload kernel module"); +} + +#[test] +fn test_init_and_delete_module_with_params() { + skip_if_not_root!("test_init_module"); + + let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module(); + + let mut f = File::open(kmod_path).expect("unable to open kernel module"); + let mut contents: Vec<u8> = Vec::new(); + f.read_to_end(&mut contents) + .expect("unable to read kernel module content to buffer"); + init_module(&mut contents, &CString::new("who=Nix number=2015").unwrap()) + .expect("unable to load kernel module"); + + delete_module( + &CString::new(kmod_name).unwrap(), + DeleteModuleFlags::empty(), + ).expect("unable to unload kernel module"); +} + +#[test] +fn test_finit_module_invalid() { + skip_if_not_root!("test_finit_module_invalid"); + + let kmod_path = "/dev/zero"; + + let f = File::open(kmod_path).expect("unable to open kernel module"); + let result = finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty()); + + assert_eq!(result.unwrap_err(), Error::Sys(Errno::EINVAL)); +} + +#[test] +fn test_finit_module_twice_and_delete_module() { + skip_if_not_root!("test_finit_module_twice_and_delete_module"); + + let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module(); + + let f = File::open(kmod_path).expect("unable to open kernel module"); + finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty()) + .expect("unable to load kernel module"); + + let result = finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty()); + + assert_eq!(result.unwrap_err(), Error::Sys(Errno::EEXIST)); + + delete_module( + &CString::new(kmod_name).unwrap(), + DeleteModuleFlags::empty(), + ).expect("unable to unload kernel module"); +} + +#[test] +fn test_delete_module_not_loaded() { + skip_if_not_root!("test_delete_module_not_loaded"); + + let result = delete_module(&CString::new("hello").unwrap(), DeleteModuleFlags::empty()); + + assert_eq!(result.unwrap_err(), Error::Sys(Errno::ENOENT)); +} |