dynlink/
library.rs

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