Skip to content

Commit

Permalink
Merge #930
Browse files Browse the repository at this point in the history
930: Add wrapper for linux kernel module loading r=Susurrus a=bachp

- init_module and finit_module to load kernel modules
- delete_module to unload kernel modules

Co-authored-by: Pascal Bach <pascal.bach@nextrem.ch>
  • Loading branch information
bors[bot] and bachp committed Sep 23, 2018
2 parents d302e8d + a7fea44 commit 1537b63
Show file tree
Hide file tree
Showing 18 changed files with 368 additions and 70 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -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/
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Expand Up @@ -23,7 +23,7 @@ cc = "1"
[dev-dependencies]
bytes = "0.4.8"
lazy_static = "1"
rand = "0.4"
rand = "0.5"
tempfile = "3"

[target.'cfg(target_os = "freebsd")'.dev-dependencies]
Expand Down
123 changes: 123 additions & 0 deletions 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)
}
9 changes: 7 additions & 2 deletions src/lib.rs
Expand Up @@ -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",
Expand All @@ -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
Expand Down
3 changes: 1 addition & 2 deletions src/pty.rs
Expand Up @@ -108,8 +108,7 @@ pub fn grantpt(fd: &PtyMaster) -> Result<()> {
/// let slave_name = unsafe { ptsname(&master_fd) }?;
///
/// // Try to open the slave
/// # #[allow(unused_variables)]
/// let slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, Mode::empty())?;
/// let _slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, Mode::empty())?;
/// # Ok(())
/// # }
/// ```
Expand Down
6 changes: 2 additions & 4 deletions test/sys/test_aio.rs
Expand Up @@ -441,8 +441,7 @@ extern fn sigfunc(_: c_int) {
#[test]
#[cfg_attr(any(all(target_env = "musl", target_arch = "x86_64"), target_arch = "mips", target_arch = "mips64"), ignore)]
fn test_write_sigev_signal() {
#[allow(unused_variables)]
let m = ::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
let _m = ::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
let sa = SigAction::new(SigHandler::Handler(sigfunc),
SaFlags::SA_RESETHAND,
SigSet::empty());
Expand Down Expand Up @@ -580,8 +579,7 @@ fn test_liocb_listio_nowait() {
#[cfg(not(any(target_os = "ios", target_os = "macos")))]
#[cfg_attr(any(target_arch = "mips", target_arch = "mips64", target_env = "musl"), ignore)]
fn test_liocb_listio_signal() {
#[allow(unused_variables)]
let m = ::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
let _m = ::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
const INITIAL: &[u8] = b"abcdef123456";
const WBUF: &[u8] = b"CDEF";
let mut rbuf = vec![0; 4];
Expand Down
1 change: 0 additions & 1 deletion test/sys/test_select.rs
Expand Up @@ -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() {
Expand Down
3 changes: 1 addition & 2 deletions test/sys/test_signal.rs
Expand Up @@ -28,8 +28,7 @@ fn test_sigprocmask_noop() {

#[test]
fn test_sigprocmask() {
#[allow(unused_variables)]
let m = ::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
let _m = ::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");

// This needs to be a signal that rust doesn't use in the test harness.
const SIGNAL: Signal = Signal::SIGCHLD;
Expand Down
3 changes: 1 addition & 2 deletions test/sys/test_signalfd.rs
Expand Up @@ -4,8 +4,7 @@ fn test_signalfd() {
use nix::sys::signal::{self, raise, Signal, SigSet};

// Grab the mutex for altering signals so we don't interfere with other tests.
#[allow(unused_variables)]
let m = ::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");
let _m = ::SIGNAL_MTX.lock().expect("Mutex got poisoned by another test");

// Block the SIGUSR1 signal from automatic processing for this thread
let mut mask = SigSet::empty();
Expand Down
9 changes: 3 additions & 6 deletions test/sys/test_termios.rs
Expand Up @@ -19,8 +19,7 @@ fn write_all(f: RawFd, buf: &[u8]) {
#[test]
fn test_tcgetattr_pty() {
// openpty uses ptname(3) internally
#[allow(unused_variables)]
let m = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
let _m = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");

let pty = openpty(None, None).expect("openpty failed");
assert!(termios::tcgetattr(pty.master).is_ok());
Expand All @@ -47,8 +46,7 @@ fn test_tcgetattr_ebadf() {
#[test]
fn test_output_flags() {
// openpty uses ptname(3) internally
#[allow(unused_variables)]
let m = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
let _m = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");

// Open one pty to get attributes for the second one
let mut termios = {
Expand Down Expand Up @@ -90,8 +88,7 @@ fn test_output_flags() {
#[test]
fn test_local_flags() {
// openpty uses ptname(3) internally
#[allow(unused_variables)]
let m = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
let _m = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");

// Open one pty to get attributes for the second one
let mut termios = {
Expand Down
8 changes: 4 additions & 4 deletions test/sys/test_uio.rs
@@ -1,6 +1,7 @@
use nix::sys::uio::*;
use nix::unistd::*;
use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;
use std::{cmp, iter};
use std::fs::{OpenOptions};
use std::os::unix::io::AsRawFd;
Expand All @@ -11,7 +12,7 @@ use tempfile::{tempfile, tempdir};
fn test_writev() {
let mut to_write = Vec::with_capacity(16 * 128);
for _ in 0..16 {
let s: String = thread_rng().gen_ascii_chars().take(128).collect();
let s: String = thread_rng().sample_iter(&Alphanumeric).take(128).collect();
let b = s.as_bytes();
to_write.extend(b.iter().cloned());
}
Expand Down Expand Up @@ -53,7 +54,7 @@ fn test_writev() {

#[test]
fn test_readv() {
let s:String = thread_rng().gen_ascii_chars().take(128).collect();
let s:String = thread_rng().sample_iter(&Alphanumeric).take(128).collect();
let to_write = s.as_bytes().to_vec();
let mut storage = Vec::new();
let mut allocated = 0;
Expand Down Expand Up @@ -199,8 +200,7 @@ fn test_process_vm_readv() {
use nix::sys::signal::*;
use nix::sys::wait::*;

#[allow(unused_variables)]
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
let _ = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");

// Pre-allocate memory in the child, since allocation isn't safe
// post-fork (~= async-signal-safe)
Expand Down
9 changes: 3 additions & 6 deletions test/sys/test_wait.rs
Expand Up @@ -7,8 +7,7 @@ use libc::_exit;

#[test]
fn test_wait_signal() {
#[allow(unused_variables)]
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
let _ = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");

// Safe: The child only calls `pause` and/or `_exit`, which are async-signal-safe.
match fork().expect("Error: Fork Failed") {
Expand All @@ -25,8 +24,7 @@ fn test_wait_signal() {

#[test]
fn test_wait_exit() {
#[allow(unused_variables)]
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
let _m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");

// Safe: Child only calls `_exit`, which is async-signal-safe.
match fork().expect("Error: Fork Failed") {
Expand Down Expand Up @@ -96,8 +94,7 @@ mod ptrace {

#[test]
fn test_wait_ptrace() {
#[allow(unused_variables)]
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
let _m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");

match fork().expect("Error: Fork Failed") {
Child => ptrace_child(),
Expand Down
18 changes: 18 additions & 0 deletions test/test.rs
Expand Up @@ -9,9 +9,27 @@ extern crate libc;
extern crate rand;
extern crate tempfile;

macro_rules! skip_if_not_root {
($name:expr) => {
use nix::unistd::Uid;
use std;
use std::io::Write;

if !Uid::current().is_root() {
let stderr = std::io::stderr();
let mut handle = stderr.lock();
writeln!(handle, "{} requires root privileges. Skipping test.", $name).unwrap();
return;
}
};
}

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",
Expand Down
7 changes: 7 additions & 0 deletions 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
26 changes: 26 additions & 0 deletions 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");

0 comments on commit 1537b63

Please sign in to comment.