dynlink/context/
load.rs

1use std::mem::size_of;
2
3use elf::{
4    abi::{
5        DT_INIT, DT_INIT_ARRAY, DT_INIT_ARRAYSZ, DT_PREINIT_ARRAY, DT_PREINIT_ARRAYSZ, PT_DYNAMIC,
6        PT_TLS,
7    },
8    dynamic::DynamicTable,
9    endian::NativeEndian,
10    file::Class,
11};
12use petgraph::stable_graph::NodeIndex;
13use secgate::RawSecGateInfo;
14use tracing::{debug, warn};
15use twizzler_rt_abi::{core::CtorSet, object::MAX_SIZE};
16
17use super::{Context, LoadedOrUnloaded};
18use crate::{
19    compartment::{Compartment, CompartmentId},
20    context::NewCompartmentFlags,
21    engines::{LoadCtx, LoadDirective, LoadFlags},
22    library::{AllowedGates, Library, LibraryId, SecgateInfo, UnloadedLibrary},
23    tls::TlsModule,
24    DynlinkError, DynlinkErrorKind, HeaderError, Vec, SMALL_VEC_SIZE,
25};
26
27#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash, Default)]
28pub struct LoadIds {
29    pub comp: CompartmentId,
30    pub lib: LibraryId,
31}
32
33impl From<&Library> for LoadIds {
34    fn from(value: &Library) -> Self {
35        Self {
36            comp: value.comp_id,
37            lib: value.id(),
38        }
39    }
40}
41
42impl Context {
43    pub(crate) fn get_secgate_info(
44        &self,
45        libname: &str,
46        elf: &elf::ElfBytes<'_, NativeEndian>,
47        base_addr: usize,
48    ) -> Result<SecgateInfo, DynlinkError> {
49        let info = elf
50            .section_header_by_name(".twz_secgate_info")?
51            .map(|info| SecgateInfo {
52                info_addr: Some((info.sh_addr as usize) + base_addr),
53                num: (info.sh_size as usize) / core::mem::size_of::<RawSecGateInfo>(),
54            })
55            .unwrap_or_default();
56
57        debug!(
58            "{}: registered secure gate info: {} gates",
59            libname, info.num
60        );
61
62        Ok(info)
63    }
64
65    // Collect information about constructors.
66    pub(crate) fn get_ctor_info(
67        &self,
68        libname: &str,
69        elf: &elf::ElfBytes<'_, NativeEndian>,
70        base_addr: usize,
71    ) -> Result<CtorSet, DynlinkError> {
72        let ph = elf.segments();
73
74        let ph = ph.unwrap();
75        let mut dynamic = None;
76        for p in ph {
77            if p.p_type == PT_DYNAMIC {
78                let slice = unsafe {
79                    std::slice::from_raw_parts(
80                        (p.p_vaddr + base_addr as u64) as *const u8,
81                        p.p_memsz as usize,
82                    )
83                };
84                dynamic = Some(DynamicTable::new(NativeEndian, Class::ELF64, slice));
85            }
86        }
87        let dynamic = dynamic.ok_or_else(|| {
88            DynlinkError::new(DynlinkErrorKind::MissingSection {
89                name: "dynamic".into(),
90            })
91        })?;
92
93        // If this isn't present, just call it 0, since if there's an init_array, this entry must be
94        // present in valid ELF files.
95        let init_array_len = dynamic
96            .iter()
97            .find_map(|d| {
98                if d.d_tag == DT_INIT_ARRAYSZ {
99                    Some((d.d_val() as usize) / size_of::<usize>())
100                } else {
101                    None
102                }
103            })
104            .unwrap_or_default();
105
106        // Init array is a pointer to an array of function pointers.
107        let init_array = dynamic.iter().find_map(|d| {
108            if d.d_tag == DT_INIT_ARRAY && d.clone().d_ptr() != 0 {
109                Some(base_addr + d.d_ptr() as usize)
110            } else {
111                None
112            }
113        });
114
115        // Legacy _init call. Supported for, well, legacy.
116        let leg_init = dynamic.iter().find_map(|d| {
117            if d.d_tag == DT_INIT && d.clone().d_ptr() != 0 {
118                Some(base_addr + d.d_ptr() as usize)
119            } else {
120                None
121            }
122        });
123
124        if dynamic.iter().any(|d| d.d_tag == DT_PREINIT_ARRAY)
125            && dynamic
126                .iter()
127                .find(|d| d.d_tag == DT_PREINIT_ARRAYSZ)
128                .is_some_and(|d| d.d_val() > 0)
129        {
130            warn!("{}: PREINIT_ARRAY is unsupported", libname);
131        }
132
133        tracing::debug!(
134            "{}: ctor info: init_array: {:?} len={}, legacy: {:?}",
135            libname,
136            init_array,
137            init_array_len,
138            leg_init
139        );
140        Ok(CtorSet {
141            legacy_init: leg_init.map(|x| unsafe { std::mem::transmute(x) }),
142            init_array: init_array.unwrap_or_default() as *mut _,
143            init_array_len,
144        })
145    }
146
147    // Load (map) a single library into memory via creating two objects, one for text, and one for
148    // data.
149    fn load(
150        &mut self,
151        comp_id: CompartmentId,
152        unlib: UnloadedLibrary,
153        idx: NodeIndex,
154        allowed_gates: AllowedGates,
155        load_ctx: &mut LoadCtx,
156    ) -> Result<Library, DynlinkError> {
157        tracing::debug!(
158            "loading library {} (idx = {:?}) into comp {}",
159            unlib,
160            idx,
161            comp_id
162        );
163        let backing = self.engine.load_object(&unlib)?;
164        let elf = backing.get_elf()?;
165
166        // Step 0: sanity check the ELF header.
167
168        const EXPECTED_CLASS: Class = Class::ELF64;
169        const EXPECTED_VERSION: u32 = 1;
170        const EXPECTED_ABI: u8 = elf::abi::ELFOSABI_SYSV;
171        const EXPECTED_ABI_VERSION: u8 = 0;
172        const EXPECTED_TYPE: u16 = elf::abi::ET_DYN;
173
174        #[cfg(target_arch = "x86_64")]
175        const EXPECTED_MACHINE: u16 = elf::abi::EM_X86_64;
176
177        #[cfg(target_arch = "aarch64")]
178        const EXPECTED_MACHINE: u16 = elf::abi::EM_AARCH64;
179
180        if elf.ehdr.class != EXPECTED_CLASS {
181            return Err(DynlinkErrorKind::from(HeaderError::ClassMismatch {
182                expect: Class::ELF64,
183                got: elf.ehdr.class,
184            })
185            .into());
186        }
187
188        if elf.ehdr.version != EXPECTED_VERSION {
189            return Err(DynlinkErrorKind::from(HeaderError::VersionMismatch {
190                expect: EXPECTED_VERSION,
191                got: elf.ehdr.version,
192            })
193            .into());
194        }
195
196        if elf.ehdr.osabi != EXPECTED_ABI {
197            return Err(DynlinkErrorKind::from(HeaderError::OSABIMismatch {
198                expect: EXPECTED_ABI,
199                got: elf.ehdr.osabi,
200            })
201            .into());
202        }
203
204        if elf.ehdr.abiversion != EXPECTED_ABI_VERSION {
205            return Err(DynlinkErrorKind::from(HeaderError::ABIVersionMismatch {
206                expect: EXPECTED_ABI_VERSION,
207                got: elf.ehdr.abiversion,
208            })
209            .into());
210        }
211
212        if elf.ehdr.e_machine != EXPECTED_MACHINE {
213            return Err(DynlinkErrorKind::from(HeaderError::MachineMismatch {
214                expect: EXPECTED_MACHINE,
215                got: elf.ehdr.e_machine,
216            })
217            .into());
218        }
219
220        if elf.ehdr.e_type != EXPECTED_TYPE {
221            return Err(DynlinkErrorKind::from(HeaderError::ELFTypeMismatch {
222                expect: EXPECTED_TYPE,
223                got: elf.ehdr.e_type,
224            })
225            .into());
226        }
227
228        // Step 1: map the PT_LOAD directives to copy-from commands Twizzler can use for creating
229        // objects.
230        let directives: Vec<_, SMALL_VEC_SIZE> = elf
231            .segments()
232            .ok_or_else(|| DynlinkErrorKind::MissingSection {
233                name: "segment info".into(),
234            })?
235            .iter()
236            .filter(|p| p.p_type == elf::abi::PT_LOAD)
237            .map(|phdr| {
238                let ld = LoadDirective {
239                    load_flags: if phdr.p_flags & elf::abi::PF_W != 0
240                        || phdr.p_vaddr > MAX_SIZE as u64
241                    {
242                        LoadFlags::TARGETS_DATA
243                    } else {
244                        LoadFlags::empty()
245                    },
246                    vaddr: phdr.p_vaddr as usize,
247                    memsz: phdr.p_memsz as usize,
248                    offset: phdr.p_offset as usize,
249                    align: phdr.p_align as usize,
250                    filesz: phdr.p_filesz as usize,
251                };
252
253                debug!("{}: {:?}", unlib, ld);
254
255                ld
256            })
257            .collect();
258
259        // call the system impl to actually map things
260        let backings = self
261            .engine
262            .load_segments(&backing, &directives, comp_id, load_ctx)?;
263
264        if backings.is_empty() {
265            return Err(DynlinkErrorKind::NewBackingFail.into());
266        }
267        let base_addr = backings[0].load_addr();
268
269        debug!(
270            "{}: loaded to {:x} (data at {:x})",
271            unlib,
272            base_addr,
273            backings.get(1).map(|b| b.load_addr()).unwrap_or_default()
274        );
275
276        // Step 2: look for any TLS information, stored in program header PT_TLS.
277        let tls_phdr = elf
278            .segments()
279            .and_then(|phdrs| phdrs.iter().find(|phdr| phdr.p_type == PT_TLS));
280
281        let tls_id = tls_phdr
282            .map(|tls_phdr| {
283                let formatter = humansize::make_format(humansize::BINARY);
284                debug!(
285                    "{}: registering TLS data ({} total, {} copy)",
286                    unlib,
287                    formatter(tls_phdr.p_memsz),
288                    formatter(tls_phdr.p_filesz)
289                );
290                let tm = TlsModule::new_static(
291                    base_addr + tls_phdr.p_vaddr as usize,
292                    tls_phdr.p_filesz as usize,
293                    tls_phdr.p_memsz as usize,
294                    tls_phdr.p_align as usize,
295                );
296                let comp = &mut self.get_compartment_mut(comp_id)?;
297                Ok::<_, DynlinkError>(comp.insert(tm))
298            })
299            .transpose()?;
300
301        debug!("{}: got TLS ID {:?}", unlib, tls_id);
302
303        // Step 3: lookup constructor and secgate information for this library.
304        let ctor_info = self.get_ctor_info(&unlib.name, &elf, base_addr)?;
305        let secgate_info = self.get_secgate_info(&unlib.name, &elf, base_addr)?;
306
307        let comp = self.get_compartment(comp_id)?;
308        Ok(Library::new(
309            backing.full_name().to_owned(),
310            idx,
311            comp.id,
312            comp.name.clone(),
313            backing,
314            backings,
315            tls_id,
316            ctor_info,
317            secgate_info,
318            allowed_gates,
319        ))
320    }
321
322    fn find_cross_compartment_library(
323        &self,
324        unlib: &UnloadedLibrary,
325    ) -> Option<(NodeIndex, CompartmentId, &Compartment)> {
326        for (idx, comp) in self.compartments.iter().enumerate() {
327            if let Some(lib_id) = comp.1.library_names.get(&unlib.name) {
328                let lib = self.get_library(LibraryId(*lib_id));
329                if let Ok(lib) = lib {
330                    // Only allow cross-compartment refs for a library that has secure gates.
331                    if lib.secgate_info.info_addr.is_some() && lib.allows_gates() {
332                        return Some((*lib_id, CompartmentId(idx), comp.1));
333                    }
334                    return None;
335                }
336            }
337        }
338
339        None
340    }
341
342    fn has_secgate_info(&self, elf: &elf::ElfBytes<'_, NativeEndian>) -> bool {
343        elf.section_header_by_name(".twz_secgate_info")
344            .ok()
345            .is_some_and(|s| s.is_some())
346    }
347
348    fn select_compartment(
349        &mut self,
350        unlib: &UnloadedLibrary,
351        parent_comp_name: String,
352    ) -> Option<CompartmentId> {
353        let backing = self.engine.load_object(unlib).ok()?;
354        let elf = backing.get_elf().ok()?;
355        if self.has_secgate_info(&elf) {
356            let name = format!("{}::{}", parent_comp_name, unlib.name);
357            let id = self
358                .add_compartment(&name, NewCompartmentFlags::empty())
359                .ok()?;
360            tracing::debug!(
361                "creating new compartment {}({}) for library {}",
362                name,
363                id,
364                unlib.name
365            );
366            // TODO: Handle collisions
367            Some(id)
368        } else {
369            None
370        }
371    }
372
373    // Load a library and all its deps, using the supplied name resolution callback for deps.
374    pub(crate) fn load_library(
375        &mut self,
376        comp_id: CompartmentId,
377        root_unlib: UnloadedLibrary,
378        idx: NodeIndex,
379        allowed_gates: AllowedGates,
380        load_ctx: &mut LoadCtx,
381    ) -> Result<Vec<LoadIds, SMALL_VEC_SIZE>, DynlinkError> {
382        let root_comp_name = self.get_compartment(comp_id)?.name.clone();
383        tracing::debug!(
384            "loading library {} (idx = {:?}) in {}",
385            root_unlib,
386            idx,
387            root_comp_name
388        );
389        let mut ids = Vec::new();
390        // First load the main library.
391        let lib = self
392            .load(comp_id, root_unlib.clone(), idx, allowed_gates, load_ctx)
393            .map_err(|e| {
394                DynlinkError::new_collect(
395                    DynlinkErrorKind::LibraryLoadFail {
396                        library: root_unlib.clone(),
397                    },
398                    vec![e],
399                )
400            })?;
401        ids.push((&lib).into());
402
403        tracing::debug!("enumerating deps for {}", lib);
404        // Second, go through deps
405        let deps = self.enumerate_needed(&lib).map_err(|e| {
406            DynlinkError::new_collect(
407                DynlinkErrorKind::DepEnumerationFail {
408                    library: root_unlib.name.as_str().into(),
409                },
410                vec![e],
411            )
412        })?;
413        if !deps.is_empty() {
414            debug!("{}: loading {} dependencies", root_unlib, deps.len());
415        }
416        let deps = deps
417            .iter()
418            .map(|dep_unlib| {
419                // Dependency search + load alg:
420                // 1. Search library name in current compartment. If found, use that.
421                // 2. Fallback to searching globally for the name, by checking compartment by
422                //    compartment. If found, use that.
423                // 3. Okay, now we know we need to load the dep, so check if it can go in the
424                //    current compartment. If not, create a new compartment.
425                // 4. Finally, recurse to load it and its dependencies into either the current
426                //    compartment or the new one, if created.
427
428                let comp = self.get_compartment(comp_id)?;
429                let (existing_idx, load_comp) =
430                    if let Some(existing) = comp.library_names.get(&dep_unlib.name) {
431                        debug!(
432                            "{}: dep using existing library for {} (intra-compartment in {}): {:?}",
433                            root_unlib, dep_unlib.name, comp.name, existing
434                        );
435                        (Some(*existing), comp_id)
436                    } else if let Some((existing, other_comp_id, other_comp)) =
437                        self.find_cross_compartment_library(&dep_unlib)
438                    {
439                        debug!(
440                            "{}: dep using existing library for {} (cross-compartment to {}): {:?}",
441                            root_unlib, dep_unlib.name, other_comp.name, existing
442                        );
443                        (Some(existing), other_comp_id)
444                    } else {
445                        (
446                            None,
447                            self.select_compartment(&dep_unlib, root_comp_name.clone())
448                                .unwrap_or(comp_id),
449                        )
450                    };
451
452                // If we decided to use an existing library, then use that. Otherwise, load into the
453                // chosen compartment.
454                let idx = if let Some(existing_idx) = existing_idx {
455                    existing_idx
456                } else {
457                    let idx = self.add_library(dep_unlib.clone());
458
459                    let comp = self.get_compartment_mut(load_comp)?;
460                    comp.library_names.insert(dep_unlib.name.clone(), idx);
461                    let allowed_gates = if comp.id == comp_id {
462                        AllowedGates::Private
463                    } else {
464                        AllowedGates::Public
465                    };
466                    let recs = self
467                        .load_library(load_comp, dep_unlib.clone(), idx, allowed_gates, load_ctx)
468                        .map_err(|e| {
469                            tracing::error!("failed to load dependency for {}: {}", lib, e);
470                            DynlinkError::new_collect(
471                                DynlinkErrorKind::LibraryLoadFail {
472                                    library: dep_unlib.clone(),
473                                },
474                                vec![e],
475                            )
476                        })?;
477                    ids.extend_from_slice(recs.as_slice());
478                    idx
479                };
480                self.add_dep(lib.idx, idx);
481                Ok(idx)
482            })
483            .collect::<std::vec::Vec<Result<_, DynlinkError>>>();
484
485        let _ = DynlinkError::collect(
486            DynlinkErrorKind::LibraryLoadFail {
487                library: root_unlib,
488            },
489            deps,
490        )?;
491
492        assert_eq!(idx, lib.idx);
493        self.library_deps[idx] = LoadedOrUnloaded::Loaded(lib);
494        Ok(ids)
495    }
496
497    /// Load a library into a given compartment.
498    pub fn load_library_in_compartment(
499        &mut self,
500        comp_id: CompartmentId,
501        unlib: UnloadedLibrary,
502        allowed_gates: AllowedGates,
503        load_ctx: &mut LoadCtx,
504    ) -> Result<Vec<LoadIds, SMALL_VEC_SIZE>, DynlinkError> {
505        let idx = self.add_library(unlib.clone());
506        // Step 1: insert into the compartment's library names.
507        let comp = self.get_compartment_mut(comp_id)?;
508
509        // At this level, it's an error to insert an already loaded library.
510        if comp.library_names.contains_key(&unlib.name) {
511            return Err(DynlinkErrorKind::NameAlreadyExists {
512                name: unlib.name.as_str().into(),
513            }
514            .into());
515        }
516        comp.library_names.insert(unlib.name.clone(), idx);
517
518        // Step 2: load the library. This call recurses on dependencies.
519        self.load_library(comp_id, unlib.clone(), idx, allowed_gates, load_ctx)
520    }
521}