mod insn;
use goblin::elf::{Elf, RelocSection, SectionHeader};
use int_enum::IntEnum;
use crate::{
BIT, BIT_U64, ModuleErr, Result,
arch::{Ptr, aarch64::insn::*, get_rela_sym_idx, get_rela_type},
loader::*,
};
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct PltEntry {
adrp: u32,
add: u32,
br: u32,
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
struct ModPltSec {
shndx: usize,
num_entries: usize,
max_entries: usize,
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
pub struct ModuleArchSpecific {
plt: ModPltSec,
}
#[repr(u32)]
#[derive(Debug, Clone, Copy, IntEnum, PartialEq, Eq)]
#[allow(non_camel_case_types)]
pub enum ArchRelocationType {
R_ARM_NONE = 0,
R_AARCH64_NONE = 256,
R_AARCH64_ABS64 = 257,
R_AARCH64_ABS32 = 258,
R_AARCH64_ABS16 = 259,
R_AARCH64_PREL64 = 260,
R_AARCH64_PREL32 = 261,
R_AARCH64_PREL16 = 262,
R_AARCH64_MOVW_UABS_G0 = 263,
R_AARCH64_MOVW_UABS_G0_NC = 264,
R_AARCH64_MOVW_UABS_G1 = 265,
R_AARCH64_MOVW_UABS_G1_NC = 266,
R_AARCH64_MOVW_UABS_G2 = 267,
R_AARCH64_MOVW_UABS_G2_NC = 268,
R_AARCH64_MOVW_UABS_G3 = 269,
R_AARCH64_MOVW_SABS_G0 = 270,
R_AARCH64_MOVW_SABS_G1 = 271,
R_AARCH64_MOVW_SABS_G2 = 272,
R_AARCH64_LD_PREL_LO19 = 273,
R_AARCH64_ADR_PREL_LO21 = 274,
R_AARCH64_ADR_PREL_PG_HI21 = 275,
R_AARCH64_ADR_PREL_PG_HI21_NC = 276,
R_AARCH64_ADD_ABS_LO12_NC = 277,
R_AARCH64_LDST8_ABS_LO12_NC = 278,
R_AARCH64_TSTBR14 = 279,
R_AARCH64_CONDBR19 = 280,
R_AARCH64_JUMP26 = 282,
R_AARCH64_CALL26 = 283,
R_AARCH64_LDST16_ABS_LO12_NC = 284,
R_AARCH64_LDST32_ABS_LO12_NC = 285,
R_AARCH64_LDST64_ABS_LO12_NC = 286,
R_AARCH64_LDST128_ABS_LO12_NC = 299,
R_AARCH64_MOVW_PREL_G0 = 287,
R_AARCH64_MOVW_PREL_G0_NC = 288,
R_AARCH64_MOVW_PREL_G1 = 289,
R_AARCH64_MOVW_PREL_G1_NC = 290,
R_AARCH64_MOVW_PREL_G2 = 291,
R_AARCH64_MOVW_PREL_G2_NC = 292,
R_AARCH64_MOVW_PREL_G3 = 293,
R_AARCH64_RELATIVE = 1027,
}
type Arm64RelTy = ArchRelocationType;
const fn do_reloc(op: Aarch64RelocOp, location: Ptr, address: u64) -> u64 {
match op {
Aarch64RelocOp::RELOC_OP_ABS => address,
Aarch64RelocOp::RELOC_OP_PREL => address.wrapping_sub(location.0),
Aarch64RelocOp::RELOC_OP_PAGE => (address & !0xfff).wrapping_sub(location.0 & !0xfff),
Aarch64RelocOp::RELOC_OP_NONE => 0,
}
}
const AARCH64_INSN_ADRP: u32 = 0x9000_0000;
const AARCH64_INSN_ADD_IMM_64: u32 = 0x9100_0000;
const AARCH64_INSN_BR: u32 = 0xd61f_0000;
const AARCH64_REG_IP0: u32 = 16;
const fn signed_imm_check(value: i128, bits: u32) -> bool {
value >= -(1i128 << (bits - 1)) && value < (1i128 << (bits - 1))
}
fn emit_plt_entry(address: u64, plt_entry_addr: u64) -> Result<PltEntry> {
let target_page = (address & !0xfff) as i64 as i128;
let plt_page = (plt_entry_addr & !0xfff) as i64 as i128;
let page_delta = (target_page - plt_page) >> 12;
if !signed_imm_check(page_delta, 21) {
log::error!(
"AArch64 PLT target {:#x} is out of ADRP range from PLT entry {:#x}",
address,
plt_entry_addr
);
return Err(ModuleErr::ENOEXEC);
}
let imm = (page_delta as u32) & ((1 << 21) - 1);
let immlo = imm & 0x3;
let immhi = (imm >> 2) & 0x7ffff;
let adrp = AARCH64_INSN_ADRP | (immlo << 29) | (immhi << 5) | AARCH64_REG_IP0;
let add = AARCH64_INSN_ADD_IMM_64
| (((address & 0xfff) as u32) << 10)
| (AARCH64_REG_IP0 << 5)
| AARCH64_REG_IP0;
let br = AARCH64_INSN_BR | (AARCH64_REG_IP0 << 5);
Ok(PltEntry { adrp, add, br })
}
fn module_emit_plt_entry(
module: &mut ModuleOwner<impl KernelModuleHelper>,
sechdrs: &[SectionHeader],
address: u64,
) -> Result<&'static mut PltEntry> {
if module.arch.plt.num_entries >= module.arch.plt.max_entries {
log::error!("{}: too many PLT entries", module.name());
return Err(ModuleErr::ENOEXEC);
}
let plt_sec = &mut module.arch.plt;
let idx = plt_sec.num_entries;
let plt_entries_addr = sechdrs[plt_sec.shndx].sh_addr;
let plt_entries = unsafe {
core::slice::from_raw_parts_mut(plt_entries_addr as *mut PltEntry, plt_sec.max_entries)
};
let plt_entry_addr = &plt_entries[idx] as *const PltEntry as u64;
plt_entries[idx] = emit_plt_entry(address, plt_entry_addr)?;
plt_sec.num_entries += 1;
Ok(&mut plt_entries[idx])
}
fn is_forbidden_offset_for_adrp(address: u64) -> bool {
((address & 0xfff) >= 0xff8) && false
}
impl ArchRelocationType {
fn reloc_data(
&self,
op: Aarch64RelocOp,
location: Ptr,
address: u64,
len: usize,
) -> Result<bool> {
let s_addr = do_reloc(op, location, address) as i64;
match len {
16 => {
location.write::<i16>(s_addr as i16);
match op {
Aarch64RelocOp::RELOC_OP_ABS => Ok(s_addr < 0 || s_addr > u16::MAX as i64),
Aarch64RelocOp::RELOC_OP_PREL => {
Ok(s_addr < i16::MIN as i64 || s_addr > i16::MAX as i64)
}
_ => {
unreachable!("Unsupported operation for AArch64 16-bit relocation")
}
}
}
32 => {
location.write::<i32>(s_addr as i32);
match op {
Aarch64RelocOp::RELOC_OP_ABS => Ok(s_addr < 0 || s_addr > u32::MAX as i64),
Aarch64RelocOp::RELOC_OP_PREL => {
Ok(s_addr < i32::MIN as i64 || s_addr > i32::MAX as i64)
}
_ => {
unreachable!("Unsupported operation for AArch64 32-bit relocation")
}
}
}
64 => {
location.write::<u64>(s_addr as u64);
Ok(false)
}
_ => unreachable!("Unsupported length for AArch64 relocation"),
}
}
fn reloc_insn_movw(
&self,
op: Aarch64RelocOp,
location: Ptr,
address: u64,
lsb: i32,
imm_type: Aarch64InsnMovwImmType,
) -> Result<bool> {
let mut insn = location.read::<u32>();
let s_addr = do_reloc(op, location, address) as i64;
let mut imm = (s_addr >> lsb) as u64;
if imm_type == Aarch64InsnMovwImmType::AARCH64_INSN_IMM_MOVNZ {
insn &= !(3 << 29);
if s_addr >= 0 {
insn |= 2 << 29;
} else {
imm = !imm;
}
}
insn = aarch64_insn_encode_immediate(Aarch64InsnImmType::AARCH64_INSN_IMM_16, insn, imm);
location.write::<u32>(insn);
if imm > u16::MAX as u64 {
Ok(true)
} else {
Ok(false)
}
}
fn reloc_insn_imm(
&self,
op: Aarch64RelocOp,
location: Ptr,
address: u64,
lsb: i32,
len: i32,
imm_type: Aarch64InsnImmType,
) -> Result<bool> {
let mut insn = location.read::<u32>();
let mut s_addr = do_reloc(op, location, address) as i64;
s_addr >>= lsb;
let imm_mask = (BIT_U64!(lsb + len) - 1) >> lsb;
let imm = (s_addr as u64) & imm_mask;
insn = aarch64_insn_encode_immediate(imm_type, insn, imm);
location.write::<u32>(insn);
s_addr = (s_addr & !((imm_mask >> 1) as i64)) >> (len - 1);
if (s_addr + 1) as u64 >= 2 {
Ok(true)
} else {
Ok(false)
}
}
fn reloc_insn_adrp(&self, location: Ptr, address: u64) -> Result<bool> {
if !is_forbidden_offset_for_adrp(location.0) {
return self.reloc_insn_imm(
Aarch64RelocOp::RELOC_OP_PAGE,
location,
address,
12,
21,
Aarch64InsnImmType::AARCH64_INSN_IMM_ADR,
);
}
let ovf = self.reloc_insn_imm(
Aarch64RelocOp::RELOC_OP_PREL,
location,
address & !0xfff,
0,
21,
Aarch64InsnImmType::AARCH64_INSN_IMM_ADR,
)?;
if !ovf {
let mut insn = location.read::<u32>();
insn &= !BIT!(31); location.write::<u32>(insn);
Ok(false)
} else {
log::error!("ADR out of range for veneer emission");
Err(ModuleErr::ENOEXEC)
}
}
fn apply_relocation(
&self,
module: &mut ModuleOwner<impl KernelModuleHelper>,
sechdrs: &[SectionHeader],
location: u64,
address: u64,
) -> Result<()> {
let mut check_overflow = true;
let location = Ptr(location);
let ovf = match self {
Arm64RelTy::R_ARM_NONE | Arm64RelTy::R_AARCH64_NONE => false,
Arm64RelTy::R_AARCH64_ABS64 => {
check_overflow = false;
self.reloc_data(Aarch64RelocOp::RELOC_OP_ABS, location, address, 64)?
}
Arm64RelTy::R_AARCH64_ABS32 => {
self.reloc_data(Aarch64RelocOp::RELOC_OP_ABS, location, address, 32)?
}
Arm64RelTy::R_AARCH64_ABS16 => {
self.reloc_data(Aarch64RelocOp::RELOC_OP_ABS, location, address, 16)?
}
Arm64RelTy::R_AARCH64_PREL64 => {
check_overflow = false;
self.reloc_data(Aarch64RelocOp::RELOC_OP_PREL, location, address, 64)?
}
Arm64RelTy::R_AARCH64_PREL32 => {
self.reloc_data(Aarch64RelocOp::RELOC_OP_PREL, location, address, 32)?
}
Arm64RelTy::R_AARCH64_PREL16 => {
self.reloc_data(Aarch64RelocOp::RELOC_OP_PREL, location, address, 16)?
}
Arm64RelTy::R_AARCH64_MOVW_UABS_G0_NC | Arm64RelTy::R_AARCH64_MOVW_UABS_G0 => {
if *self == Arm64RelTy::R_AARCH64_MOVW_UABS_G0_NC {
check_overflow = false;
}
self.reloc_insn_movw(
Aarch64RelocOp::RELOC_OP_ABS,
location,
address,
0,
Aarch64InsnMovwImmType::AARCH64_INSN_IMM_MOVKZ,
)?
}
Arm64RelTy::R_AARCH64_MOVW_UABS_G1_NC | Arm64RelTy::R_AARCH64_MOVW_UABS_G1 => {
if *self == Arm64RelTy::R_AARCH64_MOVW_UABS_G1_NC {
check_overflow = false;
}
self.reloc_insn_movw(
Aarch64RelocOp::RELOC_OP_ABS,
location,
address,
16,
Aarch64InsnMovwImmType::AARCH64_INSN_IMM_MOVKZ,
)?
}
Arm64RelTy::R_AARCH64_MOVW_UABS_G2_NC | Arm64RelTy::R_AARCH64_MOVW_UABS_G2 => {
if *self == Arm64RelTy::R_AARCH64_MOVW_UABS_G2_NC {
check_overflow = false;
}
self.reloc_insn_movw(
Aarch64RelocOp::RELOC_OP_ABS,
location,
address,
32,
Aarch64InsnMovwImmType::AARCH64_INSN_IMM_MOVKZ,
)?
}
Arm64RelTy::R_AARCH64_MOVW_UABS_G3 => {
check_overflow = false;
self.reloc_insn_movw(
Aarch64RelocOp::RELOC_OP_ABS,
location,
address,
48,
Aarch64InsnMovwImmType::AARCH64_INSN_IMM_MOVKZ,
)?
}
Arm64RelTy::R_AARCH64_MOVW_SABS_G0 => self.reloc_insn_movw(
Aarch64RelocOp::RELOC_OP_ABS,
location,
address,
0,
Aarch64InsnMovwImmType::AARCH64_INSN_IMM_MOVNZ,
)?,
Arm64RelTy::R_AARCH64_MOVW_SABS_G1 => self.reloc_insn_movw(
Aarch64RelocOp::RELOC_OP_ABS,
location,
address,
16,
Aarch64InsnMovwImmType::AARCH64_INSN_IMM_MOVNZ,
)?,
Arm64RelTy::R_AARCH64_MOVW_SABS_G2 => self.reloc_insn_movw(
Aarch64RelocOp::RELOC_OP_ABS,
location,
address,
32,
Aarch64InsnMovwImmType::AARCH64_INSN_IMM_MOVNZ,
)?,
Arm64RelTy::R_AARCH64_MOVW_PREL_G0_NC | Arm64RelTy::R_AARCH64_MOVW_PREL_G0 => {
let mut imm_type = Aarch64InsnMovwImmType::AARCH64_INSN_IMM_MOVNZ;
if *self == Arm64RelTy::R_AARCH64_MOVW_PREL_G0_NC {
check_overflow = false;
imm_type = Aarch64InsnMovwImmType::AARCH64_INSN_IMM_MOVKZ;
}
self.reloc_insn_movw(
Aarch64RelocOp::RELOC_OP_PREL,
location,
address,
0,
imm_type,
)?
}
Arm64RelTy::R_AARCH64_MOVW_PREL_G1_NC | Arm64RelTy::R_AARCH64_MOVW_PREL_G1 => {
let mut imm_type = Aarch64InsnMovwImmType::AARCH64_INSN_IMM_MOVNZ;
if *self == Arm64RelTy::R_AARCH64_MOVW_PREL_G1_NC {
check_overflow = false;
imm_type = Aarch64InsnMovwImmType::AARCH64_INSN_IMM_MOVKZ;
}
self.reloc_insn_movw(
Aarch64RelocOp::RELOC_OP_PREL,
location,
address,
16,
imm_type,
)?
}
Arm64RelTy::R_AARCH64_MOVW_PREL_G2_NC | Arm64RelTy::R_AARCH64_MOVW_PREL_G2 => {
let mut imm_type = Aarch64InsnMovwImmType::AARCH64_INSN_IMM_MOVNZ;
if *self == Arm64RelTy::R_AARCH64_MOVW_PREL_G2_NC {
check_overflow = false;
imm_type = Aarch64InsnMovwImmType::AARCH64_INSN_IMM_MOVKZ;
}
self.reloc_insn_movw(
Aarch64RelocOp::RELOC_OP_PREL,
location,
address,
32,
imm_type,
)?
}
Arm64RelTy::R_AARCH64_MOVW_PREL_G3 => {
check_overflow = false;
self.reloc_insn_movw(
Aarch64RelocOp::RELOC_OP_PREL,
location,
address,
48,
Aarch64InsnMovwImmType::AARCH64_INSN_IMM_MOVNZ,
)?
}
Arm64RelTy::R_AARCH64_LD_PREL_LO19 => self.reloc_insn_imm(
Aarch64RelocOp::RELOC_OP_PREL,
location,
address,
2,
19,
Aarch64InsnImmType::AARCH64_INSN_IMM_19,
)?,
Arm64RelTy::R_AARCH64_ADR_PREL_LO21 => self.reloc_insn_imm(
Aarch64RelocOp::RELOC_OP_PREL,
location,
address,
0,
21,
Aarch64InsnImmType::AARCH64_INSN_IMM_ADR,
)?,
Arm64RelTy::R_AARCH64_ADR_PREL_PG_HI21_NC | Arm64RelTy::R_AARCH64_ADR_PREL_PG_HI21 => {
if *self == Arm64RelTy::R_AARCH64_ADR_PREL_PG_HI21_NC {
check_overflow = false;
}
self.reloc_insn_adrp(location, address)?
}
Arm64RelTy::R_AARCH64_ADD_ABS_LO12_NC | Arm64RelTy::R_AARCH64_LDST8_ABS_LO12_NC => {
check_overflow = false;
self.reloc_insn_imm(
Aarch64RelocOp::RELOC_OP_ABS,
location,
address,
0,
12,
Aarch64InsnImmType::AARCH64_INSN_IMM_12,
)?
}
Arm64RelTy::R_AARCH64_LDST16_ABS_LO12_NC => {
check_overflow = false;
self.reloc_insn_imm(
Aarch64RelocOp::RELOC_OP_ABS,
location,
address,
1,
11,
Aarch64InsnImmType::AARCH64_INSN_IMM_12,
)?
}
Arm64RelTy::R_AARCH64_LDST32_ABS_LO12_NC => {
check_overflow = false;
self.reloc_insn_imm(
Aarch64RelocOp::RELOC_OP_ABS,
location,
address,
2,
10,
Aarch64InsnImmType::AARCH64_INSN_IMM_12,
)?
}
Arm64RelTy::R_AARCH64_LDST64_ABS_LO12_NC => {
check_overflow = false;
self.reloc_insn_imm(
Aarch64RelocOp::RELOC_OP_ABS,
location,
address,
3,
9,
Aarch64InsnImmType::AARCH64_INSN_IMM_12,
)?
}
Arm64RelTy::R_AARCH64_LDST128_ABS_LO12_NC => {
check_overflow = false;
self.reloc_insn_imm(
Aarch64RelocOp::RELOC_OP_ABS,
location,
address,
4,
8,
Aarch64InsnImmType::AARCH64_INSN_IMM_12,
)?
}
Arm64RelTy::R_AARCH64_TSTBR14 => self.reloc_insn_imm(
Aarch64RelocOp::RELOC_OP_PREL,
location,
address,
2,
14,
Aarch64InsnImmType::AARCH64_INSN_IMM_14,
)?,
Arm64RelTy::R_AARCH64_CONDBR19 => self.reloc_insn_imm(
Aarch64RelocOp::RELOC_OP_PREL,
location,
address,
2,
19,
Aarch64InsnImmType::AARCH64_INSN_IMM_19,
)?,
Arm64RelTy::R_AARCH64_JUMP26 | Arm64RelTy::R_AARCH64_CALL26 => {
let mut ovf = self.reloc_insn_imm(
Aarch64RelocOp::RELOC_OP_PREL,
location,
address,
2,
26,
Aarch64InsnImmType::AARCH64_INSN_IMM_26,
)?;
if ovf {
let plt = module_emit_plt_entry(module, sechdrs, address)?;
let plt_addr = plt as *const PltEntry as u64;
ovf = self.reloc_insn_imm(
Aarch64RelocOp::RELOC_OP_PREL,
location,
plt_addr,
2,
26,
Aarch64InsnImmType::AARCH64_INSN_IMM_26,
)?;
}
ovf
}
_ => {
log::error!("Relocation type {:?} not implemented yet", self);
return Err(ModuleErr::ENOEXEC);
}
};
if check_overflow && ovf {
log::error!("Overflow detected during relocation type {:?}", self);
return Err(ModuleErr::ENOEXEC);
}
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 = Arm64RelTy::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 = reloc_type.apply_relocation(module, sechdrs, 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_plts = 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_EXECINSTR as u64 == 0 {
continue;
}
num_plts += count_plts(rela_sec);
}
if num_plts == 0 {
return Ok(());
}
let mut plt_section_idx = None;
for (idx, shdr) in elf.section_headers.iter().enumerate() {
let sec_name = elf.shdr_strtab.get_at(shdr.sh_name).unwrap_or("<unknown>");
if sec_name == ".plt" {
plt_section_idx = Some(idx);
break;
}
}
let Some(plt_section_idx) = plt_section_idx else {
log::error!("{:?}: module .PLT section missing", owner.name());
return Err(ModuleErr::ENOEXEC);
};
let plt_sec = &mut elf.section_headers[plt_section_idx];
plt_sec.sh_type = goblin::elf::section_header::SHT_PROGBITS;
plt_sec.sh_flags = (goblin::elf::section_header::SHF_ALLOC
| goblin::elf::section_header::SHF_EXECINSTR) as u64;
plt_sec.sh_addralign = 4;
plt_sec.sh_size = (num_plts * core::mem::size_of::<PltEntry>()) as u64;
owner.arch.plt.shndx = plt_section_idx;
owner.arch.plt.num_entries = 0;
owner.arch.plt.max_entries = num_plts;
Ok(())
}
fn count_plts(rela_sec: &RelocSection) -> usize {
rela_sec
.iter()
.filter(|rela| {
matches!(
Arm64RelTy::try_from(rela.r_type),
Ok(Arm64RelTy::R_AARCH64_CALL26 | Arm64RelTy::R_AARCH64_JUMP26)
)
})
.count()
}