emd_common/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3// - STD
4#[cfg(feature = "std")]
5use std::{
6    ops::Range,
7    path::Path,
8    fs::{File, read_to_string},
9    io::{Result, BufReader, BufRead, Error, ErrorKind},
10};
11
12// This file exists to enable the library target.
13pub const BUFFER_SIZE: usize = 16384;
14pub const QUEUE_SIZE: u32 = 64;
15pub const MAX_QUEUE_SIZE: usize = QUEUE_SIZE as usize * BUFFER_SIZE;
16
17// IOMEM
18pub const PROC_IOMEM: &str = "/proc/iomem";
19pub const SEPARATOR_SYSTEM_RAM: &str = " : System RAM";
20pub const SEPARATOR_HYPHEN: char = '-';
21
22// KALLSYMS
23pub const PROC_KALLSYMS: &str = "/proc/kallsyms";
24pub const PAGE_OFFSET_BASE: &str = "page_offset_base";
25#[cfg(feature = "std")]
26const PROC_KPTR_RESTRICT: &str = "/proc/sys/kernel/kptr_restrict";
27#[cfg(feature = "std")]
28const PROC_OSRELEASE: &str = "/proc/sys/kernel/osrelease";
29#[cfg(feature = "std")]
30const SYSTEMMAP_PREFIX: &str = "/boot/System.map-";
31
32// self exe
33pub const PROC_SELF_EXE: &str = "/proc/self/exe";
34
35// eBPF fns
36pub const READ_KERNEL_MEM: &str = "read_kernel_memory";
37
38// - Errors
39pub const ERROR_DUMP_MEMORY_IOMEM_SEPARATE_KEY_VAL_MAP: &str = "There is no left side in key/value pair";
40pub const ERROR_DUMP_MEMORY_IOMEM_CAPSYSADM: &str = "Need CAP_SYS_ADMIN to read /proc/iomem";
41pub const ERROR_PATH_READ_SYS: &str = "An error occured while trying to read necessary data from /sys";
42
43#[derive(Debug)]
44pub struct LimeHeader {
45    pub magic_bytes: u32,
46    pub header_version: u32,
47    pub start_address: u64,
48    pub end_address: u64,
49    pub reserved_space: [u8; 8],
50}
51
52impl Default for LimeHeader {
53    fn default() -> Self {
54        Self {
55            magic_bytes: 0x4C694D45,
56            header_version: 1,
57            start_address: 0,
58            end_address: 0,
59            reserved_space: [0u8; 8],
60        }
61    }
62}
63
64impl LimeHeader {
65    pub fn new(start_address: u64, end_address: u64) -> Self {
66        Self {
67            start_address,
68            end_address,
69            ..Default::default()
70        }
71    }
72
73    pub fn as_bytes(&self) -> [u8; 32] {
74        let mut bytes = [0u8; 32];
75        bytes[0..4].copy_from_slice(&self.magic_bytes.to_le_bytes());
76        bytes[4..8].copy_from_slice(&self.header_version.to_le_bytes());
77        bytes[8..16].copy_from_slice(&self.start_address.to_le_bytes());
78        bytes[16..24].copy_from_slice(&self.end_address.to_le_bytes());
79        bytes[24..32].copy_from_slice(&self.reserved_space);
80        bytes
81    }
82}
83
84pub enum Header {
85    None,
86    Lime(LimeHeader),
87}
88
89pub fn calc_queue_elements(dump_size: usize) -> usize {
90    if dump_size % BUFFER_SIZE == 0 {
91            dump_size / BUFFER_SIZE
92    } else {
93        dump_size / BUFFER_SIZE + 1
94    }
95}
96
97/// This method results you the current map of the system's memory for each physical device. Normally, could be found at
98/// /proc/iomem. 
99/// # Example output
100///As following you can see an  **partial** example output of the content of file **/proc/iomem**:
101/// ```bash
102/// 00000000-00000fff : Reserved
103/// 00001000-0009efff : System RAM
104/// 0009f000-000fffff : Reserved
105///   000a0000-000bffff : PCI Bus 0000:00
106///   000f0000-000fffff : System ROM
107/// 00100000-5894bfff : System RAM
108/// 5894c000-589c6fff : Reserved
109/// 589c7000-5e068fff : System RAM
110/// 5e069000-5e968fff : Reserved
111/// 5e969000-6cb7efff : System RAM
112/// 6cb7f000-6cf4efff : Unknown E820 type
113/// 6cf4f000-6ed6efff : Reserved
114/// 6ed6f000-6fbcefff : ACPI Non-volatile Storage
115/// 6fbcf000-6fc4efff : ACPI Tables
116/// 6fc4f000-6fc4ffff : System RAM
117/// 6fc50000-7d7fffff : Reserved
118///   70200000-75f7ffff : INT0E0C:00
119///   79800000-7d7fffff : Graphics Stolen Memory
120/// 7d800000-dfffffff : PCI Bus 0000:00
121/// ```
122///The first column displays the memory registers used by each of the different types of memory. The second column
123///lists the kind of memory located within those registers and displays which memory registers are used by the kernel
124///within the system RAM or, if the network interface card has multiple Ethernet ports, the memory registers assigned
125///for each port.
126///This method results you the entries as a Vec<std::ops::Range<u64>> for the given identifier (e.g. "System RAM").
127#[cfg(feature = "std")]
128pub fn extract_mem_range<I: Into<String>>(identifier: I) -> Result<Vec<Range<u64>>> {
129    let identifier = identifier.into();
130    let path = Path::new(PROC_IOMEM);
131    let file = File::open(path)?;
132    let reader = BufReader::new(file);
133    let mut ranges = Vec::new();
134
135    for line in reader.lines() {
136        let line = line?;
137        if line.contains(&identifier) {
138            if let Some((start, end)) = parse_memory_range(&line) {
139                ranges.push(start..(end + 1));
140            }
141        }
142    }
143
144    Ok(ranges)
145}
146
147#[cfg(feature = "std")]
148pub fn get_page_offset_base_address_from_file() -> Result<u64>{
149    let file = match get_kptr_restrict()? {
150        KptrRestrict::Full => get_system_map_fd()?,
151        _ => get_kallsyms_fd()?,
152    };
153    let reader = BufReader::new(file);
154    for line in reader.lines() {
155        let line = line?;
156        if line.contains(PAGE_OFFSET_BASE) {
157            let parts: Vec<&str> = line.split_whitespace().collect();
158            if let Some(offset_str) = parts.first() {
159                return u64::from_str_radix(offset_str, 16)
160                    .map_err(|e| Error::new(ErrorKind::InvalidData, e))
161            }
162        }
163    }
164    // if no page offset base is found, KASLR is disabled...?
165    Ok(0)
166}
167
168#[cfg(feature = "std")]
169fn get_system_map_fd() -> Result<File> {
170    let os_release = read_to_string(PROC_OSRELEASE)?;
171    let path = Path::new(SYSTEMMAP_PREFIX).join(os_release);
172    File::open(path)
173}
174
175#[cfg(feature = "std")]
176fn get_kallsyms_fd() -> Result<File> {
177    let path = Path::new(PROC_KALLSYMS);
178    File::open(path)
179}
180
181#[cfg(feature = "std")]
182fn get_kptr_restrict() -> Result<KptrRestrict> {
183    let value_str = read_to_string(PROC_KPTR_RESTRICT)?;
184    let value = value_str.trim().parse::<u8>().unwrap();
185    KptrRestrict::try_from(value)
186}
187
188#[cfg(feature = "std")]
189fn parse_memory_range(line: &str) -> Option<(u64, u64)> {
190    let parts: Vec<&str> = line.split_whitespace().collect();
191    if let Some(range_part) = parts.first() {
192        let bounds: Vec<&str> = range_part.split(SEPARATOR_HYPHEN).collect();
193        if bounds.len() == 2 {
194            if let (Ok(start), Ok(end)) = (u64::from_str_radix(bounds[0], 16), u64::from_str_radix(bounds[1], 16)) {
195                return Some((start, end));
196            }
197        }
198    }
199    None
200}
201
202#[cfg(feature = "std")]
203enum KptrRestrict {
204    None,
205    Partial,
206    Full
207}
208
209#[cfg(feature = "std")]
210impl TryFrom<u8> for KptrRestrict {
211    type Error = std::io::Error;
212
213    fn try_from(value: u8) -> core::result::Result<Self, Self::Error> {
214        match value {
215            0 => Ok(KptrRestrict::None),
216            1 => Ok(KptrRestrict::Partial),
217            2 => Ok(KptrRestrict::Full),
218            _ => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, format!("{value}"))),
219        }
220    }
221}