Skip to main content

memf_linux/
modules.rs

1//! Linux kernel module walker.
2//!
3//! Enumerates loaded kernel modules by walking the `modules` linked list.
4//! Each `struct module` is connected via `list` (`list_head`).
5
6use memf_core::object_reader::ObjectReader;
7use memf_format::PhysicalMemoryProvider;
8
9use crate::{Error, ModuleInfo, ModuleState, Result};
10
11/// Walk the Linux kernel module list.
12pub fn walk_modules<P: PhysicalMemoryProvider>(
13    reader: &ObjectReader<P>,
14) -> Result<Vec<ModuleInfo>> {
15    let modules_addr = reader.required_symbol("modules")?;
16
17    let module_addrs = reader.walk_list(modules_addr, "module", "list")?;
18
19    let mut modules = Vec::new();
20    for &mod_addr in &module_addrs {
21        if let Ok(info) = read_module_info(reader, mod_addr) {
22            modules.push(info);
23        }
24    }
25
26    Ok(modules)
27}
28
29fn read_module_info<P: PhysicalMemoryProvider>(
30    reader: &ObjectReader<P>,
31    mod_addr: u64,
32) -> Result<ModuleInfo> {
33    let name = reader.read_field_string(mod_addr, "module", "name", 56)?;
34    let state: u32 = reader.read_field(mod_addr, "module", "state")?;
35    let (base_addr, size) = read_core_layout(reader, mod_addr)?;
36
37    Ok(ModuleInfo {
38        name,
39        base_addr,
40        size,
41        state: ModuleState::from_raw(state),
42    })
43}
44
45fn read_core_layout<P: PhysicalMemoryProvider>(
46    reader: &ObjectReader<P>,
47    mod_addr: u64,
48) -> Result<(u64, u64)> {
49    // Try core_layout.base / core_layout.size (kernel >= 4.5)
50    if let (Some(layout_off), Some(_base_off), Some(_size_off)) = (
51        reader.symbols().field_offset("module", "core_layout"),
52        reader.symbols().field_offset("module_layout", "base"),
53        reader.symbols().field_offset("module_layout", "size"),
54    ) {
55        let layout_addr = mod_addr + layout_off;
56        let base: u64 = reader.read_field(layout_addr, "module_layout", "base")?;
57        let size: u32 = reader.read_field(layout_addr, "module_layout", "size")?;
58        return Ok((base, u64::from(size)));
59    }
60
61    // Fallback: older kernels with module_core / core_size
62    if reader
63        .symbols()
64        .field_offset("module", "module_core")
65        .is_some()
66        && reader
67            .symbols()
68            .field_offset("module", "core_size")
69            .is_some()
70    {
71        let base: u64 = reader.read_field(mod_addr, "module", "module_core")?;
72        let size: u32 = reader.read_field(mod_addr, "module", "core_size")?;
73        return Ok((base, u64::from(size)));
74    }
75
76    Err(Error::WalkFailed {
77        walker: "get_module_core_layout",
78        reason: "cannot determine module core layout: no core_layout or module_core field".into(),
79    })
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85    use crate::testing::make_reader;
86    use memf_core::object_reader::ObjectReader;
87    use memf_core::test_builders::{flags, PageTableBuilder, SyntheticPhysMem};
88    use memf_core::vas::{TranslationMode, VirtualAddressSpace};
89    use memf_symbols::isf::IsfResolver;
90    use memf_symbols::test_builders::IsfBuilder;
91
92    fn make_module_reader(data: &[u8], vaddr: u64, paddr: u64) -> ObjectReader<SyntheticPhysMem> {
93        let isf = IsfBuilder::new()
94            .add_struct("module", 256)
95            .add_field("module", "list", 0, "list_head")
96            .add_field("module", "name", 16, "char")
97            .add_field("module", "state", 72, "unsigned int")
98            .add_field("module", "core_layout", 80, "module_layout")
99            .add_struct("module_layout", 32)
100            .add_field("module_layout", "base", 0, "pointer")
101            .add_field("module_layout", "size", 8, "unsigned int")
102            .add_struct("list_head", 16)
103            .add_field("list_head", "next", 0, "pointer")
104            .add_field("list_head", "prev", 8, "pointer")
105            .add_symbol("modules", vaddr);
106        let ptb = PageTableBuilder::new()
107            .map_4k(vaddr, paddr, flags::WRITABLE)
108            .write_phys(paddr, data);
109        make_reader(&isf, ptb)
110    }
111
112    #[test]
113    fn walk_two_modules() {
114        let vaddr: u64 = 0xFFFF_8000_0010_0000;
115        let paddr: u64 = 0x0080_0000;
116        let head = vaddr;
117        let a_list = vaddr + 0x100;
118        let b_list = vaddr + 0x300;
119
120        let mut data = vec![0u8; 4096];
121
122        // head: next -> A.list, prev -> B.list
123        data[0..8].copy_from_slice(&a_list.to_le_bytes());
124        data[8..16].copy_from_slice(&b_list.to_le_bytes());
125
126        // Module A at 0x100
127        data[0x100..0x108].copy_from_slice(&b_list.to_le_bytes());
128        data[0x108..0x110].copy_from_slice(&head.to_le_bytes());
129        data[0x110..0x118].copy_from_slice(b"ext4\0\0\0\0");
130        data[0x148..0x14C].copy_from_slice(&0u32.to_le_bytes());
131        data[0x150..0x158].copy_from_slice(&0xFFFF_A000u64.to_le_bytes());
132        data[0x158..0x15C].copy_from_slice(&0x2000u32.to_le_bytes());
133
134        // Module B at 0x300
135        data[0x300..0x308].copy_from_slice(&head.to_le_bytes());
136        data[0x308..0x310].copy_from_slice(&a_list.to_le_bytes());
137        data[0x310..0x318].copy_from_slice(b"nf_nat\0\0");
138        data[0x348..0x34C].copy_from_slice(&0u32.to_le_bytes());
139        data[0x350..0x358].copy_from_slice(&0xFFFF_B000u64.to_le_bytes());
140        data[0x358..0x35C].copy_from_slice(&0x1000u32.to_le_bytes());
141
142        let reader = make_module_reader(&data, vaddr, paddr);
143        let mods = walk_modules(&reader).unwrap();
144
145        assert_eq!(mods.len(), 2);
146        assert_eq!(mods[0].name, "ext4");
147        assert_eq!(mods[0].base_addr, 0xFFFF_A000);
148        assert_eq!(mods[0].size, 0x2000);
149        assert_eq!(mods[0].state, ModuleState::Live);
150        assert_eq!(mods[1].name, "nf_nat");
151        assert_eq!(mods[1].base_addr, 0xFFFF_B000);
152        assert_eq!(mods[1].size, 0x1000);
153    }
154
155    #[test]
156    fn walk_modules_with_legacy_module_core_layout() {
157        // Uses module_core / core_size fields (old kernel fallback path)
158        // module_layout and core_layout fields intentionally absent
159        let vaddr: u64 = 0xFFFF_8000_0020_0000;
160        let paddr: u64 = 0x0090_0000;
161        let mod_vaddr: u64 = 0xFFFF_8000_0021_0000;
162        let mod_paddr: u64 = 0x0091_0000;
163
164        let mut head_page = [0u8; 4096];
165        head_page[0..8].copy_from_slice(&mod_vaddr.to_le_bytes());
166        head_page[8..16].copy_from_slice(&mod_vaddr.to_le_bytes());
167
168        let mut mod_page = [0u8; 4096];
169        // list.next → head (terminate after one module)
170        mod_page[0..8].copy_from_slice(&vaddr.to_le_bytes());
171        mod_page[8..16].copy_from_slice(&vaddr.to_le_bytes());
172        // name at offset 16
173        mod_page[16..23].copy_from_slice(b"virtio\0");
174        // state at offset 72
175        mod_page[72..76].copy_from_slice(&0u32.to_le_bytes()); // MODULE_STATE_LIVE
176                                                               // module_core at offset 80
177        mod_page[80..88].copy_from_slice(&0xFFFF_C000_0000u64.to_le_bytes());
178        // core_size at offset 88
179        mod_page[88..92].copy_from_slice(&0x8000u32.to_le_bytes());
180
181        let isf = IsfBuilder::new()
182            .add_struct("module", 256)
183            .add_field("module", "list", 0x00u64, "list_head")
184            .add_field("module", "name", 0x10u64, "char")
185            .add_field("module", "state", 0x48u64, "unsigned int")
186            .add_field("module", "module_core", 0x50u64, "pointer")
187            .add_field("module", "core_size", 0x58u64, "unsigned int")
188            // core_layout intentionally absent → fallback to module_core/core_size
189            .add_struct("list_head", 16)
190            .add_field("list_head", "next", 0x00u64, "pointer")
191            .add_field("list_head", "prev", 0x08u64, "pointer")
192            .add_symbol("modules", vaddr)
193            .build_json();
194
195        let resolver = IsfResolver::from_value(&isf).unwrap();
196        let (cr3, mem) = PageTableBuilder::new()
197            .map_4k(vaddr, paddr, flags::WRITABLE)
198            .write_phys(paddr, &head_page)
199            .map_4k(mod_vaddr, mod_paddr, flags::WRITABLE)
200            .write_phys(mod_paddr, &mod_page)
201            .build();
202        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
203        let reader = ObjectReader::new(vas, Box::new(resolver));
204
205        let mods = walk_modules(&reader).unwrap();
206        assert_eq!(mods.len(), 1, "should find one module via legacy layout");
207        assert_eq!(mods[0].name, "virtio");
208        assert_eq!(mods[0].base_addr, 0xFFFF_C000_0000);
209        assert_eq!(mods[0].size, 0x8000);
210    }
211
212    #[test]
213    fn walk_modules_no_layout_fields_skips_module() {
214        // Neither core_layout nor module_core/core_size fields exist
215        // → read_core_layout returns Err → module is skipped via if let Ok
216        let vaddr: u64 = 0xFFFF_8000_0030_0000;
217        let paddr: u64 = 0x0092_0000;
218        let mod_vaddr: u64 = 0xFFFF_8000_0031_0000;
219        let mod_paddr: u64 = 0x0093_0000;
220
221        let mut head_page = [0u8; 4096];
222        head_page[0..8].copy_from_slice(&mod_vaddr.to_le_bytes());
223        head_page[8..16].copy_from_slice(&mod_vaddr.to_le_bytes());
224
225        let mut mod_page = [0u8; 4096];
226        mod_page[0..8].copy_from_slice(&vaddr.to_le_bytes());
227        mod_page[8..16].copy_from_slice(&vaddr.to_le_bytes());
228        mod_page[16..23].copy_from_slice(b"broken\0");
229        mod_page[72..76].copy_from_slice(&0u32.to_le_bytes());
230
231        let isf = IsfBuilder::new()
232            .add_struct("module", 256)
233            .add_field("module", "list", 0x00u64, "list_head")
234            .add_field("module", "name", 0x10u64, "char")
235            .add_field("module", "state", 0x48u64, "unsigned int")
236            // No core_layout field and no module_core/core_size fields
237            .add_struct("list_head", 16)
238            .add_field("list_head", "next", 0x00u64, "pointer")
239            .add_field("list_head", "prev", 0x08u64, "pointer")
240            .add_symbol("modules", vaddr)
241            .build_json();
242
243        let resolver = IsfResolver::from_value(&isf).unwrap();
244        let (cr3, mem) = PageTableBuilder::new()
245            .map_4k(vaddr, paddr, flags::WRITABLE)
246            .write_phys(paddr, &head_page)
247            .map_4k(mod_vaddr, mod_paddr, flags::WRITABLE)
248            .write_phys(mod_paddr, &mod_page)
249            .build();
250        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
251        let reader = ObjectReader::new(vas, Box::new(resolver));
252
253        let mods = walk_modules(&reader).unwrap();
254        // Module skipped because read_core_layout returns Err
255        assert!(
256            mods.is_empty(),
257            "module with no layout fields must be skipped"
258        );
259    }
260
261    #[test]
262    fn walk_modules_missing_symbol_returns_error() {
263        // No "modules" symbol → walk_modules returns Err
264        let isf = IsfBuilder::new()
265            .add_struct("module", 256)
266            .add_struct("list_head", 16)
267            .add_field("list_head", "next", 0x00u64, "pointer")
268            .add_field("list_head", "prev", 0x08u64, "pointer")
269            .build_json();
270
271        let resolver = IsfResolver::from_value(&isf).unwrap();
272        let (cr3, mem) = PageTableBuilder::new().build();
273        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
274        let reader = ObjectReader::new(vas, Box::new(resolver));
275
276        let result = walk_modules(&reader);
277        assert!(result.is_err(), "missing modules symbol must return Err");
278    }
279
280    #[test]
281    fn empty_module_list() {
282        let vaddr: u64 = 0xFFFF_8000_0010_0000;
283        let paddr: u64 = 0x0080_0000;
284        let mut data = vec![0u8; 4096];
285        data[0..8].copy_from_slice(&vaddr.to_le_bytes());
286        data[8..16].copy_from_slice(&vaddr.to_le_bytes());
287
288        let reader = make_module_reader(&data, vaddr, paddr);
289        let mods = walk_modules(&reader).unwrap();
290        assert_eq!(mods.len(), 0);
291    }
292}