Skip to main content

memf_core/
lib.rs

1#![deny(unsafe_code)]
2#![warn(missing_docs)]
3//! Virtual address translation and kernel object reading.
4//!
5//! This crate provides:
6//! - [`VirtualAddressSpace`] — page table walking for x86_64 (4-level, 5-level),
7//!   AArch64, and x86 PAE/non-PAE modes
8//! - [`ObjectReader`] — high-level kernel struct traversal using symbol information
9
10pub mod lzo;
11pub mod object_reader;
12pub mod pagefile;
13pub mod proto_pte;
14pub mod test_builders;
15pub mod vas;
16
17/// Error type for memf-core operations.
18#[non_exhaustive]
19#[derive(Debug, thiserror::Error)]
20pub enum Error {
21    /// Physical memory read error.
22    #[error("physical memory error: {0}")]
23    Physical(#[from] memf_format::Error),
24
25    /// Symbol resolution error.
26    #[error("symbol error: {0}")]
27    Symbol(#[from] memf_symbols::Error),
28
29    /// Page table entry not present (page fault).
30    #[error("page not present at virtual address {0:#018x}")]
31    PageNotPresent(u64),
32
33    /// Read crossed a page boundary and the next page is not mapped.
34    #[error("partial read: got {got} of {requested} bytes at {addr:#018x}")]
35    PartialRead {
36        /// Virtual address of the read.
37        addr: u64,
38        /// Bytes requested.
39        requested: usize,
40        /// Bytes actually read.
41        got: usize,
42    },
43
44    /// A required symbol or field was not found.
45    #[error("missing symbol or field: {0}")]
46    MissingSymbol(String),
47
48    /// Type size mismatch during Pod cast.
49    #[error("type size mismatch: expected {expected}, got {got}")]
50    SizeMismatch {
51        /// Expected size in bytes.
52        expected: usize,
53        /// Actual size available.
54        got: usize,
55    },
56
57    /// The list walk exceeded the maximum iteration count (cycle protection).
58    #[error("list walk exceeded {0} iterations (possible cycle)")]
59    ListCycle(usize),
60
61    /// Page is in a pagefile that was not provided.
62    #[error("page at {vaddr:#018x} paged out to pagefile {pagefile_num} offset {page_offset:#x}")]
63    PagedOut {
64        /// Virtual address of the faulting page.
65        vaddr: u64,
66        /// Pagefile number (0 = pagefile.sys, 1-15 = secondary).
67        pagefile_num: u8,
68        /// Page offset within the pagefile.
69        page_offset: u64,
70    },
71
72    /// Page uses a prototype PTE (shared section, not yet supported).
73    #[error("prototype PTE at {0:#018x} (not yet supported)")]
74    PrototypePte(u64),
75}
76
77/// A Result alias for memf-core.
78pub type Result<T> = std::result::Result<T, Error>;
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn error_display_page_not_present() {
86        let e = Error::PageNotPresent(0xFFFF_8000_0000_1000);
87        assert!(e.to_string().contains("0xffff800000001000"));
88    }
89
90    #[test]
91    fn error_display_partial_read() {
92        let e = Error::PartialRead {
93            addr: 0x1000,
94            requested: 8,
95            got: 4,
96        };
97        assert!(e.to_string().contains("4 of 8"));
98    }
99
100    #[test]
101    fn error_display_list_cycle() {
102        let e = Error::ListCycle(10000);
103        assert!(e.to_string().contains("10000"));
104    }
105
106    #[test]
107    fn error_display_missing_symbol() {
108        let e = Error::MissingSymbol("task_struct.pid".into());
109        assert!(e.to_string().contains("task_struct.pid"));
110    }
111
112    #[test]
113    fn error_display_size_mismatch() {
114        let e = Error::SizeMismatch {
115            expected: 8,
116            got: 4,
117        };
118        let msg = e.to_string();
119        assert!(msg.contains('8'));
120        assert!(msg.contains('4'));
121    }
122
123    #[test]
124    fn error_from_physical() {
125        let phys_err = memf_format::Error::UnknownFormat;
126        let e: Error = Error::from(phys_err);
127        assert!(matches!(e, Error::Physical(_)));
128        assert!(e.to_string().contains("unknown dump format"));
129    }
130
131    #[test]
132    fn error_from_symbol() {
133        let sym_err = memf_symbols::Error::NotFound("init_task".into());
134        let e: Error = Error::from(sym_err);
135        assert!(matches!(e, Error::Symbol(_)));
136        assert!(e.to_string().contains("init_task"));
137    }
138
139    #[test]
140    fn error_from_io_via_physical() {
141        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file gone");
142        let phys_err = memf_format::Error::from(io_err);
143        let e: Error = Error::from(phys_err);
144        assert!(matches!(e, Error::Physical(_)));
145    }
146
147    #[test]
148    fn error_display_paged_out() {
149        let e = Error::PagedOut {
150            vaddr: 0xFFFF_8000_0000_2000,
151            pagefile_num: 0,
152            page_offset: 0x1234,
153        };
154        let msg = e.to_string();
155        assert!(msg.contains("0xffff800000002000"));
156        assert!(msg.contains("pagefile 0"));
157        assert!(msg.contains("0x1234"));
158    }
159
160    #[test]
161    fn error_display_prototype_pte() {
162        let e = Error::PrototypePte(0xFFFF_8000_DEAD_0000);
163        let msg = e.to_string();
164        assert!(msg.contains("0xffff8000dead0000"));
165        assert!(msg.contains("prototype PTE"));
166    }
167}