diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ce3fb1..9c6c0a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,20 +10,25 @@ env: CARGO_TERM_COLOR: always jobs: - build-test-x86_64-linux: - runs-on: ubuntu-latest + build-test-native: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ + ubuntu-latest, + ] steps: - uses: actions/checkout@v4 - name: Build all features run: cargo build --verbose --all-features - - name: Build no_std - run: cargo build --verbose --no-default-features - name: Run tests run: cargo test --verbose cross: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: target: [ i686-unknown-linux-gnu, @@ -35,8 +40,6 @@ jobs: run: cargo install cross - name: Build all features run: cross build --verbose --all-features --target ${{ matrix.target }} - - name: Build no_std - run: cross build --verbose --no-default-features --target ${{ matrix.target }} - name: Run tests run: cross test --verbose --target ${{ matrix.target }} diff --git a/Cargo.lock b/Cargo.lock index 72aac20..06b92ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,24 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - [[package]] name = "equivalent" version = "1.0.1" @@ -66,18 +48,6 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases", - "libc", -] - [[package]] name = "proc-macro2" version = "1.0.86" @@ -146,7 +116,7 @@ dependencies = [ name = "static-keys" version = "0.2.0" dependencies = [ - "nix", + "libc", "trybuild", ] diff --git a/Cargo.toml b/Cargo.toml index f91cc23..7b53f1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,12 +12,8 @@ readme = "README.md" keywords = ["static-keys", "Linux-kernel"] categories = ["rust-patterns", "no-std"] -[features] -default = ["std"] -std = ["dep:nix"] - -[dependencies] -nix = { version = "0.29", features = ["mman"], optional = true } +[target.'cfg(target_os = "linux")'.dependencies] +libc = { version = "0.2", default-features = false } [dev-dependencies] trybuild = "1" diff --git a/src/code_manipulate.rs b/src/code_manipulate.rs index ccf1397..39ef426 100644 --- a/src/code_manipulate.rs +++ b/src/code_manipulate.rs @@ -19,54 +19,7 @@ pub trait CodeManipulator { unsafe fn restore_code_region_protect(&self); } -/// A conveninent [`CodeManipulator`] using [`nix`] with `mprotect`. -#[cfg(feature = "std")] -pub struct NixCodeManipulator { - /// Aligned addr - addr: core::ptr::NonNull, - /// Aligned length - length: usize, -} - -#[cfg(feature = "std")] -impl CodeManipulator for NixCodeManipulator { - unsafe fn mark_code_region_writable(addr: *const core::ffi::c_void, length: usize) -> Self { - use nix::sys::mman::ProtFlags; - // TODO: The page size should be probed using `sysconf`. - const PAGE_SIZE: usize = 4096; - let aligned_addr_val = (addr as usize) / PAGE_SIZE * PAGE_SIZE; - let aligned_addr = - core::ptr::NonNull::new_unchecked(aligned_addr_val as *mut core::ffi::c_void); - let aligned_length = if (addr as usize) + length - aligned_addr_val > PAGE_SIZE { - PAGE_SIZE * 2 - } else { - PAGE_SIZE - }; - nix::sys::mman::mprotect( - aligned_addr, - aligned_length, - ProtFlags::PROT_READ | ProtFlags::PROT_WRITE | ProtFlags::PROT_EXEC, - ) - .expect("Unable to make code region writable"); - Self { - addr: aligned_addr, - length: aligned_length, - } - } - - /// Due to limitation of Linux, we cannot get the original memory protection flags easily - /// without parsing `/proc/[pid]/maps`. As a result, we just make the code region non-writable. - unsafe fn restore_code_region_protect(&self) { - use nix::sys::mman::ProtFlags; - nix::sys::mman::mprotect( - self.addr, - self.length, - ProtFlags::PROT_READ | ProtFlags::PROT_EXEC, - ) - .expect("Unable to restore code region to non-writable"); - } -} - +/// Dummy code manipulator. Do nothing. Used to declare a dummy static key which is never modified pub(crate) struct DummyCodeManipulator; impl CodeManipulator for DummyCodeManipulator { diff --git a/src/lib.rs b/src/lib.rs index d55b306..c75458c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,9 +3,6 @@ #![feature(asm_goto)] #![feature(asm_const)] -#[cfg(feature = "std")] -extern crate std; - mod arch; pub mod code_manipulate; mod os; @@ -61,8 +58,8 @@ impl JumpEntry { } /// Unique reference to associated key - fn key_mut(&self) -> &mut NoStdStaticKey { - unsafe { &mut *(self.key_addr() as usize as *mut NoStdStaticKey) } + fn key_mut(&self) -> &mut GenericStaticKey { + unsafe { &mut *(self.key_addr() as usize as *mut GenericStaticKey) } } /// Whether this jump entry is dummy @@ -71,24 +68,18 @@ impl JumpEntry { } } -/// Static key to hold data about current status and which jump entries are associated with this key. -/// -/// For now, it is not encouraged to modify static key in a multi-thread application (which I don't think -/// is a common situation). +/// Static key generic over code manipulator. /// /// The `M: CodeManipulator` is required since when toggling the static key, the instructions recorded /// at associated jump entries need to be modified, which reside in `.text` section, which is a normally /// non-writable memory region. As a result, we need to change the protection of such memory region. /// -/// If you are in a std environment, just use [`StaticKey`], which is a convenient alias, utilizing -/// [`nix`] to modify memory protection. -/// /// The `const S: bool` indicates the initial status of this key. This value is determined /// at compile time, and only affect the initial generation of branch layout. All subsequent /// manually disabling and enabling will not be affected by the initial status. The struct /// layout is also consistent with different initial status. As a result, it is safe /// to assign arbitrary status to the static key generic when using. -pub struct NoStdStaticKey { +pub struct GenericStaticKey { /// Whether current key is true or false enabled: bool, /// Start address of associated jump entries. @@ -103,14 +94,14 @@ pub struct NoStdStaticKey { phantom: core::marker::PhantomData, } -/// A convenient alias for [`NoStdStaticKey`], utilizing [`nix`] for memory protection manipulation. -#[cfg(feature = "std")] -pub type StaticKey = NoStdStaticKey; +/// Static key to hold data about current status and which jump entries are associated with this key. +/// +/// For now, it is not encouraged to modify static key in a multi-thread application (which I don't think +/// is a common situation). +pub type StaticKey = GenericStaticKey; /// A [`StaticKey`] with initial status `true`. -#[cfg(feature = "std")] pub type StaticTrueKey = StaticKey; /// A [`StaticKey`] with initial status `false`. -#[cfg(feature = "std")] pub type StaticFalseKey = StaticKey; // Insert a dummy static key here, and use this at global_init function. This is @@ -121,10 +112,10 @@ pub type StaticFalseKey = StaticKey; // however, it seems a Rust bug to erase sections marked with "R" (retained). If we specify // --print-gc-sections for linker options, it's strange that linker itself does not // erase it. IT IS SO STRANGE. -static mut DUMMY_STATIC_KEY: NoStdStaticKey = - NoStdStaticKey::new(false); +static mut DUMMY_STATIC_KEY: GenericStaticKey = + GenericStaticKey::new(false); -impl NoStdStaticKey { +impl GenericStaticKey { /// Whether initial status is `true` #[inline(always)] pub const fn initial_enabled(&self) -> bool { @@ -154,48 +145,6 @@ impl NoStdStaticKey { pub fn disable(&mut self) { unsafe { static_key_update(self, false) } } - - /// Initialize the static keys data. Always call this method at beginning of application, before using any static key related - /// functionalities. Users in `std` environment should use [`global_init`] as convenience. - pub fn global_init() { - // DUMMY_STATIC_KEY will never changed, and this will always be a NOP. - if static_branch_unlikely!(DUMMY_STATIC_KEY) { - return; - } - let jump_entry_start_addr = core::ptr::addr_of_mut!(os::JUMP_ENTRY_START); - let jump_entry_stop_addr = core::ptr::addr_of_mut!(os::JUMP_ENTRY_STOP); - let jump_entry_len = - unsafe { jump_entry_stop_addr.offset_from(jump_entry_start_addr) as usize }; - let jump_entries = - unsafe { core::slice::from_raw_parts_mut(jump_entry_start_addr, jump_entry_len) }; - // Update jump entries to be absolute address - for jump_entry in jump_entries.iter_mut() { - if jump_entry.is_dummy() { - continue; - } - jump_entry.make_relative_address_absolute(); - } - // The jump entries are sorted by key address and code address - jump_entries - .sort_unstable_by_key(|jump_entry| (jump_entry.key_addr(), jump_entry.code_addr())); - // Update associated static keys - let mut last_key_addr = 0; - for jump_entry in jump_entries { - if jump_entry.is_dummy() { - continue; - } - let key_addr = jump_entry.key_addr(); - if key_addr == last_key_addr { - continue; - } - let entries_start_addr = jump_entry as *mut _ as usize; - // The S generic is useless here - let key = jump_entry.key_mut::(); - // Here we assign associated static key with the start address of jump entries - key.entries = entries_start_addr; - last_key_addr = key_addr; - } - } } /// Count of jump entries in __static_keys section. Note that @@ -209,9 +158,43 @@ pub fn jump_entries_count() { // ---------------------------- Create ---------------------------- /// Initialize the static keys data. Always call this method at beginning of application, before using any static key related /// functionalities. -#[cfg(feature = "std")] pub fn global_init() { - StaticTrueKey::global_init(); + // DUMMY_STATIC_KEY will never changed, and this will always be a NOP. + if static_branch_unlikely!(DUMMY_STATIC_KEY) { + return; + } + let jump_entry_start_addr = core::ptr::addr_of_mut!(os::JUMP_ENTRY_START); + let jump_entry_stop_addr = core::ptr::addr_of_mut!(os::JUMP_ENTRY_STOP); + let jump_entry_len = + unsafe { jump_entry_stop_addr.offset_from(jump_entry_start_addr) as usize }; + let jump_entries = + unsafe { core::slice::from_raw_parts_mut(jump_entry_start_addr, jump_entry_len) }; + // Update jump entries to be absolute address + for jump_entry in jump_entries.iter_mut() { + if jump_entry.is_dummy() { + continue; + } + jump_entry.make_relative_address_absolute(); + } + // The jump entries are sorted by key address and code address + jump_entries.sort_unstable_by_key(|jump_entry| (jump_entry.key_addr(), jump_entry.code_addr())); + // Update associated static keys + let mut last_key_addr = 0; + for jump_entry in jump_entries { + if jump_entry.is_dummy() { + continue; + } + let key_addr = jump_entry.key_addr(); + if key_addr == last_key_addr { + continue; + } + let entries_start_addr = jump_entry as *mut _ as usize; + // The M and S generic is useless here + let key = jump_entry.key_mut::(); + // Here we assign associated static key with the start address of jump entries + key.entries = entries_start_addr; + last_key_addr = key_addr; + } } /// Create a new static key with `false` as initial value. @@ -220,7 +203,6 @@ pub fn global_init() { /// to create a static key on stack or heap, and use this static key to control branches. /// /// Use [`define_static_key_false`] for short. -#[cfg(feature = "std")] pub const fn new_static_false_key() -> StaticFalseKey { StaticFalseKey::new(false) } @@ -231,7 +213,6 @@ pub const fn new_static_false_key() -> StaticFalseKey { /// to create a static key on stack or heap, and use this static key to control branches. /// /// Use [`define_static_key_true`] for short. -#[cfg(feature = "std")] pub const fn new_static_true_key() -> StaticTrueKey { StaticTrueKey::new(true) } @@ -248,7 +229,6 @@ pub const fn new_static_true_key() -> StaticTrueKey { /// /// define_static_key_false!(MY_FALSE_STATIC_KEY); /// ``` -#[cfg(feature = "std")] #[macro_export] macro_rules! define_static_key_false { ($key: ident) => { @@ -269,7 +249,6 @@ macro_rules! define_static_key_false { /// /// define_static_key_true!(MY_TRUE_STATIC_KEY); /// ``` -#[cfg(feature = "std")] #[macro_export] macro_rules! define_static_key_true { ($key: ident) => { @@ -279,11 +258,11 @@ macro_rules! define_static_key_true { } // ---------------------------- Update ---------------------------- -/// The internal method used for [`NoStdStaticKey::enable`] and [`NoStdStaticKey::disable`]. +/// The internal method used for [`GenericStaticKey::enable`] and [`GenericStaticKey::disable`]. /// /// This method will update instructions recorded in each jump entries that associated with thie static key unsafe fn static_key_update( - key: &mut NoStdStaticKey, + key: &mut GenericStaticKey, enabled: bool, ) { if key.enabled == enabled { diff --git a/src/os/linux.rs b/src/os/linux.rs index 39e6719..ee3f0be 100644 --- a/src/os/linux.rs +++ b/src/os/linux.rs @@ -1,6 +1,6 @@ //! Linux-specific implementations -use crate::JumpEntry; +use crate::{code_manipulate::CodeManipulator, JumpEntry}; // See https://sourceware.org/binutils/docs/as/Section.html /// Name and attribute of section storing jump entries @@ -23,3 +23,49 @@ extern "Rust" { #[link_name = "__stop___static_keys"] pub static mut JUMP_ENTRY_STOP: JumpEntry; } + +/// Arch-specific [`CodeManipulator`] using [`libc`] with `mprotect`. +pub struct ArchCodeManipulator { + /// Aligned addr + addr: *mut core::ffi::c_void, + /// Aligned length + length: usize, +} + +impl CodeManipulator for ArchCodeManipulator { + unsafe fn mark_code_region_writable(addr: *const core::ffi::c_void, length: usize) -> Self { + // TODO: page_size can be initialized once + let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }; + let aligned_addr_val = (addr as usize) / page_size * page_size; + let aligned_addr = aligned_addr_val as *mut core::ffi::c_void; + let aligned_length = if (addr as usize) + length - aligned_addr_val > page_size { + page_size * 2 + } else { + page_size + }; + let res = unsafe { + libc::mprotect( + aligned_addr, + aligned_length, + libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC, + ) + }; + if res != 0 { + panic!("Unable to make code region writable"); + } + Self { + addr: aligned_addr, + length: aligned_length, + } + } + + /// Due to limitation of Linux, we cannot get the original memory protection flags easily + /// without parsing `/proc/[pid]/maps`. As a result, we just make the code region non-writable. + unsafe fn restore_code_region_protect(&self) { + let res = + unsafe { libc::mprotect(self.addr, self.length, libc::PROT_READ | libc::PROT_EXEC) }; + if res != 0 { + panic!("Unable to restore code region to non-writable"); + } + } +}