Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add wrapper for linux kernel module loading #930

Merged
merged 4 commits into from Sep 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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();
/// ```
///
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this blank line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

/// 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.
bachp marked this conversation as resolved.
Show resolved Hide resolved
///
/// # 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");