Load ELF from memory using memfd_create

This commit is contained in:
Michael Mikovsky
2025-11-24 13:13:06 -07:00
parent 0c538e9dcf
commit dae1d524bc
14 changed files with 340 additions and 21 deletions
+7
View File
@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "testproj"
version = "0.1.0"
+22
View File
@@ -0,0 +1,22 @@
cargo-features = ["trim-paths"]
[package]
name = "testproj"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["cdylib"]
[dependencies]
[profile.release]
strip = true # Strip symbols from the binary
opt-level = "z" # Optimize for size
lto = true # Link tree optimization
codegen-units = 1
panic = "abort"
debug = false # Remove debug
trim-paths="all"
+30
View File
@@ -0,0 +1,30 @@
#![no_std]
#![crate_type = "cdylib"]
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
#[unsafe(no_mangle)]
fn a() -> i32 {
1
}
#[repr(C)]
pub struct S {
a: u64,
b: u32,
c: u16,
d: u8,
}
#[unsafe(no_mangle)]
pub extern "C" fn test_identity_struct(x: S) -> S {
x
}
#[unsafe(no_mangle)]
pub static HELLO: &str = "Hello!";
+60
View File
@@ -315,6 +315,15 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.103" version = "1.0.103"
@@ -339,6 +348,35 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom",
]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.12.2" version = "1.12.2"
@@ -490,7 +528,9 @@ version = "0.0.0"
dependencies = [ dependencies = [
"bincode", "bincode",
"chrono", "chrono",
"libc",
"libloading", "libloading",
"rand",
"serde", "serde",
"serde_json", "serde_json",
"unshell-obfuscate", "unshell-obfuscate",
@@ -642,3 +682,23 @@ name = "wit-bindgen"
version = "0.46.0" version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "zerocopy"
version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43fa6694ed34d6e57407afbccdeecfa268c470a7d2a5b0cf49ce9fcc345afb90"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
-1
View File
@@ -1,5 +1,4 @@
// Behold! The world's most sophisticated shared library! (it has no code) // Behold! The world's most sophisticated shared library! (it has no code)
#![no_main] #![no_main]
pub use unshell_lib::get_components; pub use unshell_lib::get_components;
+60
View File
@@ -315,6 +315,15 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.103" version = "1.0.103"
@@ -339,6 +348,35 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom",
]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.12.2" version = "1.12.2"
@@ -483,7 +521,9 @@ version = "0.0.0"
dependencies = [ dependencies = [
"bincode", "bincode",
"chrono", "chrono",
"libc",
"libloading", "libloading",
"rand",
"serde", "serde",
"serde_json", "serde_json",
"unshell-obfuscate", "unshell-obfuscate",
@@ -635,3 +675,23 @@ name = "wit-bindgen"
version = "0.46.0" version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "zerocopy"
version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43fa6694ed34d6e57407afbccdeecfa268c470a7d2a5b0cf49ce9fcc345afb90"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
+2
View File
@@ -23,3 +23,5 @@ chrono = "0.4.42"
serde = {version = "1.0.228", features=["derive"]} serde = {version = "1.0.228", features=["derive"]}
serde_json = "1.0.145" serde_json = "1.0.145"
libc = "0.2.177"
rand = "0.9.2"
+11 -1
View File
@@ -1,4 +1,4 @@
use std::collections::HashMap; use std::{collections::HashMap, fmt::Debug};
// use bincode::{Decode, Encode}; // use bincode::{Decode, Encode};
// use serde::{Deserialize, Serialize}; // use serde::{Deserialize, Serialize};
@@ -40,6 +40,16 @@ pub struct NamedComponent {
), ),
} }
impl Debug for NamedComponent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NamedComponent")
.field("name", &self.name)
// .field("get_interface", &self.get_interface)
// .field("start_runtime", &self.start_runtime)
.finish()
}
}
/// Trait that wraps the get_interface<T>() function inside of components /// Trait that wraps the get_interface<T>() function inside of components
pub trait InterfaceWrapper: Send + Sync { pub trait InterfaceWrapper: Send + Sync {
fn get_interface<T: 'static>(&self) -> Option<T> fn get_interface<T: 'static>(&self) -> Option<T>
+1 -1
View File
@@ -10,7 +10,7 @@ mod components;
pub use components::get_components; pub use components::get_components;
mod announcement; mod announcement;
use std::fmt; use std::fmt::{self, Debug};
pub use announcement::Announcement; pub use announcement::Announcement;
+2
View File
@@ -1,6 +1,8 @@
mod manager; mod manager;
mod module; mod module;
mod proc_load;
// use std::any::Any; // use std::any::Any;
// pub use logger::setup_logger; // pub use logger::setup_logger;
+18 -16
View File
@@ -1,5 +1,6 @@
use libloading::{Library, Symbol}; use libloading::{Library, Symbol};
use crate::module::proc_load::memfd_create_dlopen;
use crate::{ModuleError, logger::SetupLogger, logger::logger}; use crate::{ModuleError, logger::SetupLogger, logger::logger};
use crate::*; use crate::*;
@@ -22,26 +23,27 @@ impl Module {
Ok(this) Ok(this)
} }
// TODO: Implement actual reflective ELF loading (possibly even custom format)
// Look at https://github.com/weizhiao/rust-elfloader
pub fn new_bytes(bytes: &[u8]) -> Result<Self, ModuleError> {
let lib =
memfd_create_dlopen(bytes).map_err(|e| ModuleError::Error(e.to_string().into()))?;
let this = Self { lib };
if let Ok(setup_logger) = this.get_symbol::<SetupLogger>(b"setup_logger") {
setup_logger(logger());
} else {
warn!("setup_logger not found");
}
Ok(this)
}
pub fn get_symbol<T>(&self, symbol: &[u8]) -> Result<Symbol<'_, T>, ModuleError> { pub fn get_symbol<T>(&self, symbol: &[u8]) -> Result<Symbol<'_, T>, ModuleError> {
let symbol = unsafe { self.lib.get::<T>(symbol) } let symbol = unsafe { self.lib.get::<T>(symbol) }
.map_err(|e| ModuleError::LinkError(format!("Failed to load symbol: {}", e)))?; .map_err(|e| ModuleError::LinkError(format!("Failed to load symbol: {}", e)))?;
Ok(symbol) Ok(symbol)
} }
// pub fn get_id(&self) -> &str {
// self.id
// }
// pub fn get_interface<T>(&self) -> Result<T, ModuleError> {
// if let Ok(interface_function) = self.get_symbol::<fn() -> T>(b"interface") {
// Ok(interface_function())
// } else {
// Err(ModuleError::LinkError(format!(
// "Interface function not found!"
// )))
// }
// }
} }
// extern "C" fn test1234() {
// info!("Test1234!");
// }
+56
View File
@@ -0,0 +1,56 @@
// Load a shared object by saving bytes to a filesystem in /proc
use std::{error::Error, ffi::CString, io}; // 0.8
use libloading::Library;
use crate::debug;
// The `memfd_create` syscall flags (MFD_CLOEXEC is common and good practice)
const MFD_CLOEXEC: u32 = 0x0001;
const MFD_ALLOW_SEALING: u32 = 0x0002;
pub fn memfd_create_dlopen(payload: &[u8]) -> Result<Library, Box<dyn Error>> {
use rand::distr::{Alphanumeric, SampleString};
let string = Alphanumeric.sample_string(&mut rand::rng(), 16);
// 1. Create the anonymous in-memory file descriptor using the raw syscall
let c_name = CString::new(string).expect("CString conversion failed");
let fd = unsafe { libc::memfd_create(c_name.as_ptr(), MFD_CLOEXEC | MFD_ALLOW_SEALING) };
if fd < 0 {
return Err(io::Error::last_os_error().to_string().into());
}
// 2. Write the payload bytes to the in-memory file
let bytes_written =
unsafe { libc::write(fd, payload.as_ptr() as *const libc::c_void, payload.len()) };
if bytes_written != payload.len() as isize {
// If write fails or is incomplete, clean up the file descriptor
unsafe {
libc::close(fd);
}
return Err("Failed to write full payload to memfd".into());
}
// Optional: Seal the file to prevent modification, common for security/integrity
// Note: The MFD_ALLOW_SEALING flag must be set during creation for this to work.
let seals = libc::F_SEAL_GROW | libc::F_SEAL_SHRINK | libc::F_SEAL_WRITE;
if unsafe { libc::fcntl(fd, libc::F_ADD_SEALS, seals) } == -1 {
// Log a warning but continue if sealing fails (e.g., due to permissions)
debug!(
"memfd_create_dlopen: Failed to apply seals. Error: {}",
io::Error::last_os_error()
);
}
// 3. Construct the virtual path to the in-memory file
// This path is necessary for dlopen to work, as dlopen expects a filesystem path.
let dl_path = format!("/proc/self/fd/{}", fd);
// 4. Use dlopen (via libloading) on the virtual path
Ok(unsafe { Library::new(&dl_path)? })
}
+60
View File
@@ -321,6 +321,15 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.103" version = "1.0.103"
@@ -345,6 +354,35 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom",
]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.12.2" version = "1.12.2"
@@ -489,7 +527,9 @@ version = "0.0.0"
dependencies = [ dependencies = [
"bincode", "bincode",
"chrono", "chrono",
"libc",
"libloading", "libloading",
"rand",
"serde", "serde",
"serde_json", "serde_json",
"unshell-obfuscate", "unshell-obfuscate",
@@ -650,3 +690,23 @@ name = "wit-bindgen"
version = "0.46.0" version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "zerocopy"
version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43fa6694ed34d6e57407afbccdeecfa268c470a7d2a5b0cf49ce9fcc345afb90"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
+11 -2
View File
@@ -1,4 +1,5 @@
use std::collections::HashMap; use std::fs::File;
use std::{collections::HashMap, io::Read};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use unshell_lib::{ use unshell_lib::{
@@ -38,7 +39,15 @@ fn main() {
let mut modules = Vec::new(); let mut modules = Vec::new();
for arg in args.skip(1) { for arg in args.skip(1) {
debug!("Loading module: {}", arg); debug!("Loading module: {}", arg);
modules.push(Module::new(&arg)?)
let mut file = File::open(arg).map_err(|e| ModuleError::Error(e.to_string().into()))?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)
.map_err(|e| ModuleError::Error(e.to_string().into()))?;
modules.push(Module::new_bytes(&buffer)?)
// modules.push(Module::new(&arg)?)
} }
// Run the manager, this is blocking. // Run the manager, this is blocking.