Skip to content

Commit a7fea44

Browse files
committed
Add wrapper for linux kernel module loading
- init_module and finit_module to load kernel modules - delete_module to unload kernel modules Signed-off-by: Pascal Bach <[email protected]>
1 parent 81088ab commit a7fea44

File tree

8 files changed

+320
-3
lines changed

8 files changed

+320
-3
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
1414
([#923](https://github.com/nix-rust/nix/pull/923))
1515
- Added a `dir` module for reading directories (wraps `fdopendir`, `readdir`, and `rewinddir`).
1616
([#916](https://github.com/nix-rust/nix/pull/916))
17+
- Added `kmod` module that allows loading and unloading kernel modules on Linux.
18+
([#930](https://github.com/nix-rust/nix/pull/930))
1719

1820
### Changed
1921
- Increased required Rust version to 1.22.1/

src/kmod.rs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
//! Load and unload kernel modules.
2+
//!
3+
//! For more details see
4+
5+
use libc;
6+
use std::ffi::CStr;
7+
use std::os::unix::io::AsRawFd;
8+
9+
use errno::Errno;
10+
use Result;
11+
12+
/// Loads a kernel module from a buffer.
13+
///
14+
/// It loads an ELF image into kernel space,
15+
/// performs any necessary symbol relocations,
16+
/// initializes module parameters to values provided by the caller,
17+
/// and then runs the module's init function.
18+
///
19+
/// This function requires `CAP_SYS_MODULE` privilege.
20+
///
21+
/// The `module_image` argument points to a buffer containing the binary image
22+
/// to be loaded. The buffer should contain a valid ELF image
23+
/// built for the running kernel.
24+
///
25+
/// The `param_values` argument is a string containing space-delimited specifications
26+
/// of the values for module parameters.
27+
/// Each of the parameter specifications has the form:
28+
///
29+
/// `name[=value[,value...]]`
30+
///
31+
/// # Example
32+
///
33+
/// ```no_run
34+
/// use std::fs::File;
35+
/// use std::io::Read;
36+
/// use std::ffi::CString;
37+
/// use nix::kmod::init_module;
38+
///
39+
/// let mut f = File::open("mykernel.ko").unwrap();
40+
/// let mut contents: Vec<u8> = Vec::new();
41+
/// f.read_to_end(&mut contents).unwrap();
42+
/// init_module(&mut contents, &CString::new("who=Rust when=Now,12").unwrap()).unwrap();
43+
/// ```
44+
///
45+
/// See [`man init_module(2)`](http://man7.org/linux/man-pages/man2/init_module.2.html) for more information.
46+
pub fn init_module(module_image: &[u8], param_values: &CStr) -> Result<()> {
47+
let res = unsafe {
48+
libc::syscall(
49+
libc::SYS_init_module,
50+
module_image.as_ptr(),
51+
module_image.len(),
52+
param_values.as_ptr(),
53+
)
54+
};
55+
56+
Errno::result(res).map(drop)
57+
}
58+
59+
libc_bitflags!(
60+
/// Flags used by the `finit_module` function.
61+
pub struct ModuleInitFlags: libc::c_uint {
62+
/// Ignore symbol version hashes.
63+
MODULE_INIT_IGNORE_MODVERSIONS;
64+
/// Ignore kernel version magic.
65+
MODULE_INIT_IGNORE_VERMAGIC;
66+
}
67+
);
68+
69+
/// Loads a kernel module from a given file descriptor.
70+
///
71+
/// # Example
72+
///
73+
/// ```no_run
74+
/// use std::fs::File;
75+
/// use std::ffi::CString;
76+
/// use nix::kmod::{finit_module, ModuleInitFlags};
77+
///
78+
/// let f = File::open("mymod.ko").unwrap();
79+
/// finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty()).unwrap();
80+
/// ```
81+
///
82+
/// See [`man init_module(2)`](http://man7.org/linux/man-pages/man2/init_module.2.html) for more information.
83+
pub fn finit_module<T: AsRawFd>(fd: &T, param_values: &CStr, flags: ModuleInitFlags) -> Result<()> {
84+
let res = unsafe {
85+
libc::syscall(
86+
libc::SYS_finit_module,
87+
fd.as_raw_fd(),
88+
param_values.as_ptr(),
89+
flags.bits(),
90+
)
91+
};
92+
93+
Errno::result(res).map(drop)
94+
}
95+
96+
libc_bitflags!(
97+
/// Flags used by `delete_module`.
98+
///
99+
/// See [`man delete_module(2)`](http://man7.org/linux/man-pages/man2/delete_module.2.html)
100+
/// for a detailed description how these flags work.
101+
pub struct DeleteModuleFlags: libc::c_int {
102+
O_NONBLOCK;
103+
O_TRUNC;
104+
}
105+
);
106+
107+
/// Unloads the kernel module with the given name.
108+
///
109+
/// # Example
110+
///
111+
/// ```no_run
112+
/// use std::ffi::CString;
113+
/// use nix::kmod::{delete_module, DeleteModuleFlags};
114+
///
115+
/// delete_module(&CString::new("mymod").unwrap(), DeleteModuleFlags::O_NONBLOCK).unwrap();
116+
/// ```
117+
///
118+
/// See [`man delete_module(2)`](http://man7.org/linux/man-pages/man2/delete_module.2.html) for more information.
119+
pub fn delete_module(name: &CStr, flags: DeleteModuleFlags) -> Result<()> {
120+
let res = unsafe { libc::syscall(libc::SYS_delete_module, name.as_ptr(), flags.bits()) };
121+
122+
Errno::result(res).map(drop)
123+
}

src/lib.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ pub mod fcntl;
4343
target_os = "netbsd",
4444
target_os = "openbsd"))]
4545
pub mod ifaddrs;
46-
#[cfg(any(target_os = "linux", target_os = "android"))]
46+
#[cfg(any(target_os = "android",
47+
target_os = "linux"))]
48+
pub mod kmod;
49+
#[cfg(any(target_os = "android",
50+
target_os = "linux"))]
4751
pub mod mount;
4852
#[cfg(any(target_os = "dragonfly",
4953
target_os = "freebsd",
@@ -57,7 +61,8 @@ pub mod net;
5761
pub mod poll;
5862
#[deny(missing_docs)]
5963
pub mod pty;
60-
#[cfg(any(target_os = "linux", target_os = "android"))]
64+
#[cfg(any(target_os = "android",
65+
target_os = "linux"))]
6166
pub mod sched;
6267
pub mod sys;
6368
// This can be implemented for other platforms as soon as libc

test/sys/test_select.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use nix::sys::select::*;
22
use nix::unistd::{pipe, write};
33
use nix::sys::signal::SigSet;
44
use nix::sys::time::{TimeSpec, TimeValLike};
5-
use std::os::unix::io::RawFd;
65

76
#[test]
87
pub fn test_pselect() {

test/test.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ macro_rules! skip_if_not_root {
2727
mod sys;
2828
mod test_dir;
2929
mod test_fcntl;
30+
#[cfg(any(target_os = "android",
31+
target_os = "linux"))]
32+
mod test_kmod;
3033
#[cfg(any(target_os = "dragonfly",
3134
target_os = "freebsd",
3235
target_os = "fushsia",

test/test_kmod/hello_mod/Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
obj-m += hello.o
2+
3+
all:
4+
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
5+
6+
clean:
7+
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean

test/test_kmod/hello_mod/hello.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* SPDX-License-Identifier: GPL-2.0+ or MIT
3+
*/
4+
#include <linux/module.h>
5+
#include <linux/kernel.h>
6+
7+
static int number= 1;
8+
static char *who = "World";
9+
10+
module_param(number, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
11+
MODULE_PARM_DESC(myint, "Just some number");
12+
module_param(who, charp, 0000);
13+
MODULE_PARM_DESC(who, "Whot to greet");
14+
15+
int init_module(void)
16+
{
17+
printk(KERN_INFO "Hello %s (%d)!\n", who, number);
18+
return 0;
19+
}
20+
21+
void cleanup_module(void)
22+
{
23+
printk(KERN_INFO "Goodbye %s (%d)!\n", who, number);
24+
}
25+
26+
MODULE_LICENSE("Dual MIT/GPL");

test/test_kmod/mod.rs

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
use std::fs::copy;
2+
use std::path::PathBuf;
3+
use std::process::Command;
4+
use tempfile::{tempdir, TempDir};
5+
6+
fn compile_kernel_module() -> (PathBuf, String, TempDir) {
7+
let _m = ::FORK_MTX
8+
.lock()
9+
.expect("Mutex got poisoned by another test");
10+
11+
let tmp_dir = tempdir().expect("unable to create temporary build directory");
12+
13+
copy(
14+
"test/test_kmod/hello_mod/hello.c",
15+
&tmp_dir.path().join("hello.c"),
16+
).expect("unable to copy hello.c to temporary build directory");
17+
copy(
18+
"test/test_kmod/hello_mod/Makefile",
19+
&tmp_dir.path().join("Makefile"),
20+
).expect("unable to copy Makefile to temporary build directory");
21+
22+
let status = Command::new("make")
23+
.current_dir(tmp_dir.path())
24+
.status()
25+
.expect("failed to run make");
26+
27+
assert!(status.success());
28+
29+
// Return the relative path of the build kernel module
30+
(tmp_dir.path().join("hello.ko"), "hello".to_owned(), tmp_dir)
31+
}
32+
33+
use nix::errno::Errno;
34+
use nix::kmod::{delete_module, DeleteModuleFlags};
35+
use nix::kmod::{finit_module, init_module, ModuleInitFlags};
36+
use nix::Error;
37+
use std::ffi::CString;
38+
use std::fs::File;
39+
use std::io::Read;
40+
41+
#[test]
42+
fn test_finit_and_delete_module() {
43+
skip_if_not_root!("test_finit_module");
44+
45+
let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module();
46+
47+
let f = File::open(kmod_path).expect("unable to open kernel module");
48+
finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty())
49+
.expect("unable to load kernel module");
50+
51+
delete_module(
52+
&CString::new(kmod_name).unwrap(),
53+
DeleteModuleFlags::empty(),
54+
).expect("unable to unload kernel module");
55+
}
56+
57+
#[test]
58+
fn test_finit_and_delete_modul_with_params() {
59+
skip_if_not_root!("test_finit_module_with_params");
60+
61+
let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module();
62+
63+
let f = File::open(kmod_path).expect("unable to open kernel module");
64+
finit_module(
65+
&f,
66+
&CString::new("who=Rust number=2018").unwrap(),
67+
ModuleInitFlags::empty(),
68+
).expect("unable to load kernel module");
69+
70+
delete_module(
71+
&CString::new(kmod_name).unwrap(),
72+
DeleteModuleFlags::empty(),
73+
).expect("unable to unload kernel module");
74+
}
75+
76+
#[test]
77+
fn test_init_and_delete_module() {
78+
skip_if_not_root!("test_init_module");
79+
80+
let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module();
81+
82+
let mut f = File::open(kmod_path).expect("unable to open kernel module");
83+
let mut contents: Vec<u8> = Vec::new();
84+
f.read_to_end(&mut contents)
85+
.expect("unable to read kernel module content to buffer");
86+
init_module(&mut contents, &CString::new("").unwrap()).expect("unable to load kernel module");
87+
88+
delete_module(
89+
&CString::new(kmod_name).unwrap(),
90+
DeleteModuleFlags::empty(),
91+
).expect("unable to unload kernel module");
92+
}
93+
94+
#[test]
95+
fn test_init_and_delete_module_with_params() {
96+
skip_if_not_root!("test_init_module");
97+
98+
let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module();
99+
100+
let mut f = File::open(kmod_path).expect("unable to open kernel module");
101+
let mut contents: Vec<u8> = Vec::new();
102+
f.read_to_end(&mut contents)
103+
.expect("unable to read kernel module content to buffer");
104+
init_module(&mut contents, &CString::new("who=Nix number=2015").unwrap())
105+
.expect("unable to load kernel module");
106+
107+
delete_module(
108+
&CString::new(kmod_name).unwrap(),
109+
DeleteModuleFlags::empty(),
110+
).expect("unable to unload kernel module");
111+
}
112+
113+
#[test]
114+
fn test_finit_module_invalid() {
115+
skip_if_not_root!("test_finit_module_invalid");
116+
117+
let kmod_path = "/dev/zero";
118+
119+
let f = File::open(kmod_path).expect("unable to open kernel module");
120+
let result = finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty());
121+
122+
assert_eq!(result.unwrap_err(), Error::Sys(Errno::EINVAL));
123+
}
124+
125+
#[test]
126+
fn test_finit_module_twice_and_delete_module() {
127+
skip_if_not_root!("test_finit_module_twice_and_delete_module");
128+
129+
let (kmod_path, kmod_name, _kmod_dir) = compile_kernel_module();
130+
131+
let f = File::open(kmod_path).expect("unable to open kernel module");
132+
finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty())
133+
.expect("unable to load kernel module");
134+
135+
let result = finit_module(&f, &CString::new("").unwrap(), ModuleInitFlags::empty());
136+
137+
assert_eq!(result.unwrap_err(), Error::Sys(Errno::EEXIST));
138+
139+
delete_module(
140+
&CString::new(kmod_name).unwrap(),
141+
DeleteModuleFlags::empty(),
142+
).expect("unable to unload kernel module");
143+
}
144+
145+
#[test]
146+
fn test_delete_module_not_loaded() {
147+
skip_if_not_root!("test_delete_module_not_loaded");
148+
149+
let result = delete_module(&CString::new("hello").unwrap(), DeleteModuleFlags::empty());
150+
151+
assert_eq!(result.unwrap_err(), Error::Sys(Errno::ENOENT));
152+
}

0 commit comments

Comments
 (0)