⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/elf/
dynamic.rs

1//! ELF Dynamic Linker Bootstrap Support
2//!
3//! Provides auxiliary vector construction, interpreter discovery, and dynamic
4//! linker preparation for running dynamically-linked ELF binaries.
5
6// ELF dynamic linker bootstrap
7
8#[cfg(feature = "alloc")]
9extern crate alloc;
10
11#[cfg(feature = "alloc")]
12use alloc::{string::String, vec::Vec};
13
14use super::types::{Elf64ProgramHeader, ElfBinary, ProgramType};
15use crate::{error::KernelError, mm::PAGE_SIZE};
16
17// ---------------------------------------------------------------------------
18// Auxiliary Vector Types
19// ---------------------------------------------------------------------------
20
21/// Auxiliary vector entry type identifiers (from the ELF specification / Linux
22/// ABI).
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24#[repr(u64)]
25pub enum AuxType {
26    /// End of vector (sentinel).
27    AtNull = 0,
28    /// Program headers address in memory.
29    AtPhdr = 3,
30    /// Size of one program header entry.
31    AtPhent = 4,
32    /// Number of program headers.
33    AtPhnum = 5,
34    /// System page size.
35    AtPagesz = 6,
36    /// Interpreter base address.
37    AtBase = 7,
38    /// Entry point of the binary.
39    AtEntry = 9,
40    /// Address of 16 random bytes (for stack canaries / ASLR).
41    AtRandom = 25,
42    /// Filename of the program being run.
43    AtExecfn = 31,
44}
45
46/// A single entry in the auxiliary vector passed to the dynamic linker.
47#[derive(Debug, Clone, Copy)]
48pub struct AuxVecEntry {
49    /// The type of this entry.
50    pub type_id: AuxType,
51    /// The value associated with this type.
52    pub value: u64,
53}
54
55impl AuxVecEntry {
56    /// Create a new auxiliary vector entry.
57    pub fn new(type_id: AuxType, value: u64) -> Self {
58        Self { type_id, value }
59    }
60}
61
62// ---------------------------------------------------------------------------
63// Dynamic Linker Info
64// ---------------------------------------------------------------------------
65
66/// Information required to set up the dynamic linker for a binary.
67#[cfg(feature = "alloc")]
68pub struct DynamicLinkerInfo {
69    /// Path to the interpreter (e.g., `/lib/ld-linux.so.2`).
70    pub interp_path: String,
71    /// Base address at which the interpreter was loaded.
72    pub interp_base: u64,
73    /// Entry point of the interpreter.
74    pub interp_entry: u64,
75    /// Auxiliary vector to place on the initial user stack.
76    pub aux_vector: Vec<AuxVecEntry>,
77}
78
79// ---------------------------------------------------------------------------
80// Interpreter Discovery
81// ---------------------------------------------------------------------------
82
83/// Find the interpreter path from a list of program headers.
84///
85/// Scans for a `PT_INTERP` segment and extracts the null-terminated path
86/// string from the ELF data. Returns `None` for statically linked binaries.
87#[cfg(feature = "alloc")]
88pub fn find_interpreter(
89    data: &[u8],
90    program_headers: &[Elf64ProgramHeader],
91) -> Result<Option<String>, KernelError> {
92    for ph in program_headers {
93        if ph.p_type == ProgramType::Interp as u32 {
94            let offset = ph.p_offset as usize;
95            let size = ph.p_filesz as usize;
96
97            if size == 0 {
98                return Err(KernelError::InvalidArgument {
99                    name: "PT_INTERP",
100                    value: "empty interpreter path",
101                });
102            }
103
104            if offset.checked_add(size).is_none() || offset + size > data.len() {
105                return Err(KernelError::InvalidArgument {
106                    name: "PT_INTERP",
107                    value: "segment extends past end of file",
108                });
109            }
110
111            let interp_bytes = &data[offset..offset + size];
112
113            // Remove trailing null terminator if present.
114            let path_bytes = if interp_bytes.last() == Some(&0) {
115                &interp_bytes[..size - 1]
116            } else {
117                interp_bytes
118            };
119
120            let path =
121                core::str::from_utf8(path_bytes).map_err(|_| KernelError::InvalidArgument {
122                    name: "PT_INTERP",
123                    value: "interpreter path is not valid UTF-8",
124                })?;
125
126            return Ok(Some(String::from(path)));
127        }
128    }
129
130    Ok(None)
131}
132
133// ---------------------------------------------------------------------------
134// Auxiliary Vector Construction
135// ---------------------------------------------------------------------------
136
137/// Build an auxiliary vector for the dynamic linker.
138///
139/// The auxiliary vector is placed on the user stack before the process starts.
140/// It provides the dynamic linker with information about the loaded binary.
141///
142/// # Arguments
143/// * `elf_binary` - Parsed ELF binary information.
144/// * `phdr_addr`  - Address where program headers are loaded in memory.
145/// * `interp_base` - Base address of the loaded interpreter (0 if none).
146/// * `random_addr` - Address of 16 random bytes for AT_RANDOM.
147/// * `execfn_addr` - Address of the filename string.
148#[cfg(feature = "alloc")]
149pub fn build_aux_vector(
150    elf_binary: &ElfBinary,
151    phdr_addr: u64,
152    interp_base: u64,
153    random_addr: u64,
154    execfn_addr: u64,
155) -> Vec<AuxVecEntry> {
156    let phdr_count = elf_binary
157        .segments
158        .iter()
159        .filter(|s| s.segment_type == super::types::SegmentType::Phdr)
160        .count();
161
162    // If no PHDR segment, count all segments as accessible via phdr_addr.
163    let phnum = if phdr_count > 0 {
164        phdr_count as u64
165    } else {
166        elf_binary.segments.len() as u64
167    };
168
169    let phent_size = core::mem::size_of::<Elf64ProgramHeader>() as u64;
170
171    let mut aux = Vec::with_capacity(10);
172
173    aux.push(AuxVecEntry::new(AuxType::AtPhdr, phdr_addr));
174    aux.push(AuxVecEntry::new(AuxType::AtPhent, phent_size));
175    aux.push(AuxVecEntry::new(AuxType::AtPhnum, phnum));
176    aux.push(AuxVecEntry::new(AuxType::AtPagesz, PAGE_SIZE as u64));
177    aux.push(AuxVecEntry::new(AuxType::AtEntry, elf_binary.entry_point));
178
179    if interp_base != 0 {
180        aux.push(AuxVecEntry::new(AuxType::AtBase, interp_base));
181    }
182
183    if random_addr != 0 {
184        aux.push(AuxVecEntry::new(AuxType::AtRandom, random_addr));
185    }
186
187    if execfn_addr != 0 {
188        aux.push(AuxVecEntry::new(AuxType::AtExecfn, execfn_addr));
189    }
190
191    // Terminate the vector.
192    aux.push(AuxVecEntry::new(AuxType::AtNull, 0));
193
194    aux
195}
196
197// ---------------------------------------------------------------------------
198// Dynamic Linking Preparation
199// ---------------------------------------------------------------------------
200
201/// Prepare everything needed to run a dynamically linked ELF binary.
202///
203/// This function:
204/// 1. Checks whether the binary has an interpreter.
205/// 2. Loads the interpreter (if present) using the ELF loader.
206/// 3. Builds the auxiliary vector for the dynamic linker.
207///
208/// Returns a [`DynamicLinkerInfo`] with all information needed to start the
209/// process. If the binary is statically linked, returns `None`.
210///
211/// # Arguments
212/// * `_elf_data`   - Raw bytes of the ELF file (reserved for future use).
213/// * `elf_binary`  - Pre-parsed ELF binary metadata.
214/// * `load_base`   - Base address where the main binary was loaded.
215#[cfg(feature = "alloc")]
216pub fn prepare_dynamic_linking(
217    _elf_data: &[u8],
218    elf_binary: &ElfBinary,
219    load_base: u64,
220) -> Result<Option<DynamicLinkerInfo>, KernelError> {
221    // Check whether the binary has an interpreter.
222    let interp_path = match &elf_binary.interpreter {
223        Some(path) => path.clone(),
224        None => return Ok(None), // Statically linked.
225    };
226
227    // Load the interpreter ELF binary from the filesystem.
228    let interp_elf =
229        super::load_elf_from_file(&interp_path).map_err(|_| KernelError::NotFound {
230            resource: "interpreter",
231            id: 0,
232        })?;
233
234    let interp_base = interp_elf.load_base;
235    let interp_entry = interp_elf.entry_point;
236
237    // The program headers are located at load_base + phoff. For a standard
238    // ELF the PHDR segment points to them. Use load_base as the phdr address
239    // for now; the kernel stack setup will place them correctly.
240    let phdr_addr = load_base;
241
242    // Build auxiliary vector. AT_RANDOM and AT_EXECFN addresses will be set
243    // up later when the user stack is constructed.
244    let aux_vector = build_aux_vector(elf_binary, phdr_addr, interp_base, 0, 0);
245
246    Ok(Some(DynamicLinkerInfo {
247        interp_path,
248        interp_base,
249        interp_entry,
250        aux_vector,
251    }))
252}
253
254#[cfg(test)]
255mod tests {
256    use alloc::vec;
257
258    use super::*;
259
260    #[test]
261    fn test_aux_type_values() {
262        assert_eq!(AuxType::AtNull as u64, 0);
263        assert_eq!(AuxType::AtPhdr as u64, 3);
264        assert_eq!(AuxType::AtPhent as u64, 4);
265        assert_eq!(AuxType::AtPhnum as u64, 5);
266        assert_eq!(AuxType::AtPagesz as u64, 6);
267        assert_eq!(AuxType::AtBase as u64, 7);
268        assert_eq!(AuxType::AtEntry as u64, 9);
269        assert_eq!(AuxType::AtRandom as u64, 25);
270        assert_eq!(AuxType::AtExecfn as u64, 31);
271    }
272
273    #[test]
274    fn test_aux_vec_entry_new() {
275        let entry = AuxVecEntry::new(AuxType::AtPagesz, 4096);
276        assert_eq!(entry.type_id, AuxType::AtPagesz);
277        assert_eq!(entry.value, 4096);
278    }
279
280    #[test]
281    fn test_find_interpreter_no_interp() {
282        // Empty program headers -- no interpreter.
283        let headers: Vec<Elf64ProgramHeader> = Vec::new();
284        let data = &[];
285        let result = find_interpreter(data, &headers);
286        assert!(result.is_ok());
287        assert!(result.unwrap().is_none());
288    }
289
290    #[test]
291    fn test_find_interpreter_with_interp() {
292        let interp_path = b"/lib/ld-linux.so.2\0";
293        let mut data = vec![0u8; 128];
294        let offset = 64usize;
295        data[offset..offset + interp_path.len()].copy_from_slice(interp_path);
296
297        let headers = vec![Elf64ProgramHeader {
298            p_type: ProgramType::Interp as u32,
299            p_flags: 4, // PF_R
300            p_offset: offset as u64,
301            p_vaddr: 0,
302            p_paddr: 0,
303            p_filesz: interp_path.len() as u64,
304            p_memsz: interp_path.len() as u64,
305            p_align: 1,
306        }];
307
308        let result = find_interpreter(&data, &headers);
309        assert!(result.is_ok());
310        let interp = result.unwrap();
311        assert!(interp.is_some());
312        assert_eq!(interp.unwrap(), "/lib/ld-linux.so.2");
313    }
314
315    #[test]
316    fn test_find_interpreter_invalid_offset() {
317        let headers = vec![Elf64ProgramHeader {
318            p_type: ProgramType::Interp as u32,
319            p_flags: 4,
320            p_offset: 1000, // Past end of data.
321            p_vaddr: 0,
322            p_paddr: 0,
323            p_filesz: 20,
324            p_memsz: 20,
325            p_align: 1,
326        }];
327
328        let data = &[0u8; 10];
329        let result = find_interpreter(data, &headers);
330        assert!(result.is_err());
331    }
332
333    #[test]
334    fn test_build_aux_vector_terminates_with_null() {
335        use alloc::vec;
336
337        let binary = ElfBinary {
338            entry_point: 0x401000,
339            load_base: 0x400000,
340            load_size: 0x2000,
341            segments: vec![],
342            interpreter: None,
343            dynamic: false,
344        };
345
346        let aux = build_aux_vector(&binary, 0x400040, 0, 0, 0);
347
348        // Must end with AT_NULL.
349        let last = aux.last().expect("aux vector should not be empty");
350        assert_eq!(last.type_id, AuxType::AtNull);
351        assert_eq!(last.value, 0);
352    }
353
354    #[test]
355    fn test_build_aux_vector_contains_essential_entries() {
356        use alloc::vec;
357
358        let binary = ElfBinary {
359            entry_point: 0x401000,
360            load_base: 0x400000,
361            load_size: 0x2000,
362            segments: vec![],
363            interpreter: Some(String::from("/lib/ld.so.1")),
364            dynamic: true,
365        };
366
367        let aux = build_aux_vector(&binary, 0x400040, 0x7F000000, 0xBEEF, 0xCAFE);
368
369        let has = |t: AuxType| aux.iter().any(|e| e.type_id == t);
370        assert!(has(AuxType::AtPhdr));
371        assert!(has(AuxType::AtPhent));
372        assert!(has(AuxType::AtPhnum));
373        assert!(has(AuxType::AtPagesz));
374        assert!(has(AuxType::AtEntry));
375        assert!(has(AuxType::AtBase));
376        assert!(has(AuxType::AtRandom));
377        assert!(has(AuxType::AtExecfn));
378        assert!(has(AuxType::AtNull));
379    }
380}