diff --git a/.travis.yml b/.travis.yml index 669f6c07..5e49f3c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ cache: - directories: - static-filesystem/target/ - hello-world/target/ + - example-sysctl/target/ branches: only: @@ -18,6 +19,7 @@ matrix: include: - env: MODULE_DIR=hello-world MODULE=helloworld - env: MODULE_DIR=static-filesystem MODULE=staticfilesystem + - env: MODULE_DIR=example-sysctl MODULE=examplesysctl install: - sudo apt-get install -y "linux-headers-$(uname -r)" diff --git a/example-sysctl/Cargo.toml b/example-sysctl/Cargo.toml new file mode 100644 index 00000000..05eb902c --- /dev/null +++ b/example-sysctl/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "example-sysctl" +version = "0.1.0" +authors = ["Alex Gaynor ", "Geoffrey Thomas "] + +[lib] +crate-type = ["staticlib"] + +[dependencies] +linux-kernel-module = { path = ".." } diff --git a/example-sysctl/Makefile b/example-sysctl/Makefile new file mode 100644 index 00000000..dfd0d65d --- /dev/null +++ b/example-sysctl/Makefile @@ -0,0 +1,9 @@ +obj-m := examplesysctl.o +examplesysctl-objs := target/x86_64-linux-kernel-module/debug/libexample_sysctl.a +EXTRA_LDFLAGS += --entry=init_module + +all: + $(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(CURDIR) + +clean: + $(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(CURDIR) clean diff --git a/example-sysctl/src/lib.rs b/example-sysctl/src/lib.rs new file mode 100644 index 00000000..60c996fb --- /dev/null +++ b/example-sysctl/src/lib.rs @@ -0,0 +1,41 @@ +#![no_std] + +use core::sync::atomic::{AtomicBool, Ordering}; + +#[macro_use] +extern crate linux_kernel_module; + +use linux_kernel_module::sysctl::Sysctl; +use linux_kernel_module::Mode; + +struct ExampleSysctlModule { + a: Sysctl, +} + +impl linux_kernel_module::KernelModule for ExampleSysctlModule { + fn init() -> linux_kernel_module::KernelResult { + let a = Sysctl::register( + "rust/example\x00", + "a\x00", + AtomicBool::new(false), + Mode::from_int(0o644), + )?; + + Ok(ExampleSysctlModule { a }) + } +} + +impl Drop for ExampleSysctlModule { + fn drop(&mut self) { + println!( + "Current sysctl value: {}", + self.a.get().load(Ordering::Relaxed) + ); + } +} +kernel_module!( + ExampleSysctlModule, + author: "Alex Gaynor and Geoffrey Thomas", + description: "A kernel module that offers a sysctl", + license: "GPL" +); diff --git a/src/helpers.c b/src/helpers.c index e3d8289c..13d76910 100644 --- a/src/helpers.c +++ b/src/helpers.c @@ -1,5 +1,7 @@ #include #include +#include + int printk_helper(const unsigned char *s, int len) { @@ -10,3 +12,8 @@ void bug_helper(void) { BUG(); } + +int access_ok_helper(unsigned int mode, const void __user *addr, unsigned long n) +{ + return access_ok(mode, addr, n); +} diff --git a/src/lib.rs b/src/lib.rs index 0c585721..71ef38c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ #![no_std] #![feature(alloc, global_allocator, allocator_api, const_fn, lang_items, panic_implementation)] +#[macro_use] extern crate alloc; #[macro_use] extern crate bitflags; @@ -14,8 +15,12 @@ mod error; pub mod filesystem; #[macro_use] pub mod printk; +pub mod sysctl; +mod types; +pub mod user_ptr; pub use error::{Error, KernelResult}; +pub use types::Mode; pub type _InitResult = c_types::c_int; diff --git a/src/sysctl.rs b/src/sysctl.rs new file mode 100644 index 00000000..7ffce293 --- /dev/null +++ b/src/sysctl.rs @@ -0,0 +1,153 @@ +use alloc::boxed::Box; +use core::mem; +use core::ptr; +use core::sync::atomic; + +use bindings; +use c_types; +use error; +use types; +use user_ptr::{UserSlicePtr, UserSlicePtrWriter}; + +pub struct Sysctl { + inner: Box, + table: Box<[bindings::ctl_table]>, + header: *mut bindings::ctl_table_header, +} + +pub trait SysctlStorage: Sync { + fn store_value(&self, data: &[u8]) -> (usize, error::KernelResult<()>); + fn read_value(&self, data: &mut UserSlicePtrWriter) -> (usize, error::KernelResult<()>); +} + +fn trim_whitespace(mut data: &[u8]) -> &[u8] { + while !data.is_empty() && (data[0] == b' ' || data[0] == b'\t' || data[0] == b'\n') { + data = &data[1..]; + } + while !data.is_empty() + && (data[data.len() - 1] == b' ' + || data[data.len() - 1] == b'\t' + || data[data.len() - 1] == b'\n') + { + data = &data[..data.len() - 1]; + } + return data; +} + +impl SysctlStorage for atomic::AtomicBool { + fn store_value(&self, data: &[u8]) -> (usize, error::KernelResult<()>) { + let result = match trim_whitespace(data) { + b"0" => { + self.store(false, atomic::Ordering::Relaxed); + Ok(()) + } + b"1" => { + self.store(true, atomic::Ordering::Relaxed); + Ok(()) + } + _ => Err(error::Error::EINVAL), + }; + return (data.len(), result); + } + + fn read_value(&self, data: &mut UserSlicePtrWriter) -> (usize, error::KernelResult<()>) { + let value = if self.load(atomic::Ordering::Relaxed) { + b"1\n" + } else { + b"0\n" + }; + (value.len(), data.write(value)) + } +} + +unsafe extern "C" fn proc_handler( + ctl: *mut bindings::ctl_table, + write: c_types::c_int, + buffer: *mut c_types::c_void, + len: *mut usize, + ppos: *mut bindings::loff_t, +) -> c_types::c_int { + // If we're reading from some offset other than the beginning of the file, + // return an empty read to signal EOF. + if *ppos != 0 && write == 0 { + *len = 0; + return 0; + } + + let data = match UserSlicePtr::new(buffer, *len) { + Ok(ptr) => ptr, + Err(e) => return e.to_kernel_errno(), + }; + let storage = &*((*ctl).data as *const T); + let (bytes_processed, result) = if write != 0 { + let data = match data.read_all() { + Ok(r) => r, + Err(e) => return e.to_kernel_errno(), + }; + storage.store_value(&data) + } else { + let mut writer = data.writer(); + storage.read_value(&mut writer) + }; + *len = bytes_processed; + *ppos += *len as bindings::loff_t; + match result { + Ok(()) => 0, + Err(e) => e.to_kernel_errno(), + } +} + +impl Sysctl { + pub fn register( + path: &'static str, + name: &'static str, + storage: T, + mode: types::Mode, + ) -> error::KernelResult> { + if !path.ends_with('\x00') || !name.ends_with('\x00') || name.contains('/') { + return Err(error::Error::EINVAL); + } + + let storage = Box::new(storage); + let mut table = vec![ + bindings::ctl_table { + procname: name.as_ptr() as *const i8, + mode: mode.as_int(), + data: &*storage as *const T as *mut c_types::c_void, + proc_handler: Some(proc_handler::), + + maxlen: 0, + child: ptr::null_mut(), + poll: ptr::null_mut(), + extra1: ptr::null_mut(), + extra2: ptr::null_mut(), + }, + unsafe { mem::zeroed() }, + ].into_boxed_slice(); + + let result = + unsafe { bindings::register_sysctl(path.as_ptr() as *const i8, table.as_mut_ptr()) }; + if result.is_null() { + return Err(error::Error::ENOMEM); + } + + return Ok(Sysctl { + inner: storage, + table: table, + header: result, + }); + } + + pub fn get(&self) -> &T { + return &self.inner; + } +} + +impl Drop for Sysctl { + fn drop(&mut self) { + unsafe { + bindings::unregister_sysctl_table(self.header); + } + self.header = ptr::null_mut(); + } +} diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 00000000..378237b7 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,13 @@ +use bindings; + +pub struct Mode(bindings::umode_t); + +impl Mode { + pub fn from_int(m: u16) -> Mode { + Mode(m) + } + + pub fn as_int(&self) -> u16 { + return self.0; + } +} diff --git a/src/user_ptr.rs b/src/user_ptr.rs new file mode 100644 index 00000000..001e295a --- /dev/null +++ b/src/user_ptr.rs @@ -0,0 +1,142 @@ +use alloc::vec::Vec; +use core::u32; + +use bindings; +use c_types; +use error; + +extern "C" { + fn access_ok_helper( + mode: c_types::c_uint, + addr: *const c_types::c_void, + len: c_types::c_ulong, + ) -> c_types::c_int; +} + +/// A reference to an area in userspace memory, which can be either +/// read-only or read-write. +/// +/// All methods on this struct are safe: invalid pointers return +/// `EFAULT`. Concurrent access, _including data races to/from userspace +/// memory_, is permitted, because fundamentally another userspace +/// thread / process could always be modifying memory at the same time +/// (in the same way that userspace Rust's std::io permits data races +/// with the contents of files on disk). In the presence of a race, the +/// exact byte values read/written are unspecified but the operation is +/// well-defined. Kernelspace code should validate its copy of data +/// after completing a read, and not expect that multiple reads of the +/// same address will return the same value. +/// +/// Constructing a `UserSlicePtr` only checks that the range is in valid +/// userspace memory, and does not depend on the current process (and +/// can safely be constructed inside a kernel thread with no current +/// userspace process). Reads and writes wrap the kernel APIs +/// `copy_from_user` and `copy_to_user`, and check the memory map of the +/// current process. +pub struct UserSlicePtr(*mut c_types::c_void, usize); + +impl UserSlicePtr { + /// Construct a user slice from a raw pointer and a length in bytes. + /// + /// Checks that the provided range is within the legal area for + /// userspace memory, using `access_ok` (e.g., on i386, the range + /// must be within the first 3 gigabytes), but does not check that + /// the actual pages are mapped in the current process with + /// appropriate permissions. Those checks are handled in the read + /// and write methods. + pub fn new(ptr: *mut c_types::c_void, length: usize) -> error::KernelResult { + // No current access_ok implementation actually distinguishes + // between VERIFY_READ and VERIFY_WRITE, so passing VERIFY_WRITE + // is fine in practice and fails safe if a future implementation + // bothers. + if unsafe { access_ok_helper(bindings::VERIFY_WRITE, ptr, length as c_types::c_ulong) } == 0 + { + return Err(error::Error::EFAULT); + } + return Ok(UserSlicePtr(ptr, length)); + } + + /// Read the entirety of the user slice and return it in a `Vec`. + /// + /// Returns EFAULT if the address does not currently point to + /// mapped, readable memory. + pub fn read_all(self) -> error::KernelResult> { + let mut data = vec![0; self.1]; + self.reader().read(&mut data)?; + return Ok(data); + } + + /// Construct a `UserSlicePtrReader` that can incrementally read + /// from the user slice. + pub fn reader(self) -> UserSlicePtrReader { + return UserSlicePtrReader(self.0, self.1); + } + + /// Write the provided slice into the user slice. + /// + /// Returns EFAULT if the address does not currently point to + /// mapped, writable memory (in which case some data from before the + /// fault may be written), or `data` is larger than the user slice + /// (in which case no data is written). + pub fn write_all(self, data: &[u8]) -> error::KernelResult<()> { + return self.writer().write(data); + } + + /// Construct a `UserSlicePtrWrite` that can incrementally write + /// into the user slice. + pub fn writer(self) -> UserSlicePtrWriter { + return UserSlicePtrWriter(self.0, self.1); + } +} + +pub struct UserSlicePtrReader(*mut c_types::c_void, usize); + +impl UserSlicePtrReader { + pub fn read(&mut self, data: &mut [u8]) -> error::KernelResult<()> { + if data.len() > self.1 || data.len() > u32::MAX as usize { + return Err(error::Error::EFAULT); + } + let res = unsafe { + bindings::_copy_from_user( + data.as_mut_ptr() as *mut c_types::c_void, + self.0, + data.len() as u32, + ) + }; + if res != 0 { + return Err(error::Error::EFAULT); + } + // Since this is not a pointer to a valid object in our program, + // we cannot use `add`, which has C-style rules for defined + // behavior. + self.0 = self.0.wrapping_add(data.len()); + self.1 -= data.len(); + Ok(()) + } +} + +pub struct UserSlicePtrWriter(*mut c_types::c_void, usize); + +impl UserSlicePtrWriter { + pub fn write(&mut self, data: &[u8]) -> error::KernelResult<()> { + if data.len() > self.1 || data.len() > u32::MAX as usize { + return Err(error::Error::EFAULT); + } + let res = unsafe { + bindings::_copy_to_user( + self.0, + data.as_ptr() as *const c_types::c_void, + data.len() as u32, + ) + }; + if res != 0 { + return Err(error::Error::EFAULT); + } + // Since this is not a pointer to a valid object in our program, + // we cannot use `add`, which has C-style rules for defined + // behavior. + self.0 = self.0.wrapping_add(data.len()); + self.1 -= data.len(); + Ok(()) + } +}