dynlink/
library.rs

1//! Management of individual libraries.
2
3use std::fmt::{Debug, Display};
4
5use elf::{
6    abi::{DT_FLAGS_1, PT_DYNAMIC, PT_PHDR, PT_TLS, STB_WEAK},
7    dynamic::Dyn,
8    endian::NativeEndian,
9    segment::{Elf64_Phdr, ProgramHeader},
10    ParseError,
11};
12use petgraph::stable_graph::NodeIndex;
13use secgate::RawSecGateInfo;
14use twizzler_rt_abi::{
15    core::{CtorSet, RuntimeInfo},
16    debug::LoadedImageId,
17};
18
19use crate::{
20    compartment::CompartmentId, engines::Backing, symbol::RelocatedSymbol, tls::TlsModId,
21    DynlinkError, DynlinkErrorKind,
22};
23
24#[derive(PartialEq, PartialOrd, Ord, Eq, Debug, Clone, Copy)]
25pub(crate) enum RelocState {
26    /// Relocation has not started.
27    Unrelocated,
28    /// Relocation has started, but not finished, or failed.
29    PartialRelocation,
30    /// Relocation completed successfully.
31    Relocated,
32}
33
34#[derive(PartialEq, PartialOrd, Ord, Eq, Debug, Clone, Copy)]
35pub enum AllowedGates {
36    /// Gates are not exported
37    Private,
38    /// Gates are exported to other compartments only
39    Public,
40    /// Gates are exported to all compartments
41    PublicInclSelf,
42}
43
44#[repr(C)]
45/// An unloaded library. It's just a name, really.
46#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
47pub struct UnloadedLibrary {
48    pub name: String,
49}
50
51impl UnloadedLibrary {
52    /// Construct a new unloaded library.
53    pub fn new(name: impl ToString) -> Self {
54        Self {
55            name: name.to_string(),
56        }
57    }
58}
59
60/// The ID struct for a library.
61#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash)]
62#[repr(transparent)]
63pub struct LibraryId(pub(crate) NodeIndex);
64
65impl From<LoadedImageId> for LibraryId {
66    fn from(value: LoadedImageId) -> Self {
67        LibraryId(NodeIndex::new(value as usize))
68    }
69}
70
71impl Into<LoadedImageId> for LibraryId {
72    fn into(self) -> LoadedImageId {
73        self.0.index() as u32
74    }
75}
76
77impl Display for LibraryId {
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        write!(f, "{}", &self.0.index())
80    }
81}
82
83#[repr(C)]
84/// A loaded library. It may be in various relocation states.
85pub struct Library {
86    /// Name of this library.
87    pub name: String,
88    /// Node index for the dependency graph.
89    pub(crate) idx: NodeIndex,
90    /// Compartment ID this library is loaded in.
91    pub(crate) comp_id: CompartmentId,
92    /// Just for debug and logging purposes.
93    comp_name: String,
94    /// Object containing the full ELF data.
95    pub full_obj: Backing,
96    /// State of relocation.
97    pub(crate) reloc_state: RelocState,
98    allowed_gates: AllowedGates,
99
100    pub backings: Vec<Backing>,
101
102    /// The module ID for the TLS region, if any.
103    pub tls_id: Option<TlsModId>,
104
105    /// Information about constructors.
106    pub(crate) ctors: CtorSet,
107    pub(crate) secgate_info: SecgateInfo,
108}
109
110#[allow(dead_code)]
111impl Library {
112    pub(crate) fn new(
113        name: String,
114        idx: NodeIndex,
115        comp_id: CompartmentId,
116        comp_name: String,
117        full_obj: Backing,
118        backings: Vec<Backing>,
119        tls_id: Option<TlsModId>,
120        ctors: CtorSet,
121        secgate_info: SecgateInfo,
122        allowed_gates: AllowedGates,
123    ) -> Self {
124        Self {
125            name,
126            idx,
127            full_obj,
128            backings,
129            tls_id,
130            ctors,
131            reloc_state: RelocState::Unrelocated,
132            comp_id,
133            comp_name,
134            secgate_info,
135            allowed_gates,
136        }
137    }
138
139    pub fn allows_gates(&self) -> bool {
140        self.allowed_gates != AllowedGates::Private
141    }
142
143    pub fn allows_self_gates(&self) -> bool {
144        self.allowed_gates == AllowedGates::PublicInclSelf
145    }
146
147    pub fn dynamic_ptr(&self) -> Option<*mut Dyn> {
148        let phdr = self
149            .get_elf()
150            .ok()?
151            .segments()?
152            .iter()
153            .find(|s| s.p_type == PT_DYNAMIC)?;
154        Some(self.laddr_mut(phdr.p_vaddr))
155    }
156
157    pub fn is_binary(&self) -> bool {
158        let Some(dynamic) = self
159            .get_elf()
160            .ok()
161            .and_then(|elf| elf.dynamic().ok())
162            .flatten()
163        else {
164            return false;
165        };
166        let Some(flags) = dynamic.iter().find_map(|ent| {
167            if ent.d_tag == DT_FLAGS_1 {
168                Some(ent.d_val())
169            } else {
170                None
171            }
172        }) else {
173            return false;
174        };
175        flags & elf::abi::DF_1_PIE as u64 != 0
176    }
177
178    /// Get the ID for this library
179    pub fn id(&self) -> LibraryId {
180        LibraryId(self.idx)
181    }
182
183    /// Get the compartment ID for this library.
184    pub fn compartment(&self) -> CompartmentId {
185        self.comp_id
186    }
187
188    /// Get a raw pointer to the program headers for this library.
189    pub fn get_phdrs_raw(&self) -> Option<(*const Elf64_Phdr, usize)> {
190        Some((
191            self.get_elf().ok()?.segments()?.iter().find_map(|p| {
192                if p.p_type == PT_PHDR {
193                    Some(self.laddr(p.p_vaddr))
194                } else {
195                    None
196                }
197            })?,
198            self.get_elf().ok()?.segments()?.len(),
199        ))
200    }
201
202    /// Return a handle to the full ELF file.
203    pub fn get_elf(&self) -> Result<elf::ElfBytes<'_, NativeEndian>, ParseError> {
204        elf::ElfBytes::minimal_parse(self.full_obj.slice())
205    }
206
207    /// Get the load address for this library.
208    pub fn base_addr(&self) -> usize {
209        self.backings[0].load_addr()
210    }
211
212    /// Compute an in-memory address for a ELF virtual addr.
213    pub fn laddr<T>(&self, val: u64) -> *const T {
214        (self.base_addr() + val as usize) as *const T
215    }
216
217    /// Compute an in-memory address (mut) for a ELF virtual addr.
218    pub fn laddr_mut<T>(&self, val: u64) -> *mut T {
219        (self.base_addr() + val as usize) as *mut T
220    }
221
222    /// Get a function pointer to this library's entry address, if one exists.
223    pub fn get_entry_address(
224        &self,
225    ) -> Result<extern "C" fn(*const RuntimeInfo) -> !, DynlinkError> {
226        let entry = self.get_elf()?.ehdr.e_entry;
227        if entry == 0 {
228            return Err(DynlinkErrorKind::NoEntryAddress {
229                name: self.name.clone(),
230            }
231            .into());
232        }
233        let entry: *const u8 = self.laddr(entry);
234        let ptr: extern "C" fn(*const RuntimeInfo) -> ! =
235            unsafe { core::mem::transmute(entry as usize) };
236        Ok(ptr)
237    }
238
239    // Helper to find the TLS program header.
240    fn get_tls_phdr(&self) -> Result<Option<ProgramHeader>, DynlinkError> {
241        Ok(self
242            .get_elf()?
243            .segments()
244            .and_then(|phdrs| phdrs.iter().find(|phdr| phdr.p_type == PT_TLS)))
245    }
246
247    pub(crate) fn get_tls_data(&self) -> Result<Option<&[u8]>, DynlinkError> {
248        let phdr = self.get_tls_phdr()?;
249        Ok(phdr.map(|phdr| {
250            let slice = unsafe {
251                core::slice::from_raw_parts(self.laddr(phdr.p_vaddr), phdr.p_memsz as usize)
252            };
253            slice
254        }))
255    }
256
257    fn do_lookup_symbol(
258        &self,
259        name: &str,
260        allow_weak: bool,
261    ) -> Result<RelocatedSymbol<'_>, DynlinkError> {
262        let elf = self.get_elf()?;
263        let common = elf.find_common_data()?;
264        tracing::debug!("lookup {} in {}", name, self.name);
265
266        /*
267        if self.is_relocated() {
268            if let Some(gates) = self.iter_secgates() {
269                for sc in gates {
270                    if let Ok(gname) = sc.name().to_str() {
271                        if gname == name {
272                            tracing::info!("found as secure gate");
273                            return Ok(RelocatedSymbol::new_sc(sc.imp, self));
274                        }
275                    }
276                }
277            }
278        }
279        */
280
281        // Try the GNU hash table, if present.
282        if let Some(h) = &common.gnu_hash {
283            if let Some((_, sym)) = h
284                .find(
285                    name.as_ref(),
286                    common
287                        .dynsyms
288                        .as_ref()
289                        .ok_or_else(|| DynlinkErrorKind::MissingSection {
290                            name: "dynsyms".to_string(),
291                        })?,
292                    common.dynsyms_strs.as_ref().ok_or_else(|| {
293                        DynlinkErrorKind::MissingSection {
294                            name: "dynsyms_strs".to_string(),
295                        }
296                    })?,
297                )
298                .ok()
299                .flatten()
300            {
301                if !sym.is_undefined() {
302                    // TODO: proper weak symbol handling.
303                    if sym.st_bind() != STB_WEAK
304                        || allow_weak
305                        || (self.is_relocated() && self.is_secgate(name))
306                    {
307                        return Ok(RelocatedSymbol::new(sym, self));
308                    } else {
309                        tracing::warn!("lookup symbol {} skipping weak binding in {}", name, self);
310                    }
311                } else {
312                    //tracing::warn!("undefined symbol: {}", name);
313                }
314            }
315        }
316
317        // Try the sysv hash table, if present.
318        if let Some(h) = &common.sysv_hash {
319            if let Some((_, sym)) = h
320                .find(
321                    name.as_ref(),
322                    common
323                        .dynsyms
324                        .as_ref()
325                        .ok_or_else(|| DynlinkErrorKind::MissingSection {
326                            name: "dynsyms".to_string(),
327                        })?,
328                    common.dynsyms_strs.as_ref().ok_or_else(|| {
329                        DynlinkErrorKind::MissingSection {
330                            name: "dynsyms_strs".to_string(),
331                        }
332                    })?,
333                )
334                .ok()
335                .flatten()
336            {
337                if !sym.is_undefined() {
338                    // TODO: proper weak symbol handling.
339                    if sym.st_bind() != STB_WEAK
340                        || allow_weak
341                        || (self.is_relocated() && self.is_secgate(name))
342                    {
343                        return Ok(RelocatedSymbol::new(sym, self));
344                    } else {
345                        tracing::warn!("lookup symbol {} skipping weak binding in {}", name, self);
346                    }
347                } else {
348                    //tracing::warn!("undefined symbol: {}", name);
349                }
350            }
351        }
352
353        if !self.allows_gates()
354            && !self.allows_self_gates()
355            && self.is_binary()
356            && !name.starts_with("__TWIZZLER_SECURE_GATE")
357        {
358            let dstrs = common.dynsyms_strs.as_ref().unwrap();
359            for sym in common.dynsyms.as_ref().unwrap().iter() {
360                let sym_name = dstrs.get(sym.st_name as usize)?;
361                if name == sym_name {
362                    if sym.st_bind() == STB_WEAK && allow_weak && !self.is_secgate(name) {
363                        /*
364                        tracing::warn!(
365                            "!! lookup symbol {} skipping weak binding in {}",
366                            name,
367                            self
368                        );
369                        */
370                        return Ok(RelocatedSymbol::new_zero(self));
371                    } else {
372                        //tracing::warn!("lookup symbol {} skipping weak binding in {}", name,
373                        // self);
374                    }
375                }
376            }
377        }
378        //tracing::warn!("undefined symbol: {}", name);
379        Err(DynlinkErrorKind::NameNotFound {
380            name: name.to_string(),
381        }
382        .into())
383    }
384
385    pub(crate) fn lookup_symbol(
386        &self,
387        name: &str,
388        allow_weak: bool,
389        allow_prefix: bool,
390    ) -> Result<RelocatedSymbol<'_>, DynlinkError> {
391        let ret = self.do_lookup_symbol(&name, allow_weak);
392        if allow_prefix && ret.is_err() && !name.starts_with("__TWIZZLER_SECURE_GATE_") {
393            let name = format!("__TWIZZLER_SECURE_GATE_{}", name);
394            if let Ok(o) = self.do_lookup_symbol(&name, allow_weak) {
395                return Ok(o);
396            }
397        }
398        ret
399    }
400
401    pub(crate) fn is_local_or_secgate_from(&self, other: &Library, name: &str) -> bool {
402        self.in_same_compartment_as(other) || (self.is_relocated() && self.is_secgate(name))
403    }
404
405    pub(crate) fn in_same_compartment_as(&self, other: &Library) -> bool {
406        other.comp_id == self.comp_id
407    }
408
409    pub fn is_relocated(&self) -> bool {
410        self.reloc_state == RelocState::Relocated
411    }
412
413    fn is_secgate(&self, name: &str) -> bool {
414        self.iter_secgates()
415            .map(|gates| {
416                gates
417                    .iter()
418                    .any(|gate| gate.name().to_bytes() == name.as_bytes())
419            })
420            .unwrap_or(false)
421    }
422
423    pub fn iter_secgates(&self) -> Option<&[RawSecGateInfo]> {
424        let addr = self.secgate_info.info_addr?;
425
426        Some(unsafe { core::slice::from_raw_parts(addr as *const _, self.secgate_info.num) })
427    }
428}
429
430impl Debug for Library {
431    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
432        f.debug_struct("Library")
433            .field("name", &self.name)
434            .field("comp_name", &self.comp_name)
435            .field("idx", &self.idx)
436            .field("tls_id", &self.tls_id)
437            .finish()
438    }
439}
440
441impl Drop for Library {
442    fn drop(&mut self) {
443        //tracing::warn!("dynlink: drop library: {:?}", self);
444    }
445}
446
447impl core::fmt::Display for Library {
448    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
449        write!(f, "{}::{}", &self.comp_name, &self.name)
450    }
451}
452
453impl core::fmt::Display for UnloadedLibrary {
454    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
455        write!(f, "{}(unloaded)", &self.name)
456    }
457}
458
459#[derive(Debug, Clone, Default)]
460pub struct SecgateInfo {
461    pub info_addr: Option<usize>,
462    pub num: usize,
463}