use std::collections::{HashMap, HashSet};
#[cfg(feature = "logging")]
use log::info;
use object::{build, elf};
use super::{Error, Result, Rewriter};
#[derive(Debug, Default)]
#[non_exhaustive]
pub struct ElfOptions {
pub add_dynamic_debug: bool,
pub delete_runpath: bool,
pub set_runpath: Option<Vec<u8>>,
pub add_runpath: Vec<Vec<u8>>,
pub use_runpath: bool,
pub use_rpath: bool,
pub delete_needed: HashSet<Vec<u8>>,
pub replace_needed: HashMap<Vec<u8>, Vec<u8>>,
pub add_needed: Vec<Vec<u8>>,
pub set_soname: Option<Vec<u8>>,
pub set_interpreter: Option<Vec<u8>>,
}
impl<'data> Rewriter<'data> {
pub fn elf_delete_symbols(&mut self, names: &HashSet<Vec<u8>>) {
for symbol in &mut self.builder.dynamic_symbols {
if names.contains(&*symbol.name) {
#[cfg(feature = "logging")]
info!("Deleting symbol {}", symbol.name);
symbol.delete = true;
self.modified = true;
}
}
}
pub fn elf_delete_dynamic_symbols(&mut self, names: &HashSet<Vec<u8>>) {
for symbol in &mut self.builder.symbols {
if names.contains(&*symbol.name) {
#[cfg(feature = "logging")]
info!("Deleting dynamic symbol {}", symbol.name);
symbol.delete = true;
self.modified = true;
}
}
}
pub fn elf_rename_symbols(&mut self, names: &HashMap<Vec<u8>, Vec<u8>>) {
for symbol in &mut self.builder.dynamic_symbols {
if let Some(name) = names.get(&*symbol.name) {
let name = name.clone().into();
#[cfg(feature = "logging")]
info!("Renaming symbol {} to {}", symbol.name, name);
symbol.name = name;
self.modified = true;
}
}
}
pub fn elf_rename_dynamic_symbols(&mut self, names: &HashMap<Vec<u8>, Vec<u8>>) {
for symbol in &mut self.builder.dynamic_symbols {
if let Some(name) = names.get(&*symbol.name) {
let name = name.clone().into();
#[cfg(feature = "logging")]
info!("Renaming dynamic symbol {} to {}", symbol.name, name);
symbol.name = name;
self.modified = true;
}
}
}
pub(crate) fn elf_delete_sections(&mut self, names: &HashSet<Vec<u8>>) {
for section in &mut self.builder.sections {
if names.contains(&*section.name) {
#[cfg(feature = "logging")]
info!("Deleting section {}", section.name);
section.delete = true;
self.modified = true;
}
}
}
pub(crate) fn elf_rename_sections(&mut self, names: &HashMap<Vec<u8>, Vec<u8>>) {
for section in &mut self.builder.sections {
if let Some(name) = names.get(&*section.name) {
let name = name.clone().into();
#[cfg(feature = "logging")]
info!("Renaming section {} to {}", section.name, name);
section.name = name;
self.modified = true;
}
}
}
pub(crate) fn elf_modify(&mut self, options: ElfOptions) -> Result<()> {
if options.add_dynamic_debug {
self.elf_add_dynamic_debug()?;
}
if options.delete_runpath {
self.elf_delete_runpath()?;
}
if let Some(path) = options.set_runpath {
self.elf_set_runpath(path)?;
}
if !options.add_runpath.is_empty() {
self.elf_add_runpath(&options.add_runpath)?;
}
if options.use_runpath {
self.elf_use_runpath()?;
}
if options.use_rpath {
self.elf_use_rpath()?;
}
if !options.delete_needed.is_empty() {
self.elf_delete_needed(&options.delete_needed)?;
}
if !options.replace_needed.is_empty() {
self.elf_replace_needed(&options.replace_needed)?;
}
if !options.add_needed.is_empty() {
self.elf_add_needed(&options.add_needed)?;
}
if let Some(name) = options.set_soname {
self.elf_set_soname(name)?;
}
if let Some(interpreter) = options.set_interpreter {
self.elf_set_interpreter(interpreter)?;
}
Ok(())
}
pub fn elf_add_dynamic_debug(&mut self) -> Result<()> {
let dynamic = self
.builder
.dynamic_data_mut()
.ok_or_else(|| Error::modify("No dynamic section found; can't add debug entry"))?;
if dynamic.iter().any(|entry| entry.tag() == elf::DT_DEBUG) {
return Ok(());
}
#[cfg(feature = "logging")]
info!("Adding DT_DEBUG entry");
dynamic.push(build::elf::Dynamic::Integer {
tag: elf::DT_DEBUG,
val: 0,
});
self.modified = true;
Ok(())
}
pub fn elf_runpath(&self) -> Option<&[u8]> {
let dynamic = self.builder.dynamic_data()?;
for entry in dynamic.iter() {
let build::elf::Dynamic::String { tag, val } = entry else {
continue;
};
if *tag != elf::DT_RPATH && *tag != elf::DT_RUNPATH {
continue;
}
return Some(val);
}
None
}
pub fn elf_delete_runpath(&mut self) -> Result<()> {
let dynamic = self
.builder
.dynamic_data_mut()
.ok_or_else(|| Error::modify("No dynamic section found; can't delete runpath"))?;
let mut modified = false;
dynamic.retain(|entry| {
let tag = entry.tag();
if tag != elf::DT_RPATH && tag != elf::DT_RUNPATH {
return true;
}
#[cfg(feature = "logging")]
info!(
"Deleting {} entry",
if tag == elf::DT_RPATH {
"DT_RPATH"
} else {
"DT_RUNPATH"
}
);
modified = true;
false
});
if modified {
self.modified = true;
}
Ok(())
}
pub fn elf_set_runpath(&mut self, runpath: Vec<u8>) -> Result<()> {
let dynamic = self
.builder
.dynamic_data_mut()
.ok_or_else(|| Error::modify("No dynamic section found; can't set runpath"))?;
let mut found = false;
for entry in dynamic.iter_mut() {
let build::elf::Dynamic::String { tag, val } = entry else {
continue;
};
if *tag != elf::DT_RPATH && *tag != elf::DT_RUNPATH {
continue;
}
*val = build::ByteString::from(runpath.clone());
#[cfg(feature = "logging")]
info!(
"Setting {} entry to {}",
if *tag == elf::DT_RPATH {
"DT_RPATH"
} else {
"DT_RUNPATH"
},
*val
);
found = true;
}
if !found {
let val = build::ByteString::from(runpath);
#[cfg(feature = "logging")]
info!("Adding DT_RUNPATH entry {}", val);
dynamic.push(build::elf::Dynamic::String {
tag: elf::DT_RUNPATH,
val,
});
}
self.modified = true;
Ok(())
}
pub fn elf_add_runpath(&mut self, runpaths: &[Vec<u8>]) -> Result<()> {
let dynamic = self
.builder
.dynamic_data_mut()
.ok_or_else(|| Error::modify("No dynamic section found; can't add runpath"))?;
let mut found = false;
for entry in dynamic.iter_mut() {
let build::elf::Dynamic::String { tag, val } = entry else {
continue;
};
if *tag != elf::DT_RPATH && *tag != elf::DT_RUNPATH {
continue;
}
for path in runpaths {
if !val.is_empty() {
val.to_mut().push(b':');
}
val.to_mut().extend_from_slice(path);
}
#[cfg(feature = "logging")]
info!(
"Changing {} entry to {}",
if *tag == elf::DT_RPATH {
"DT_RPATH"
} else {
"DT_RUNPATH"
},
val
);
found = true;
}
if !found {
let val = runpaths.join(&[b':'][..]).into();
#[cfg(feature = "logging")]
info!("Adding DT_RUNPATH entry {}", val);
dynamic.push(build::elf::Dynamic::String {
tag: elf::DT_RUNPATH,
val,
});
}
self.modified = true;
Ok(())
}
pub fn elf_use_runpath(&mut self) -> Result<()> {
let dynamic = self
.builder
.dynamic_data_mut()
.ok_or_else(|| Error::modify("No dynamic section found; can't change runpath"))?;
for entry in dynamic.iter_mut() {
let build::elf::Dynamic::String { tag, .. } = entry else {
continue;
};
if *tag != elf::DT_RPATH {
continue;
}
#[cfg(feature = "logging")]
info!("Changing DT_RPATH to DT_RUNPATH");
*tag = elf::DT_RUNPATH;
self.modified = true;
}
Ok(())
}
pub fn elf_use_rpath(&mut self) -> Result<()> {
let dynamic = self
.builder
.dynamic_data_mut()
.ok_or_else(|| Error::modify("No dynamic section found; can't change rpath"))?;
for entry in dynamic.iter_mut() {
let build::elf::Dynamic::String { tag, .. } = entry else {
continue;
};
if *tag != elf::DT_RUNPATH {
continue;
}
#[cfg(feature = "logging")]
info!("Changing DT_RUNPATH to DT_RPATH");
*tag = elf::DT_RPATH;
self.modified = true;
}
Ok(())
}
pub fn elf_needed(&self) -> impl Iterator<Item = &[u8]> {
let dynamic = self.builder.dynamic_data().unwrap_or(&[]);
dynamic.iter().filter_map(|entry| {
if let build::elf::Dynamic::String { tag, val } = entry {
if *tag == elf::DT_NEEDED {
return Some(val.as_slice());
}
}
None
})
}
pub fn elf_delete_needed(&mut self, names: &HashSet<Vec<u8>>) -> Result<()> {
let dynamic = self.builder.dynamic_data_mut().ok_or_else(|| {
Error::modify("No dynamic section found; can't delete needed library")
})?;
let mut modified = false;
dynamic.retain(|entry| {
let build::elf::Dynamic::String { tag, val } = entry else {
return true;
};
if *tag != elf::DT_NEEDED || !names.contains(val.as_slice()) {
return true;
}
#[cfg(feature = "logging")]
info!("Deleting DT_NEEDED entry {}", val);
modified = true;
false
});
if modified {
self.modified = true;
}
Ok(())
}
pub fn elf_replace_needed(&mut self, names: &HashMap<Vec<u8>, Vec<u8>>) -> Result<()> {
let dynamic = self.builder.dynamic_data_mut().ok_or_else(|| {
Error::modify("No dynamic section found; can't replace needed library")
})?;
for entry in dynamic.iter_mut() {
let build::elf::Dynamic::String { tag, val } = entry else {
continue;
};
if *tag != elf::DT_NEEDED {
continue;
}
let Some(name) = names.get(val.as_slice()) else {
continue;
};
let name = name.clone().into();
#[cfg(feature = "logging")]
info!("Replacing DT_NEEDED entry {} with {}", val, name);
*val = name;
self.modified = true;
}
Ok(())
}
pub fn elf_add_needed(&mut self, names: &[Vec<u8>]) -> Result<()> {
let dynamic = self
.builder
.dynamic_data_mut()
.ok_or_else(|| Error::modify("No dynamic section found; can't add needed library"))?;
let mut found = HashSet::new();
for entry in dynamic.iter() {
let build::elf::Dynamic::String { tag, val } = entry else {
continue;
};
if *tag != elf::DT_NEEDED {
continue;
}
found.insert(val.clone());
}
for name in names
.iter()
.rev()
.filter(|name| !found.contains(name.as_slice()))
{
let val = name.clone().into();
#[cfg(feature = "logging")]
info!("Adding DT_NEEDED entry {}", val);
dynamic.insert(
0,
build::elf::Dynamic::String {
tag: elf::DT_NEEDED,
val,
},
);
self.modified = true;
}
Ok(())
}
pub fn elf_soname(&self) -> Option<&[u8]> {
let id = self.builder.dynamic_section()?;
let section = self.builder.sections.get(id);
let build::elf::SectionData::Dynamic(dynamic) = §ion.data else {
return None;
};
for entry in dynamic.iter() {
let build::elf::Dynamic::String { tag, val } = entry else {
continue;
};
if *tag != elf::DT_SONAME {
continue;
}
return Some(val);
}
None
}
pub fn elf_set_soname(&mut self, soname: Vec<u8>) -> Result<()> {
let dynamic = self
.builder
.dynamic_data_mut()
.ok_or_else(|| Error::modify("No dynamic section found; can't set soname"))?;
let mut found = false;
for entry in dynamic.iter_mut() {
let build::elf::Dynamic::String { tag, val } = entry else {
continue;
};
if *tag != elf::DT_SONAME {
continue;
}
*val = soname.clone().into();
#[cfg(feature = "logging")]
info!("Setting DT_SONAME entry to {}", val);
found = true;
}
if !found {
let val = soname.into();
#[cfg(feature = "logging")]
info!("Adding DT_SONAME entry {}", val);
dynamic.push(build::elf::Dynamic::String {
tag: elf::DT_SONAME,
val,
});
}
self.modified = true;
Ok(())
}
pub fn elf_interpreter(&self) -> Option<&[u8]> {
self.builder.interp_data()
}
pub fn elf_set_interpreter(&mut self, mut interpreter: Vec<u8>) -> Result<()> {
let data = self
.builder
.interp_data_mut()
.ok_or_else(|| Error::modify("No interp section found; can't set interpreter"))?;
#[cfg(feature = "logging")]
info!(
"Setting interpreter to {}",
build::ByteString::from(interpreter.as_slice())
);
if !interpreter.is_empty() && interpreter.last() != Some(&0) {
interpreter.push(0);
}
*data = interpreter.into();
self.modified = true;
Ok(())
}
pub(crate) fn elf_finalize(&mut self) -> Result<()> {
if self.modified {
move_sections(&mut self.builder)?;
}
Ok(())
}
}
enum BlockKind {
FileHeader,
ProgramHeaders,
Segment,
Section(build::elf::SectionId),
}
struct Block<'a> {
#[allow(dead_code)]
name: build::ByteString<'a>,
kind: BlockKind,
address: u64,
size: u64,
move_priority: u8,
}
fn move_sections(builder: &mut build::elf::Builder) -> Result<()> {
builder.delete_orphans();
builder.delete_unused_versions();
builder.set_section_sizes();
let mut added_p_flags = Vec::new();
let mut added_segments = 0;
loop {
let mut move_sections = find_move_sections(builder, added_segments)?;
if move_sections.is_empty() {
return Ok(());
}
added_p_flags.clear();
for id in &move_sections {
let section = builder.sections.get_mut(*id);
section.sh_offset = 0;
let p_flags = section.p_flags();
if !added_p_flags.contains(&p_flags) {
added_p_flags.push(p_flags);
}
}
let mut split_segments = 0;
for segment in &mut builder.segments {
if segment.p_type == elf::PT_LOAD {
continue;
}
let mut any = false;
let mut all = true;
for id in &segment.sections {
if move_sections.contains(id) {
any = true;
} else {
all = false;
}
}
if !any || all {
continue;
}
split_segments += 1;
}
if added_segments < split_segments + added_p_flags.len() {
added_segments = split_segments + added_p_flags.len();
continue;
}
#[cfg(feature = "logging")]
info!(
"Moving {} sections, adding {} PT_LOAD segments, splitting {} segments",
move_sections.len(),
added_p_flags.len(),
split_segments,
);
move_sections.sort_by_key(|id| {
let section = builder.sections.get(*id);
(section.sh_addr, section.sh_size)
});
for p_flags in added_p_flags {
let segment = builder
.segments
.add_load_segment(p_flags, builder.load_align);
for id in &move_sections {
let section = builder.sections.get_mut(*id);
if p_flags == section.p_flags() {
segment.append_section(section);
#[cfg(feature = "logging")]
info!(
"Moved {} to offset {:x}, addr {:x}",
section.name, section.sh_offset, section.sh_addr
);
}
}
#[cfg(feature = "logging")]
info!(
"Added PT_LOAD segment with p_flags {:x}, offset {:x}, addr {:x}, size {:x}",
p_flags, segment.p_offset, segment.p_vaddr, segment.p_memsz,
);
}
let sections = &builder.sections;
let mut split_segments = Vec::new();
for segment in &mut builder.segments {
if segment.p_type == elf::PT_LOAD {
continue;
}
let mut any = false;
let mut all = true;
for id in &segment.sections {
if move_sections.contains(id) {
any = true;
} else {
all = false;
}
}
if !any {
continue;
}
if !all {
let mut split_sections = Vec::new();
segment.sections.retain(|id| {
if move_sections.contains(id) {
split_sections.push(*id);
false
} else {
true
}
});
split_segments.push((segment.id(), split_sections));
}
segment.recalculate_ranges(sections);
}
for (segment_id, split_sections) in split_segments {
let segment = builder.segments.copy(segment_id);
for id in split_sections {
let section = builder.sections.get_mut(id);
segment.append_section(section);
}
#[cfg(feature = "logging")]
info!(
"Split segment with type {:x}, offset {:x}, addr {:x}, size {:x}",
segment.p_type, segment.p_offset, segment.p_vaddr, segment.p_memsz,
);
}
let size = builder.program_headers_size() as u64;
for segment in &mut builder.segments {
if segment.p_type != elf::PT_PHDR {
continue;
}
segment.p_filesz = size;
segment.p_memsz = size;
}
return Ok(());
}
}
fn find_move_sections(
builder: &build::elf::Builder,
added_segments: usize,
) -> Result<Vec<build::elf::SectionId>> {
use build::elf::SectionData;
let mut move_sections = Vec::new();
let mut blocks = Vec::new();
let file_header_size = builder.file_header_size() as u64;
let program_headers_size = (builder.program_headers_size()
+ added_segments * builder.class().program_header_size())
as u64;
let interp = builder.interp_section();
if let Some(segment) = builder.segments.find_load_segment_from_offset(0) {
let address = segment.address_from_offset(0);
blocks.push(Block {
name: "file header".into(),
kind: BlockKind::FileHeader,
address,
size: file_header_size,
move_priority: 0,
});
}
if let Some(segment) = builder
.segments
.find_load_segment_from_offset(builder.header.e_phoff)
{
let address = segment.address_from_offset(builder.header.e_phoff);
blocks.push(Block {
name: "program headers".into(),
kind: BlockKind::ProgramHeaders,
address,
size: program_headers_size,
move_priority: 0,
});
}
for segment in &builder.segments {
if segment.p_type != elf::PT_LOAD {
continue;
}
blocks.push(Block {
name: "segment start".into(),
kind: BlockKind::Segment,
address: segment.p_vaddr,
size: 0,
move_priority: 0,
});
blocks.push(Block {
name: "segment end".into(),
kind: BlockKind::Segment,
address: segment.p_vaddr + segment.p_memsz,
size: 0,
move_priority: 0,
});
}
for section in &builder.sections {
if !section.is_alloc() {
continue;
}
if section.sh_offset == 0 {
move_sections.push(section.id());
continue;
}
if section.sh_type == elf::SHT_NOBITS && section.sh_flags & u64::from(elf::SHF_TLS) != 0 {
continue;
}
let move_priority = match §ion.data {
SectionData::Data(_) => {
if Some(section.id()) == interp {
1
} else {
0
}
}
SectionData::UninitializedData(_) | SectionData::Dynamic(_) => 0,
SectionData::DynamicRelocation(_) => 0,
SectionData::Relocation(_)
| SectionData::Note(_)
| SectionData::Attributes(_)
| SectionData::SectionString
| SectionData::Symbol
| SectionData::SymbolSectionIndex
| SectionData::String
| SectionData::DynamicSymbol
| SectionData::DynamicString
| SectionData::Hash
| SectionData::GnuHash
| SectionData::GnuVersym
| SectionData::GnuVerdef
| SectionData::GnuVerneed => 2,
};
blocks.push(Block {
name: (*section.name).into(),
kind: BlockKind::Section(section.id()),
address: section.sh_addr,
size: section.sh_size,
move_priority,
});
}
blocks.sort_by_key(|block| (block.address, block.size));
let mut i = 0;
while i + 1 < blocks.len() {
let end_address = blocks[i].address + blocks[i].size;
if end_address <= blocks[i + 1].address {
i += 1;
continue;
}
if blocks[i].move_priority >= blocks[i + 1].move_priority {
if blocks[i].move_priority == 0 {
#[cfg(feature = "logging")]
info!(
"Immovable {} (end address {:x}) overlaps immovable {} (start address {:x})",
blocks[i].name,
end_address,
blocks[i + 1].name,
blocks[i + 1].address,
);
return Err(Error::modify("Overlapping immovable sections"));
}
#[cfg(feature = "logging")]
info!(
"Need to move {} (end address {:x}) because of {} (start address {:x})",
blocks[i].name,
end_address,
blocks[i + 1].name,
blocks[i + 1].address,
);
if let BlockKind::Section(section) = blocks[i].kind {
move_sections.push(section);
blocks.remove(i);
} else {
unreachable!();
}
} else {
#[cfg(feature = "logging")]
info!(
"Need to move {} (start address {:x}) because of {} (end address {:x})",
blocks[i + 1].name,
blocks[i + 1].address,
blocks[i].name,
end_address,
);
if let BlockKind::Section(section) = blocks[i + 1].kind {
move_sections.push(section);
blocks.remove(i + 1);
} else {
unreachable!();
}
}
}
Ok(move_sections)
}