elf_loader 0.17.0

A no_std-friendly ELF loader and runtime linker for Rust.
Documentation
#[cfg(any(
    feature = "use-syscall",
    all(any(target_os = "linux", target_os = "android"), feature = "libc")
))]
use elf_loader::{Error, error::ParseEhdrError};
use elf_loader::{
    Loader,
    elf::{ElfFileType, ElfProgramType},
    image::ScannedElf,
    input::ElfBinary,
};
use std::sync::OnceLock;

const LOADER: Loader = Loader::new();
const E_TYPE_OFFSET: usize = 0x10;
#[cfg(target_pointer_width = "64")]
const E_PHOFF_OFFSET: usize = 0x20;
#[cfg(not(target_pointer_width = "64"))]
const E_PHOFF_OFFSET: usize = 0x1c;
#[cfg(target_pointer_width = "64")]
const E_PHENTSIZE_OFFSET: usize = 0x36;
#[cfg(not(target_pointer_width = "64"))]
const E_PHENTSIZE_OFFSET: usize = 0x2a;
#[cfg(target_pointer_width = "64")]
const E_PHNUM_OFFSET: usize = 0x38;
#[cfg(not(target_pointer_width = "64"))]
const E_PHNUM_OFFSET: usize = 0x2c;

fn mark_as_static_exec(mut bytes: Vec<u8>) -> Vec<u8> {
    bytes[E_TYPE_OFFSET..E_TYPE_OFFSET + 2].copy_from_slice(&ElfFileType::EXEC.raw().to_le_bytes());
    #[cfg(target_pointer_width = "64")]
    let phoff = u64::from_le_bytes(
        bytes[E_PHOFF_OFFSET..E_PHOFF_OFFSET + 8]
            .try_into()
            .unwrap(),
    ) as usize;
    #[cfg(not(target_pointer_width = "64"))]
    let phoff = u32::from_le_bytes(
        bytes[E_PHOFF_OFFSET..E_PHOFF_OFFSET + 4]
            .try_into()
            .unwrap(),
    ) as usize;
    let phentsize = u16::from_le_bytes(
        bytes[E_PHENTSIZE_OFFSET..E_PHENTSIZE_OFFSET + 2]
            .try_into()
            .unwrap(),
    ) as usize;
    let phnum = u16::from_le_bytes(
        bytes[E_PHNUM_OFFSET..E_PHNUM_OFFSET + 2]
            .try_into()
            .unwrap(),
    ) as usize;

    for index in 0..phnum {
        let offset = phoff + index * phentsize;
        let p_type = u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap());
        if p_type == ElfProgramType::DYNAMIC.raw() {
            bytes[offset..offset + 4].copy_from_slice(&ElfProgramType::NULL.raw().to_le_bytes());
            return bytes;
        }
    }

    panic!("compiled test image should contain PT_DYNAMIC");
}

fn static_exec() -> &'static [u8] {
    static BYTES: OnceLock<Vec<u8>> = OnceLock::new();
    BYTES.get_or_init(|| mark_as_static_exec(crate::fixture::fixtures().exec.clone()))
}

#[test]
fn loads_dylib() {
    let loaded = LOADER
        .load_dylib(ElfBinary::new(
            "provider.so",
            &crate::fixture::fixtures().dylib,
        ))
        .expect("load_dylib should accept ET_DYN");

    assert_eq!(loaded.path().as_str(), "provider.so");
    assert!(
        loaded
            .phdrs()
            .iter()
            .any(|phdr| phdr.program_type() == ElfProgramType::DYNAMIC)
    );
}

#[test]
#[cfg(any(
    feature = "use-syscall",
    all(any(target_os = "linux", target_os = "android"), feature = "libc")
))]
fn loads_exec() {
    let bytes = crate::fixture::fixtures().exec.as_slice();

    let error = LOADER
        .load_dylib(ElfBinary::new("dynamic_exec", bytes))
        .expect_err("load_dylib should reject ET_EXEC");
    assert!(matches!(
        error,
        Error::ParseEhdr(ParseEhdrError::ExpectedDylib { found })
            if found == ElfFileType::EXEC
    ));

    let loaded = LOADER
        .load_dynamic(ElfBinary::new("dynamic_exec", bytes))
        .expect("load_dynamic should accept dynamic ET_EXEC");
    assert!(
        loaded
            .phdrs()
            .iter()
            .any(|phdr| phdr.program_type() == ElfProgramType::DYNAMIC)
    );
}

#[test]
#[cfg(any(
    feature = "use-syscall",
    all(any(target_os = "linux", target_os = "android"), feature = "libc")
))]
fn loads_scanned_exec() {
    let ScannedElf::Dynamic(scanned) = LOADER
        .scan(ElfBinary::new(
            "scanned_dynamic_exec",
            &crate::fixture::fixtures().exec,
        ))
        .expect("scan should accept dynamic ET_EXEC")
    else {
        panic!("dynamic ET_EXEC should scan as dynamic");
    };
    assert_eq!(scanned.ehdr().file_type(), ElfFileType::EXEC);
    let phdr_count = scanned.phdrs().len();

    let loaded = LOADER
        .load_scanned_dynamic(scanned)
        .expect("load_scanned_dynamic should accept dynamic ET_EXEC");
    assert_eq!(loaded.phdrs().len(), phdr_count);
    assert!(
        loaded
            .phdrs()
            .iter()
            .any(|phdr| phdr.program_type() == ElfProgramType::DYNAMIC)
    );
}

#[test]
fn classifies_images() {
    let ScannedElf::Dynamic(dynamic) = LOADER
        .scan(ElfBinary::new(
            "scanned.so",
            &crate::fixture::fixtures().dylib,
        ))
        .expect("scan should accept dynamic image")
    else {
        panic!("PT_DYNAMIC image should scan as dynamic");
    };
    assert_eq!(dynamic.ehdr().file_type(), ElfFileType::DYN);
    assert!(
        dynamic
            .phdrs()
            .iter()
            .any(|phdr| phdr.program_type() == ElfProgramType::DYNAMIC)
    );

    let ScannedElf::StaticExec(exec) = LOADER
        .scan(ElfBinary::new("static_exec", static_exec()))
        .expect("scan should accept static executable metadata")
    else {
        panic!("executable without PT_DYNAMIC should scan as static exec");
    };
    assert_eq!(exec.ehdr().file_type(), ElfFileType::EXEC);
    assert!(
        exec.phdrs()
            .iter()
            .all(|phdr| phdr.program_type() != ElfProgramType::DYNAMIC)
    );
}