Skip to content

Commit

Permalink
Remove std feature since we can also use libc in no_std. Use libc ins…
Browse files Browse the repository at this point in the history
…tead of nix to implement code manipulator for Linux
  • Loading branch information
Evian-Zhang committed Jul 25, 2024
1 parent 6b31886 commit d27cfbd
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 163 deletions.
15 changes: 9 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 }}

Expand Down
32 changes: 1 addition & 31 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 2 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
49 changes: 1 addition & 48 deletions src/code_manipulate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<core::ffi::c_void>,
/// 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 {
Expand Down
121 changes: 50 additions & 71 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
#![feature(asm_goto)]
#![feature(asm_const)]

#[cfg(feature = "std")]
extern crate std;

mod arch;
pub mod code_manipulate;
mod os;
Expand Down Expand Up @@ -61,8 +58,8 @@ impl JumpEntry {
}

/// Unique reference to associated key
fn key_mut<M: CodeManipulator, const S: bool>(&self) -> &mut NoStdStaticKey<M, S> {
unsafe { &mut *(self.key_addr() as usize as *mut NoStdStaticKey<M, S>) }
fn key_mut<M: CodeManipulator, const S: bool>(&self) -> &mut GenericStaticKey<M, S> {
unsafe { &mut *(self.key_addr() as usize as *mut GenericStaticKey<M, S>) }
}

/// Whether this jump entry is dummy
Expand All @@ -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<M: CodeManipulator, const S: bool> {
pub struct GenericStaticKey<M: CodeManipulator, const S: bool> {
/// Whether current key is true or false
enabled: bool,
/// Start address of associated jump entries.
Expand All @@ -103,14 +94,14 @@ pub struct NoStdStaticKey<M: CodeManipulator, const S: bool> {
phantom: core::marker::PhantomData<M>,
}

/// A convenient alias for [`NoStdStaticKey`], utilizing [`nix`] for memory protection manipulation.
#[cfg(feature = "std")]
pub type StaticKey<const S: bool> = NoStdStaticKey<crate::code_manipulate::NixCodeManipulator, S>;
/// 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<const S: bool> = GenericStaticKey<crate::os::ArchCodeManipulator, S>;
/// A [`StaticKey`] with initial status `true`.
#[cfg(feature = "std")]
pub type StaticTrueKey = StaticKey<true>;
/// A [`StaticKey`] with initial status `false`.
#[cfg(feature = "std")]
pub type StaticFalseKey = StaticKey<false>;

// Insert a dummy static key here, and use this at global_init function. This is
Expand All @@ -121,10 +112,10 @@ pub type StaticFalseKey = StaticKey<false>;
// 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<code_manipulate::DummyCodeManipulator, false> =
NoStdStaticKey::new(false);
static mut DUMMY_STATIC_KEY: GenericStaticKey<code_manipulate::DummyCodeManipulator, false> =
GenericStaticKey::new(false);

impl<M: CodeManipulator, const S: bool> NoStdStaticKey<M, S> {
impl<M: CodeManipulator, const S: bool> GenericStaticKey<M, S> {
/// Whether initial status is `true`
#[inline(always)]
pub const fn initial_enabled(&self) -> bool {
Expand Down Expand Up @@ -154,48 +145,6 @@ impl<M: CodeManipulator, const S: bool> NoStdStaticKey<M, S> {
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::<M, true>();
// 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
Expand All @@ -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::<code_manipulate::DummyCodeManipulator, true>();
// 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.
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -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) => {
Expand All @@ -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) => {
Expand All @@ -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<M: CodeManipulator, const S: bool>(
key: &mut NoStdStaticKey<M, S>,
key: &mut GenericStaticKey<M, S>,
enabled: bool,
) {
if key.enabled == enabled {
Expand Down
Loading

0 comments on commit d27cfbd

Please sign in to comment.