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 smallstr::SmallString;
15use twizzler_rt_abi::{
16    core::{CtorSet, RuntimeInfo},
17    debug::LoadedImageId,
18};
19
20use crate::{
21    compartment::CompartmentId, engines::Backing, symbol::RelocatedSymbol, tls::TlsModId,
22    DynlinkError, DynlinkErrorKind,
23};
24
25#[derive(PartialEq, PartialOrd, Ord, Eq, Debug, Clone, Copy)]
26pub(crate) enum RelocState {
27    /// Relocation has not started.
28    Unrelocated,
29    /// Relocation has started, but not finished, or failed.
30    PartialRelocation,
31    /// Relocation completed successfully.
32    Relocated,
33}
34
35#[derive(PartialEq, PartialOrd, Ord, Eq, Debug, Clone, Copy)]
36pub enum AllowedGates {
37    /// Gates are not exported
38    Private,
39    /// Gates are exported to other compartments only
40    Public,
41    /// Gates are exported to all compartments
42    PublicInclSelf,
43}
44
45#[repr(C)]
46/// An unloaded library. It's just a name, really.
47#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Hash, Default)]
48pub struct UnloadedLibrary {
49    pub name: String,
50}
51
52impl UnloadedLibrary {
53    /// Construct a new unloaded library.
54    pub fn new(name: impl AsRef<str>) -> Self {
55        Self {
56            name: name.as_ref().to_string(),
57        }
58    }
59}
60
61/// The ID struct for a library.
62#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash, Default)]
63#[repr(transparent)]
64pub struct LibraryId(pub(crate) NodeIndex);
65
66impl From<LoadedImageId> for LibraryId {
67    fn from(value: LoadedImageId) -> Self {
68        LibraryId(NodeIndex::new(value as usize))
69    }
70}
71
72impl Into<LoadedImageId> for LibraryId {
73    fn into(self) -> LoadedImageId {
74        self.0.index() as u32
75    }
76}
77
78impl Display for LibraryId {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        write!(f, "{}", &self.0.index())
81    }
82}
83
84#[repr(C)]
85/// A loaded library. It may be in various relocation states.
86pub struct Library {
87    /// Name of this library.
88    pub name: String,
89    /// Node index for the dependency graph.
90    pub(crate) idx: NodeIndex,
91    /// Compartment ID this library is loaded in.
92    pub(crate) comp_id: CompartmentId,
93    /// Just for debug and logging purposes.
94    comp_name: String,
95    /// Object containing the full ELF data.
96    pub full_obj: Backing,
97    /// State of relocation.
98    pub(crate) reloc_state: RelocState,
99    allowed_gates: AllowedGates,
100
101    pub backings: Vec<Backing>,
102
103    /// The module ID for the TLS region, if any.
104    pub tls_id: Option<TlsModId>,
105
106    /// Information about constructors.
107    pub(crate) ctors: CtorSet,
108    pub(crate) secgate_info: SecgateInfo,
109}
110
111#[allow(dead_code)]
112impl Library {
113    pub(crate) fn new(
114        name: String,
115        idx: NodeIndex,
116        comp_id: CompartmentId,
117        comp_name: String,
118        full_obj: Backing,
119        backings: Vec<Backing>,
120        tls_id: Option<TlsModId>,
121        ctors: CtorSet,
122        secgate_info: SecgateInfo,
123        allowed_gates: AllowedGates,
124    ) -> Self {
125        Self {
126            name,
127            idx,
128            full_obj,
129            backings,
130            tls_id,
131            ctors,
132            reloc_state: RelocState::Unrelocated,
133            comp_id,
134            comp_name,
135            secgate_info,
136            allowed_gates,
137        }
138    }
139
140    pub fn allows_gates(&self) -> bool {
141        self.allowed_gates != AllowedGates::Private
142    }
143
144    pub fn allows_self_gates(&self) -> bool {
145        self.allowed_gates == AllowedGates::PublicInclSelf
146    }
147
148    pub fn dynamic_ptr(&self) -> Option<*mut Dyn> {
149        let phdr = self
150            .get_elf()
151            .ok()?
152            .segments()?
153            .iter()
154            .find(|s| s.p_type == PT_DYNAMIC)?;
155        Some(self.laddr_mut(phdr.p_vaddr))
156    }
157
158    pub fn is_binary(&self) -> bool {
159        let Some(dynamic) = self
160            .get_elf()
161            .ok()
162            .and_then(|elf| elf.dynamic().ok())
163            .flatten()
164        else {
165            return false;
166        };
167        let Some(flags) = dynamic.iter().find_map(|ent| {
168            if ent.d_tag == DT_FLAGS_1 {
169                Some(ent.d_val())
170            } else {
171                None
172            }
173        }) else {
174            return false;
175        };
176        flags & elf::abi::DF_1_PIE as u64 != 0
177    }
178
179    /// Get the ID for this library
180    pub fn id(&self) -> LibraryId {
181        LibraryId(self.idx)
182    }
183
184    /// Get the compartment ID for this library.
185    pub fn compartment(&self) -> CompartmentId {
186        self.comp_id
187    }
188
189    /// Get a raw pointer to the program headers for this library.
190    pub fn get_phdrs_raw(&self) -> Option<(*const Elf64_Phdr, usize)> {
191        Some((
192            self.get_elf().ok()?.segments()?.iter().find_map(|p| {
193                if p.p_type == PT_PHDR {
194                    Some(self.laddr(p.p_vaddr))
195                } else {
196                    None
197                }
198            })?,
199            self.get_elf().ok()?.segments()?.len(),
200        ))
201    }
202
203    /// Return a handle to the full ELF file.
204    pub fn get_elf(&self) -> Result<elf::ElfBytes<'_, NativeEndian>, ParseError> {
205        elf::ElfBytes::minimal_parse(self.full_obj.slice())
206    }
207
208    /// Get the load address for this library.
209    pub fn base_addr(&self) -> usize {
210        self.backings[0].load_addr()
211    }
212
213    /// Compute an in-memory address for a ELF virtual addr.
214    pub fn laddr<T>(&self, val: u64) -> *const T {
215        (self.base_addr() + val as usize) as *const T
216    }
217
218    /// Compute an in-memory address (mut) for a ELF virtual addr.
219    pub fn laddr_mut<T>(&self, val: u64) -> *mut T {
220        (self.base_addr() + val as usize) as *mut T
221    }
222
223    /// Get a function pointer to this library's entry address, if one exists.
224    pub fn get_entry_address(
225        &self,
226    ) -> Result<extern "C" fn(*const RuntimeInfo) -> !, DynlinkError> {
227        let entry = self.get_elf()?.ehdr.e_entry;
228        if entry == 0 {
229            return Err(DynlinkErrorKind::NoEntryAddress {
230                name: self.name.as_str().into(),
231            }
232            .into());
233        }
234        let entry: *const u8 = self.laddr(entry);
235        let ptr: extern "C" fn(*const RuntimeInfo) -> ! =
236            unsafe { core::mem::transmute(entry as usize) };
237        Ok(ptr)
238    }
239
240    // Helper to find the TLS program header.
241    fn get_tls_phdr(&self) -> Result<Option<ProgramHeader>, DynlinkError> {
242        Ok(self
243            .get_elf()?
244            .segments()
245            .and_then(|phdrs| phdrs.iter().find(|phdr| phdr.p_type == PT_TLS)))
246    }
247
248    pub(crate) fn get_tls_data(&self) -> Result<Option<&[u8]>, DynlinkError> {
249        let phdr = self.get_tls_phdr()?;
250        Ok(phdr.map(|phdr| {
251            let slice = unsafe {
252                core::slice::from_raw_parts(self.laddr(phdr.p_vaddr), phdr.p_memsz as usize)
253            };
254            slice
255        }))
256    }
257
258    fn do_lookup_symbol(
259        &self,
260        name: &str,
261        allow_weak: bool,
262    ) -> Result<RelocatedSymbol<'_>, DynlinkError> {
263        let elf = self.get_elf()?;
264        let common = elf.find_common_data()?;
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".into(),
291                        })?,
292                    common.dynsyms_strs.as_ref().ok_or_else(|| {
293                        DynlinkErrorKind::MissingSection {
294                            name: "dynsyms_strs".into(),
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".into(),
327                        })?,
328                    common.dynsyms_strs.as_ref().ok_or_else(|| {
329                        DynlinkErrorKind::MissingSection {
330                            name: "dynsyms_strs".into(),
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 { name: name.into() }.into())
380    }
381
382    pub(crate) fn lookup_symbol(
383        &self,
384        name: &str,
385        allow_weak: bool,
386        allow_prefix: bool,
387    ) -> Result<RelocatedSymbol<'_>, DynlinkError> {
388        let ret = self.do_lookup_symbol(&name, allow_weak);
389        if allow_prefix && ret.is_err() && !name.starts_with("__TWIZZLER_SECURE_GATE_") {
390            let mut prefixedname = SmallString::<[u8; 256]>::from_str("__TWIZZLER_SECURE_GATE_");
391            prefixedname.push_str(name);
392
393            if let Ok(o) = self.do_lookup_symbol(&prefixedname, allow_weak) {
394                return Ok(o);
395            }
396        }
397        ret
398    }
399
400    pub(crate) fn is_local_or_secgate_from(&self, other: &Library, name: &str) -> bool {
401        self.in_same_compartment_as(other) || (self.is_relocated() && self.is_secgate(name))
402    }
403
404    pub(crate) fn in_same_compartment_as(&self, other: &Library) -> bool {
405        other.comp_id == self.comp_id
406    }
407
408    pub fn is_relocated(&self) -> bool {
409        self.reloc_state == RelocState::Relocated
410    }
411
412    fn is_secgate(&self, name: &str) -> bool {
413        self.iter_secgates()
414            .map(|gates| {
415                gates
416                    .iter()
417                    .any(|gate| gate.name().to_bytes() == name.as_bytes())
418            })
419            .unwrap_or(false)
420    }
421
422    pub fn iter_secgates(&self) -> Option<&[RawSecGateInfo]> {
423        let addr = self.secgate_info.info_addr?;
424
425        Some(unsafe { core::slice::from_raw_parts(addr as *const _, self.secgate_info.num) })
426    }
427}
428
429impl Debug for Library {
430    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
431        f.debug_struct("Library")
432            .field("name", &self.name)
433            .field("comp_name", &self.comp_name)
434            .field("idx", &self.idx)
435            .field("tls_id", &self.tls_id)
436            .finish()
437    }
438}
439
440impl Drop for Library {
441    fn drop(&mut self) {
442        //tracing::warn!("dynlink: drop library: {:?}", self);
443    }
444}
445
446impl core::fmt::Display for Library {
447    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
448        write!(f, "{}::{}", &self.comp_name, &self.name)
449    }
450}
451
452impl core::fmt::Display for UnloadedLibrary {
453    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
454        write!(f, "{}(unloaded)", &self.name)
455    }
456}
457
458#[derive(Debug, Clone, Default)]
459pub struct SecgateInfo {
460    pub info_addr: Option<usize>,
461    pub num: usize,
462}