blob: 19ecb3d92d20948b5ea313a7a0111fd3c7fbd4cb [file]
// We use the `object` crate for the read-only pass over ELF/Mach-O object files
// because its `Sym`/`Nlist` traits provide clean access to symbol properties without
// manual byte parsing. However, `object` does not expose mutable views into the data,
// so we cannot use it to modify symbol fields in place. Instead, the read-only pass
// collects byte-level patches (offset + new value), and the write pass
// (`apply_patches`) applies them to a copy of the byte buffer without any ELF/Mach-O
// parsing — similar to how linker relocations work.
use std::mem;
use object::read::elf::Sym as _;
use object::read::macho::Nlist;
use object::{Endianness, elf, macho};
use rustc_data_structures::fx::FxHashSet;
/// A byte-level patch collected in the read-only pass and applied in the write pass.
struct Patch {
offset: usize,
value: u8,
}
/// Apply a list of byte patches to `data`, returning the (possibly modified) bytes.
fn apply_patches(data: &[u8], patches: &[Patch]) -> Vec<u8> {
let mut buf = data.to_vec();
for p in patches {
buf[p.offset] = p.value;
}
buf
}
// ---------------------------------------------------------------------------
// ELF hide – read-only pass uses `object` crate, write pass uses `Patch` list
// ---------------------------------------------------------------------------
fn elf_hide_patches_impl<'data, Elf: object::read::elf::FileHeader<Endian = Endianness>>(
data: &'data [u8],
st_other_offset: usize,
exported: &FxHashSet<String>,
) -> Option<Vec<Patch>>
where
u64: From<Elf::Word>,
{
let header = Elf::parse(data).ok()?;
let endian = header.endian().ok()?;
let sections = header.sections(endian, data).ok()?;
let symtab = sections.symbols(endian, data, elf::SHT_SYMTAB).ok()?;
let data_ptr = data.as_ptr() as usize;
let strings = symtab.strings();
let mut patches = Vec::new();
for sym in symtab.iter() {
let binding = sym.st_bind();
if binding != elf::STB_GLOBAL && binding != elf::STB_WEAK {
continue;
}
if sym.is_undefined(endian) {
continue;
}
let Ok(name_bytes) = sym.name(endian, strings) else { continue };
let Ok(name) = str::from_utf8(name_bytes) else { continue };
if !exported.contains(name) {
let sym_addr = sym as *const Elf::Sym as usize;
let offset = sym_addr - data_ptr + st_other_offset;
let new_vis = (sym.st_other() & !0x03) | elf::STV_HIDDEN;
patches.push(Patch { offset, value: new_vis });
}
}
Some(patches)
}
// ---------------------------------------------------------------------------
// Mach-O hide – same architecture: read-only pass via `object`, write via patches
// ---------------------------------------------------------------------------
fn macho_hide_patches_impl<'data, Mach: object::read::macho::MachHeader<Endian = Endianness>>(
data: &'data [u8],
n_type_offset: usize,
exported: &FxHashSet<String>,
) -> Option<Vec<Patch>> {
let header = Mach::parse(data, 0).ok()?;
let endian = header.endian().ok()?;
let mut commands = header.load_commands(endian, data, 0).ok()?;
let symtab_cmd = loop {
let cmd = commands.next().ok()??;
if let Some(st) = cmd.symtab().ok().flatten() {
break st;
}
};
let symtab: object::read::macho::SymbolTable<'_, Mach, &_> =
symtab_cmd.symbols(endian, data).ok()?;
let data_ptr = data.as_ptr() as usize;
let strings = symtab.strings();
let mut patches = Vec::new();
for nlist in symtab.iter() {
if nlist.is_stab() {
continue;
}
if nlist.is_undefined() {
continue;
}
if nlist.n_type() & macho::N_EXT == 0 {
continue;
}
let Ok(name_bytes) = nlist.name(endian, strings) else { continue };
let Ok(name) = str::from_utf8(name_bytes) else { continue };
let name = name.strip_prefix('_').unwrap_or(name);
if !exported.contains(name) {
let nlist_addr = nlist as *const Mach::Nlist as usize;
let offset = nlist_addr - data_ptr + n_type_offset;
patches.push(Patch { offset, value: nlist.n_type() | macho::N_PEXT });
}
}
Some(patches)
}
// ---------------------------------------------------------------------------
// Unified dispatch: top-level detection via `object::File::parse`
// ---------------------------------------------------------------------------
fn hide_patches(data: &[u8], exported: &FxHashSet<String>) -> Option<Vec<Patch>> {
let file = object::File::parse(data).ok()?;
match file {
object::File::Elf64(_) => elf_hide_patches_impl::<elf::FileHeader64<Endianness>>(
data,
mem::offset_of!(elf::Sym64<Endianness>, st_other),
exported,
),
object::File::Elf32(_) => elf_hide_patches_impl::<elf::FileHeader32<Endianness>>(
data,
mem::offset_of!(elf::Sym32<Endianness>, st_other),
exported,
),
object::File::MachO64(_) => macho_hide_patches_impl::<macho::MachHeader64<Endianness>>(
data,
mem::offset_of!(macho::Nlist64<Endianness>, n_type),
exported,
),
object::File::MachO32(_) => macho_hide_patches_impl::<macho::MachHeader32<Endianness>>(
data,
mem::offset_of!(macho::Nlist32<Endianness>, n_type),
exported,
),
_ => None,
}
}
pub(super) fn apply_hide(data: &[u8], exported: &FxHashSet<String>) -> Vec<u8> {
let patches = hide_patches(data, exported).unwrap_or_default();
apply_patches(data, &patches)
}