Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,36 @@ fn path_to_c_string(path: &Path) -> CString {
.unwrap()
}

fn close_fds() {
if let Ok(dir) = std::fs::read_dir("/proc/self/fd") {
let fds: Vec<i32> = dir
.flatten()
.filter_map(|e| e.file_name().to_str()?.parse().ok())
.filter(|&fd| fd > 2)
.collect();
for fd in fds {
unsafe {
nix::libc::close(fd);
}
}
}
}

fn reset_signals() {
unsafe {
let mut sigset: nix::libc::sigset_t = std::mem::zeroed();
nix::libc::sigemptyset(&mut sigset);
nix::libc::sigprocmask(nix::libc::SIG_SETMASK, &sigset, std::ptr::null_mut());

let mut sa: nix::libc::sigaction = std::mem::zeroed();
sa.sa_sigaction = nix::libc::SIG_DFL;
nix::libc::sigemptyset(&mut sa.sa_mask);
for sig in 1..64 {
nix::libc::sigaction(sig, &sa, std::ptr::null_mut());
}
}
}

pub fn exec_with_options(options: ExecOptions) -> ! {
let (bin_addr, bin_header, opt_interp) =
crate::loader::load(&options.executable, options.interpreter);
Expand All @@ -30,6 +60,28 @@ pub fn exec_with_options(options: ExecOptions) -> ! {
&options.args,
&options.env,
);

if let Some(file_name) = options.executable.file_name() {
if let Some(name_str) = file_name.to_str() {
let mut name = name_str.to_owned();
name.truncate(15);
if let Ok(c_name) = CString::new(name) {
unsafe {
nix::libc::prctl(
nix::libc::PR_SET_NAME,
c_name.as_ptr() as std::primitive::usize,
0,
0,
0,
);
}
}
}
}

close_fds();
reset_signals();

let entry = match opt_interp {
Some((interp_addr, interp_header)) => {
let interp_entry: usize = interp_header.e_entry.try_into().unwrap();
Expand Down
18 changes: 15 additions & 3 deletions src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ pub fn load(
mmap::<BorrowedFd>(
None,
total_size,
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, // TODO: read only fix
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
MapFlags::MAP_PRIVATE | MapFlags::MAP_ANON,
None,
0,
Expand All @@ -79,7 +79,11 @@ pub fn load(
.try_into()
.unwrap();
let page_round_down = |addr: usize| addr / page_size * page_size;
let page_round_up = |addr: usize| (addr + (page_size - 1)) / page_size * page_size;
let page_round_up = |addr: usize| addr.div_ceil(page_size) * page_size;

// Track each segment's final address, size, and protection for mprotect later
let mut segments: Vec<(usize, usize, ProtFlags)> = Vec::new();

for ph in elf.program_headers {
if ph.p_type != PT_LOAD {
continue;
Expand All @@ -104,7 +108,7 @@ pub fn load(
mmap(
Some(addr),
size,
prot | ProtFlags::PROT_WRITE, // TODO: read only fix
prot | ProtFlags::PROT_WRITE,
MapFlags::MAP_PRIVATE | MapFlags::MAP_FIXED,
Some(&file),
offset,
Expand All @@ -119,6 +123,7 @@ pub fn load(
page_round_up(file_end_addr) - file_end_addr,
);
}
segments.push((addr.get(), size.get(), prot));
}

// Relocations (needed for musl but not glibc FWICT)
Expand All @@ -130,5 +135,12 @@ pub fn load(
unsafe { ptr::write(dst, src) }
}

// Apply final memory protections after relocations are done
for (addr, size, prot) in segments {
unsafe {
nix::sys::mman::mprotect(addr as *mut nix::libc::c_void, size, prot).unwrap();
}
}

(base_addr, elf.header, opt_interp)
}
252 changes: 206 additions & 46 deletions src/run.rs
Original file line number Diff line number Diff line change
@@ -1,59 +1,219 @@
use std::arch::asm;
use std::arch::global_asm;
use std::num::NonZeroUsize;
use std::os::fd::BorrowedFd;
use std::ptr;

#[cfg(target_arch = "x86_64")]
pub unsafe fn run(sp: usize, entry: usize) -> ! {
asm! {
"mov rsp, {sp}",
"jmp {entry}",
inout("rax") 0 => _,
sp = in(reg) sp,
entry = in(reg) entry,
}
unreachable!()
use nix::sys::mman::{mmap, MapFlags, ProtFlags};

#[repr(C)]
pub struct Region {
pub addr: usize,
pub len: usize,
}

#[cfg(target_arch = "x86_64")]
global_asm!(
r#"
.global tramp_start
.global tramp_end
tramp_start:
mov r12, rdi
mov r13, rsi
mov r14, rdx
mov r15, rcx
test r13, r13
jz 2f
1:
mov rax, 11
mov rdi, [r12]
mov rsi, [r12 + 8]
syscall
add r12, 16
dec r13
jnz 1b
2:
mov rsp, r14
jmp r15
tramp_end:
"#
);

#[cfg(target_arch = "aarch64")]
pub unsafe fn run(sp: usize, entry: usize) -> ! {
asm! {
"mov sp, {sp}",
"br {entry}",
sp = in(reg) sp,
entry = in(reg) entry,
}
unreachable!()
}
global_asm!(
r#"
.global tramp_start
.global tramp_end
tramp_start:
mov x19, x0
mov x20, x1
mov x21, x2
mov x22, x3
cbz x20, 2f
1:
ldr x0, [x19]
ldr x1, [x19, #8]
mov x8, #215
svc #0
add x19, x19, #16
sub x20, x20, #1
cbnz x20, 1b
2:
mov sp, x21
br x22
tramp_end:
"#
);

#[cfg(target_arch = "riscv64")]
pub unsafe fn run(sp: usize, entry: usize) -> ! {
asm! {
"mv sp, {sp}",
"jr {entry}",
sp = in(reg) sp,
entry = in(reg) entry,
}
unreachable!()
}
global_asm!(
r#"
.global tramp_start
.global tramp_end
tramp_start:
mv s0, a0
mv s1, a1
mv s2, a2
mv s3, a3
beqz s1, 2f
1:
ld a0, 0(s0)
ld a1, 8(s0)
li a7, 215
ecall
addi s0, s0, 16
addi s1, s1, -1
bnez s1, 1b
2:
mv sp, s2
jr s3
tramp_end:
"#
);

#[cfg(target_arch = "loongarch64")]
pub unsafe fn run(sp: usize, entry: usize) -> ! {
asm! {
"move $sp, {sp}",
"jirl $zero, {entry}, 0",
sp = in(reg) sp,
entry = in(reg) entry,
}
unreachable!()
}
global_asm!(
r#"
.global tramp_start
.global tramp_end
tramp_start:
move $s0, $a0
move $s1, $a1
move $s2, $a2
move $s3, $a3
beqz $s1, 2f
1:
ld.d $a0, $s0, 0
ld.d $a1, $s0, 8
li.d $a7, 215
syscall 0
addi.d $s0, $s0, 16
addi.d $s1, $s1, -1
bnez $s1, 1b
2:
move $sp, $s2
jirl $zero, $s3, 0
tramp_end:
"#
);

#[cfg(target_arch = "powerpc64")]
global_asm!(
r#"
.global tramp_start
.global tramp_end
tramp_start:
mr 14, 3
mr 15, 4
mr 16, 5
mr 17, 6
cmpdi 15, 0
beq 2f
1:
ld 3, 0(14)
ld 4, 8(14)
li 0, 73
sc
addi 14, 14, 16
addi 15, 15, -1
cmpdi 15, 0
bne 1b
2:
mtctr 17
mr 12, 17
mr 1, 16
bctr
tramp_end:
"#
);

extern "C" {
static tramp_start: u8;
static tramp_end: u8;
}

pub unsafe fn run(sp: usize, entry: usize) -> ! {
asm! {
"mtctr {entry}",
"mr 12, {entry}",
"mr 1, {sp}",
"bctr",
sp = in(reg) sp,
entry = in(reg) entry,
let mut regions = Vec::new();

// Find every memory region mapped by the Rust loader
if let Ok(maps) = std::fs::read_to_string("/proc/self/maps") {
if let Ok(exe_path) = std::fs::read_link("/proc/self/exe") {
if let Some(exe_str) = exe_path.to_str() {
for line in maps.lines() {
if line.ends_with(exe_str) {
let mut parts = line.split_whitespace();
if let Some(range) = parts.next() {
let mut range_parts = range.split('-');
if let (Some(start_str), Some(end_str)) =
(range_parts.next(), range_parts.next())
{
if let (Ok(start), Ok(end)) = (
usize::from_str_radix(start_str, 16),
usize::from_str_radix(end_str, 16),
) {
regions.push(Region {
addr: start,
len: end - start,
});
}
}
}
}
}
}
}
}
unreachable!()

let start_ptr = &tramp_start as *const u8 as usize;
let end_ptr = &tramp_end as *const u8 as usize;
let tramp_size = end_ptr - start_ptr;

// Allocate 1 page for the trampoline and the regions array
let tramp_page = mmap::<BorrowedFd>(
None,
NonZeroUsize::new(4096).unwrap(),
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE | ProtFlags::PROT_EXEC,
MapFlags::MAP_PRIVATE | MapFlags::MAP_ANON,
None,
0,
)
.unwrap() as usize;

// Copy the raw assembly machine code to the isolated page
ptr::copy_nonoverlapping(start_ptr as *const u8, tramp_page as *mut u8, tramp_size);

// Copy the regions array safely away from the Rust heap
let regions_ptr = (tramp_page + 2048) as *mut Region;
ptr::copy_nonoverlapping(regions.as_ptr(), regions_ptr, regions.len());

nix::sys::mman::mprotect(
tramp_page as *mut nix::libc::c_void,
4096,
ProtFlags::PROT_READ | ProtFlags::PROT_EXEC,
)
.unwrap();

// Jump to the trampoline!
let tramp_func: extern "C" fn(*const Region, usize, usize, usize) -> ! =
std::mem::transmute(tramp_page);
tramp_func(regions_ptr, regions.len(), sp, entry);
}
Loading