px_userland_execve/
loader.rs1use 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, 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, 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 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}