Skip to main content

px_userland_execve/
loader.rs

1use goblin::{
2    elf::{self, Elf},
3    elf64::program_header::PT_LOAD,
4};
5use nix::{
6    sys::mman::{mmap, MapFlags, ProtFlags},
7    unistd::{sysconf, SysconfVar},
8};
9use std::{
10    fs::File,
11    num::NonZeroUsize,
12    os::fd::BorrowedFd,
13    path::{Path, PathBuf},
14    ptr,
15};
16
17#[derive(Clone)]
18#[non_exhaustive]
19pub enum Executable {
20    Path(PathBuf),
21    Bytes { data: Vec<u8>, fake_path: PathBuf },
22}
23
24pub enum Interpreter {
25    FromHeader,
26    None,
27    Path(PathBuf),
28}
29
30pub fn load(
31    executable: Executable,
32    interpreter: Interpreter,
33) -> (usize, elf::Header, Option<(usize, elf::Header)>) {
34    let (file, bytes) = match executable {
35        Executable::Path(path) => (
36            Some(File::open(&path).unwrap()),
37            std::fs::read(path).unwrap(),
38        ),
39        Executable::Bytes { data, fake_path: _ } => (None, data),
40    };
41    let elf = Elf::parse(&bytes).unwrap();
42    let interp_path: Option<PathBuf> = match interpreter {
43        Interpreter::FromHeader => elf.interpreter.as_ref().map(|p| {
44            let p: &Path = p.as_ref();
45            p.to_path_buf()
46        }),
47        Interpreter::None => None,
48        Interpreter::Path(path) => Some(path),
49    };
50    let opt_interp = match interp_path {
51        Some(interp) => {
52            let (interp_load_addr, interp_header, None) =
53                load(Executable::Path(interp), Interpreter::FromHeader)
54            else {
55                panic!()
56            };
57            Some((interp_load_addr, interp_header))
58        }
59        None => None,
60    };
61    let is_pie = elf
62        .program_headers
63        .iter()
64        .find(|h| h.p_type == PT_LOAD)
65        .unwrap()
66        .p_vaddr
67        == 0;
68    assert!(is_pie);
69    let total_size: usize = elf
70        .program_headers
71        .iter()
72        .filter(|h| h.p_type == PT_LOAD)
73        .map(|h| h.p_vaddr + h.p_memsz)
74        .max()
75        .unwrap()
76        .try_into()
77        .unwrap();
78    let total_size = NonZeroUsize::new(total_size).unwrap();
79    let base_ptr = unsafe {
80        mmap::<BorrowedFd>(
81            None,
82            total_size,
83            ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, // TODO: read only fix
84            MapFlags::MAP_PRIVATE | MapFlags::MAP_ANON,
85            None,
86            0,
87        )
88    }
89    .unwrap();
90    let base_addr = base_ptr as usize;
91
92    let page_size: usize = sysconf(SysconfVar::PAGE_SIZE)
93        .unwrap()
94        .unwrap()
95        .try_into()
96        .unwrap();
97    let page_round_down = |addr: usize| addr / page_size * page_size;
98    let page_round_up = |addr: usize| (addr + (page_size - 1)) / page_size * page_size;
99    for ph in elf.program_headers {
100        if ph.p_type != PT_LOAD {
101            continue;
102        }
103        assert!(ph.p_memsz >= ph.p_filesz);
104
105        let size: usize = ph.p_filesz.try_into().unwrap();
106        let prot = (ph.p_flags >> 2) | ((ph.p_flags & 0b001) << 2) | (ph.p_flags & 0b010);
107        let prot = prot.try_into().unwrap();
108        let prot = ProtFlags::from_bits(prot).unwrap();
109        let offset: usize = ph.p_offset.try_into().unwrap();
110        let vaddr: usize = ph.p_vaddr.try_into().unwrap();
111        let unaligned_addr = base_addr + vaddr;
112        let addr = page_round_down(unaligned_addr);
113        let align_dist = unaligned_addr - addr;
114        let size = size + align_dist;
115        let size = NonZeroUsize::new(size).unwrap();
116        let offset = offset - align_dist;
117        let offset = offset.try_into().unwrap();
118        let addr = NonZeroUsize::new(addr).unwrap();
119        let flags = MapFlags::MAP_PRIVATE | MapFlags::MAP_FIXED;
120        let (flags, offset_arg) = match file {
121            Some(_) => (flags, offset),
122            None => (flags | MapFlags::MAP_ANONYMOUS, 0),
123        };
124        dbg!(false, &file, addr);
125        unsafe {
126            mmap(
127                Some(addr),
128                size,
129                prot | ProtFlags::PROT_WRITE, // TODO: read only fix
130                flags,
131                file.as_ref(),
132                offset_arg,
133            )
134        }
135        .unwrap();
136        if file.is_none() {
137            unsafe {
138                let o = offset as usize;
139                std::ptr::copy_nonoverlapping(
140                    bytes[o..o + size.get()].as_ptr(),
141                    addr.get() as *mut u8,
142                    size.get(),
143                )
144            }
145        }
146        let file_end_addr = addr.get() + size.get();
147        unsafe {
148            ptr::write_bytes(
149                file_end_addr as *mut u8,
150                0,
151                page_round_up(file_end_addr) - file_end_addr,
152            );
153        }
154    }
155
156    // Relocations (needed for musl but not glibc FWICT)
157    for rel in elf.dynrelas.iter() {
158        let offset: usize = rel.r_offset.try_into().unwrap();
159        let addend: usize = rel.r_addend.unwrap().try_into().unwrap();
160        let dst = (base_addr + offset) as *mut usize;
161        let src = base_addr + addend;
162        unsafe { ptr::write(dst, src) }
163    }
164
165    (base_addr, elf.header, opt_interp)
166}