Skip to main content

kmod_loader/arch/
x86_64.rs

1use goblin::elf::{Elf, RelocSection, SectionHeader};
2use int_enum::IntEnum;
3
4use crate::{
5    ModuleErr, Result,
6    arch::*,
7    loader::{KernelModuleHelper, ModuleLoadInfo, ModuleOwner},
8};
9
10#[derive(Debug, Clone, Copy, Default)]
11#[repr(C)]
12pub struct ModuleArchSpecific {
13    got: ModSection,
14}
15
16#[repr(u32)]
17#[derive(Debug, Clone, Copy, IntEnum)]
18#[allow(non_camel_case_types)]
19/// See <https://elixir.bootlin.com/linux/v6.6/source/arch/x86/include/asm/elf.h#L47>
20pub enum ArchRelocationType {
21    /// No reloc
22    R_X86_64_NONE = 0,
23    /// Direct 64 bit
24    R_X86_64_64 = 1,
25    /// PC relative 32 bit signed
26    R_X86_64_PC32 = 2,
27    /// 32 bit GOT entry
28    R_X86_64_GOT32 = 3,
29    /// 32 bit PLT address
30    R_X86_64_PLT32 = 4,
31    /// Copy symbol at runtime
32    R_X86_64_COPY = 5,
33    /// Create GOT entry
34    R_X86_64_GLOB_DAT = 6,
35    /// Create PLT entry
36    R_X86_64_JUMP_SLOT = 7,
37    /// Adjust by program base
38    R_X86_64_RELATIVE = 8,
39    /// 32 bit signed pc relative offset to GOT
40    R_X86_64_GOTPCREL = 9,
41    /// Direct 32 bit zero extended
42    R_X86_64_32 = 10,
43    /// Direct 32 bit sign extended
44    R_X86_64_32S = 11,
45    /// Direct 16 bit zero extended
46    R_X86_64_16 = 12,
47    /// 16 bit sign extended pc relative
48    R_X86_64_PC16 = 13,
49    /// Direct 8 bit sign extended
50    R_X86_64_8 = 14,
51    /// 8 bit sign extended pc relative
52    R_X86_64_PC8 = 15,
53    /// Place relative 64-bit signed
54    R_X86_64_PC64 = 24,
55}
56
57type X64RelTy = ArchRelocationType;
58
59impl ArchRelocationType {
60    fn apply_r_x86_64_gotpcrel(
61        &self,
62        module: &mut ModuleOwner<impl KernelModuleHelper>,
63        sechdrs: &[SectionHeader],
64        location: u64,
65        symbol_addr: u64,
66        addend: i64,
67    ) -> Result<()> {
68        let location = Ptr(location);
69        let got = common_module_emit_got_entry(&mut module.arch.got, sechdrs, symbol_addr)?;
70        let got_addr = got as *const GotEntry as u64;
71        let value = (got_addr as i64)
72            .wrapping_add(addend)
73            .wrapping_sub(location.0 as i64);
74
75        if value != value as i32 as i64 {
76            log::error!(
77                "overflow in relocation type {:?}, displacement {:#x}",
78                self,
79                value
80            );
81            return Err(ModuleErr::ENOEXEC);
82        }
83
84        if location.as_slice::<u8>(4).iter().any(|&b| b != 0) {
85            log::error!(
86                "x86/modules: Invalid relocation target, existing value is nonzero for type {:?}, loc: {:#x}, value: {:#x}",
87                self,
88                location.0,
89                value
90            );
91            return Err(ModuleErr::ENOEXEC);
92        }
93
94        location.write::<u32>(value as u32);
95        Ok(())
96    }
97
98    fn apply_relocation(&self, location: u64, mut target_addr: u64) -> Result<()> {
99        let size;
100        let location = Ptr(location);
101        let overflow = || {
102            log::error!(
103                "overflow in relocation type {:?}, target address {:#x}",
104                self,
105                target_addr
106            );
107            log::error!("module likely not compiled with -mcmodel=kernel");
108            ModuleErr::ENOEXEC
109        };
110        match self {
111            X64RelTy::R_X86_64_NONE => return Ok(()),
112            X64RelTy::R_X86_64_64 => {
113                size = 8;
114            }
115            X64RelTy::R_X86_64_32 => {
116                if target_addr != target_addr as u32 as u64 {
117                    return Err(overflow());
118                }
119                size = 4;
120            }
121            X64RelTy::R_X86_64_32S => {
122                // Check if the value fits in a signed 32-bit integer
123                // C code: if ((s64)val != *(s32 *)&val) goto overflow;
124                // This checks: i64_value != sign_extend(low_32_bits_as_i32)
125                if (target_addr as i64) != ((target_addr as i32) as i64) {
126                    return Err(overflow());
127                }
128                size = 4;
129            }
130            X64RelTy::R_X86_64_PC32 | X64RelTy::R_X86_64_PLT32 => {
131                target_addr = target_addr.wrapping_sub(location.0);
132                size = 4;
133            }
134            X64RelTy::R_X86_64_PC64 => {
135                target_addr = target_addr.wrapping_sub(location.0);
136                size = 8;
137            }
138            _ => {
139                log::error!("x86/modules: Unsupported relocation type: {:?}", self);
140                return Err(ModuleErr::ENOEXEC);
141            }
142        }
143        // if (memcmp(loc, &zero, size))
144        if location.as_slice::<u8>(size).iter().any(|&b| b != 0) {
145            log::error!(
146                "x86/modules: Invalid relocation target, existing value is nonzero for type {:?}, loc: {:#x}, value: {:#x}",
147                self,
148                location.0,
149                target_addr
150            );
151            return Err(ModuleErr::ENOEXEC);
152        } else {
153            // Write the relocated value
154            match size {
155                4 => location.write::<u32>(target_addr as u32),
156                8 => location.write::<u64>(target_addr),
157                _ => unreachable!(),
158            }
159        }
160        Ok(())
161    }
162}
163
164pub struct ArchRelocate;
165
166#[allow(unused_assignments)]
167impl ArchRelocate {
168    /// See https://elixir.bootlin.com/linux/v6.6/source/arch/x86/kernel/module.c#L252
169    pub fn apply_relocate_add<H: KernelModuleHelper>(
170        rela_list: &[goblin::elf64::reloc::Rela],
171        rel_section: &SectionHeader,
172        sechdrs: &[SectionHeader],
173        load_info: &ModuleLoadInfo,
174        module: &mut ModuleOwner<H>,
175    ) -> Result<()> {
176        for rela in rela_list {
177            let rel_type = get_rela_type(rela.r_info);
178            let sym_idx = get_rela_sym_idx(rela.r_info);
179
180            // This is where to make the change
181            let location = sechdrs[rel_section.sh_info as usize].sh_addr + rela.r_offset;
182            let (sym, sym_name) = &load_info.syms[sym_idx];
183
184            let reloc_type = ArchRelocationType::try_from(rel_type).map_err(|_| {
185                log::error!(
186                    "[{:?}]: Invalid relocation type: {}",
187                    module.name(),
188                    rel_type
189                );
190                ModuleErr::ENOEXEC
191            })?;
192
193            let target_addr = sym.st_value.wrapping_add(rela.r_addend as u64);
194
195            log::info!(
196                "[{:?}]: Applying relocation {:?} at location {:#x} with target addr {:#x}",
197                module.name(),
198                reloc_type,
199                location,
200                target_addr
201            );
202
203            let res = match reloc_type {
204                X64RelTy::R_X86_64_GOTPCREL => reloc_type.apply_r_x86_64_gotpcrel(
205                    module,
206                    sechdrs,
207                    location,
208                    sym.st_value,
209                    rela.r_addend,
210                ),
211                _ => reloc_type.apply_relocation(location, target_addr),
212            };
213            match res {
214                Err(e) => {
215                    log::error!("[{:?}]: '{}' {:?}", module.name(), sym_name, e);
216                    return Err(e);
217                }
218                Ok(_) => { /* Successfully applied relocation */ }
219            }
220        }
221        Ok(())
222    }
223}
224
225pub fn module_frob_arch_sections<H: KernelModuleHelper>(
226    elf: &mut Elf,
227    owner: &mut ModuleOwner<H>,
228) -> Result<()> {
229    let mut num_gots = 0usize;
230    for (idx, rela_sec) in elf.shdr_relocs.iter() {
231        let shdr = &elf.section_headers[*idx];
232        if shdr.sh_type != goblin::elf::section_header::SHT_RELA {
233            continue;
234        }
235
236        let to_section = &elf.section_headers[shdr.sh_info as usize];
237        if to_section.sh_flags & goblin::elf::section_header::SHF_ALLOC as u64 == 0 {
238            continue;
239        }
240
241        num_gots += count_gots(rela_sec);
242    }
243
244    if num_gots == 0 {
245        return Ok(());
246    }
247
248    common_prepare_got_section(
249        elf,
250        &mut owner.arch.got,
251        num_gots,
252        core::mem::align_of::<GotEntry>() as u64,
253        0,
254    )?;
255
256    Ok(())
257}
258
259fn count_gots(rela_sec: &RelocSection) -> usize {
260    rela_sec
261        .iter()
262        .filter(|rela| {
263            matches!(
264                X64RelTy::try_from(rela.r_type),
265                Ok(X64RelTy::R_X86_64_GOTPCREL)
266            )
267        })
268        .count()
269}