use goblin::elf::{Elf, RelocSection, SectionHeader};
use int_enum::IntEnum;
use crate::{
ModuleErr, Result,
arch::*,
loader::{KernelModuleHelper, ModuleLoadInfo, ModuleOwner},
};
#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
pub struct ModuleArchSpecific {
got: ModSection,
}
#[repr(u32)]
#[derive(Debug, Clone, Copy, IntEnum)]
#[allow(non_camel_case_types)]
pub enum ArchRelocationType {
R_X86_64_NONE = 0,
R_X86_64_64 = 1,
R_X86_64_PC32 = 2,
R_X86_64_GOT32 = 3,
R_X86_64_PLT32 = 4,
R_X86_64_COPY = 5,
R_X86_64_GLOB_DAT = 6,
R_X86_64_JUMP_SLOT = 7,
R_X86_64_RELATIVE = 8,
R_X86_64_GOTPCREL = 9,
R_X86_64_32 = 10,
R_X86_64_32S = 11,
R_X86_64_16 = 12,
R_X86_64_PC16 = 13,
R_X86_64_8 = 14,
R_X86_64_PC8 = 15,
R_X86_64_PC64 = 24,
}
type X64RelTy = ArchRelocationType;
impl ArchRelocationType {
fn apply_r_x86_64_gotpcrel(
&self,
module: &mut ModuleOwner<impl KernelModuleHelper>,
sechdrs: &[SectionHeader],
location: u64,
symbol_addr: u64,
addend: i64,
) -> Result<()> {
let location = Ptr(location);
let got = common_module_emit_got_entry(&mut module.arch.got, sechdrs, symbol_addr)?;
let got_addr = got as *const GotEntry as u64;
let value = (got_addr as i64)
.wrapping_add(addend)
.wrapping_sub(location.0 as i64);
if value != value as i32 as i64 {
log::error!(
"overflow in relocation type {:?}, displacement {:#x}",
self,
value
);
return Err(ModuleErr::ENOEXEC);
}
if location.as_slice::<u8>(4).iter().any(|&b| b != 0) {
log::error!(
"x86/modules: Invalid relocation target, existing value is nonzero for type {:?}, loc: {:#x}, value: {:#x}",
self,
location.0,
value
);
return Err(ModuleErr::ENOEXEC);
}
location.write::<u32>(value as u32);
Ok(())
}
fn apply_relocation(&self, location: u64, mut target_addr: u64) -> Result<()> {
let size;
let location = Ptr(location);
let overflow = || {
log::error!(
"overflow in relocation type {:?}, target address {:#x}",
self,
target_addr
);
log::error!("module likely not compiled with -mcmodel=kernel");
ModuleErr::ENOEXEC
};
match self {
X64RelTy::R_X86_64_NONE => return Ok(()),
X64RelTy::R_X86_64_64 => {
size = 8;
}
X64RelTy::R_X86_64_32 => {
if target_addr != target_addr as u32 as u64 {
return Err(overflow());
}
size = 4;
}
X64RelTy::R_X86_64_32S => {
if (target_addr as i64) != ((target_addr as i32) as i64) {
return Err(overflow());
}
size = 4;
}
X64RelTy::R_X86_64_PC32 | X64RelTy::R_X86_64_PLT32 => {
target_addr = target_addr.wrapping_sub(location.0);
size = 4;
}
X64RelTy::R_X86_64_PC64 => {
target_addr = target_addr.wrapping_sub(location.0);
size = 8;
}
_ => {
log::error!("x86/modules: Unsupported relocation type: {:?}", self);
return Err(ModuleErr::ENOEXEC);
}
}
if location.as_slice::<u8>(size).iter().any(|&b| b != 0) {
log::error!(
"x86/modules: Invalid relocation target, existing value is nonzero for type {:?}, loc: {:#x}, value: {:#x}",
self,
location.0,
target_addr
);
return Err(ModuleErr::ENOEXEC);
} else {
match size {
4 => location.write::<u32>(target_addr as u32),
8 => location.write::<u64>(target_addr),
_ => unreachable!(),
}
}
Ok(())
}
}
pub struct ArchRelocate;
#[allow(unused_assignments)]
impl ArchRelocate {
pub fn apply_relocate_add<H: KernelModuleHelper>(
rela_list: &[goblin::elf64::reloc::Rela],
rel_section: &SectionHeader,
sechdrs: &[SectionHeader],
load_info: &ModuleLoadInfo,
module: &mut ModuleOwner<H>,
) -> Result<()> {
for rela in rela_list {
let rel_type = get_rela_type(rela.r_info);
let sym_idx = get_rela_sym_idx(rela.r_info);
let location = sechdrs[rel_section.sh_info as usize].sh_addr + rela.r_offset;
let (sym, sym_name) = &load_info.syms[sym_idx];
let reloc_type = ArchRelocationType::try_from(rel_type).map_err(|_| {
log::error!(
"[{:?}]: Invalid relocation type: {}",
module.name(),
rel_type
);
ModuleErr::ENOEXEC
})?;
let target_addr = sym.st_value.wrapping_add(rela.r_addend as u64);
log::info!(
"[{:?}]: Applying relocation {:?} at location {:#x} with target addr {:#x}",
module.name(),
reloc_type,
location,
target_addr
);
let res = match reloc_type {
X64RelTy::R_X86_64_GOTPCREL => reloc_type.apply_r_x86_64_gotpcrel(
module,
sechdrs,
location,
sym.st_value,
rela.r_addend,
),
_ => reloc_type.apply_relocation(location, target_addr),
};
match res {
Err(e) => {
log::error!("[{:?}]: '{}' {:?}", module.name(), sym_name, e);
return Err(e);
}
Ok(_) => { }
}
}
Ok(())
}
}
pub fn module_frob_arch_sections<H: KernelModuleHelper>(
elf: &mut Elf,
owner: &mut ModuleOwner<H>,
) -> Result<()> {
let mut num_gots = 0usize;
for (idx, rela_sec) in elf.shdr_relocs.iter() {
let shdr = &elf.section_headers[*idx];
if shdr.sh_type != goblin::elf::section_header::SHT_RELA {
continue;
}
let to_section = &elf.section_headers[shdr.sh_info as usize];
if to_section.sh_flags & goblin::elf::section_header::SHF_ALLOC as u64 == 0 {
continue;
}
num_gots += count_gots(rela_sec);
}
if num_gots == 0 {
return Ok(());
}
common_prepare_got_section(
elf,
&mut owner.arch.got,
num_gots,
core::mem::align_of::<GotEntry>() as u64,
0,
)?;
Ok(())
}
fn count_gots(rela_sec: &RelocSection) -> usize {
rela_sec
.iter()
.filter(|rela| {
matches!(
X64RelTy::try_from(rela.r_type),
Ok(X64RelTy::R_X86_64_GOTPCREL)
)
})
.count()
}