1use memf_core::object_reader::ObjectReader;
9use memf_format::PhysicalMemoryProvider;
10
11use crate::{EnvVarInfo, Error, Result};
12
13const MAX_ENV_SIZE: u64 = 256 * 1024;
15
16pub fn walk_envvars<P: PhysicalMemoryProvider>(
18 reader: &ObjectReader<P>,
19) -> Result<Vec<EnvVarInfo>> {
20 let init_task_addr = reader
21 .symbols()
22 .symbol_address("init_task")
23 .ok_or_else(|| Error::MissingKernelSymbol {
24 name: "init_task".into(),
25 })?;
26
27 let tasks_offset = reader
28 .symbols()
29 .field_offset("task_struct", "tasks")
30 .ok_or_else(|| Error::MissingField {
31 struct_name: "task_struct".into(),
32 field_name: "tasks".into(),
33 })?;
34
35 let head_vaddr = init_task_addr + tasks_offset;
36 let task_addrs = reader.walk_list(head_vaddr, "task_struct", "tasks")?;
37
38 let mut all_vars = Vec::new();
39
40 collect_process_envvars(reader, init_task_addr, &mut all_vars);
41
42 for &task_addr in &task_addrs {
43 collect_process_envvars(reader, task_addr, &mut all_vars);
44 }
45
46 Ok(all_vars)
47}
48
49fn collect_process_envvars<P: PhysicalMemoryProvider>(
50 reader: &ObjectReader<P>,
51 task_addr: u64,
52 out: &mut Vec<EnvVarInfo>,
53) {
54 let mm_ptr: u64 = match reader.read_field(task_addr, "task_struct", "mm") {
55 Ok(v) => v,
56 Err(_) => return,
57 };
58 if mm_ptr == 0 {
59 return;
60 }
61
62 if let Ok(vars) = walk_process_envvars(reader, task_addr) {
63 out.extend(vars);
64 }
65}
66
67pub fn walk_process_envvars<P: PhysicalMemoryProvider>(
69 reader: &ObjectReader<P>,
70 task_addr: u64,
71) -> Result<Vec<EnvVarInfo>> {
72 let pid: u32 = reader.read_field(task_addr, "task_struct", "pid")?;
73 let comm = reader.read_field_string(task_addr, "task_struct", "comm", 16)?;
74 let mm_ptr: u64 = reader.read_field(task_addr, "task_struct", "mm")?;
75
76 if mm_ptr == 0 {
77 return Err(Error::WalkFailed {
78 walker: "walk_process_envvars",
79 reason: format!("task {comm} (PID {pid}) has NULL mm (kernel thread)"),
80 });
81 }
82
83 let env_start: u64 = reader.read_field(mm_ptr, "mm_struct", "env_start")?;
84 let env_end: u64 = reader.read_field(mm_ptr, "mm_struct", "env_end")?;
85
86 if env_start == 0 || env_end <= env_start {
87 return Ok(Vec::new());
88 }
89
90 let size = (env_end - env_start).min(MAX_ENV_SIZE);
91 let data = reader.read_bytes(env_start, size as usize)?;
92
93 Ok(parse_env_region(&data, u64::from(pid), &comm))
94}
95
96fn parse_env_region(data: &[u8], pid: u64, comm: &str) -> Vec<EnvVarInfo> {
97 let mut vars = Vec::new();
98
99 for chunk in data.split(|&b| b == 0) {
100 if chunk.is_empty() {
101 continue;
102 }
103 let s = String::from_utf8_lossy(chunk);
104 if let Some(eq_pos) = s.find('=') {
105 let key = s[..eq_pos].to_string();
106 let value = s[eq_pos + 1..].to_string();
107 vars.push(EnvVarInfo {
108 pid,
109 comm: comm.to_string(),
110 key,
111 value,
112 });
113 }
114 }
115
116 vars
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use memf_core::test_builders::{flags, PageTableBuilder, SyntheticPhysMem};
123 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
124 use memf_symbols::isf::IsfResolver;
125 use memf_symbols::test_builders::IsfBuilder;
126
127 fn make_test_reader(
128 data: &[u8],
129 vaddr: u64,
130 paddr: u64,
131 extra_mappings: &[(u64, u64, &[u8])],
132 ) -> ObjectReader<SyntheticPhysMem> {
133 let isf = IsfBuilder::new()
134 .add_struct("task_struct", 128)
135 .add_field("task_struct", "pid", 0, "int")
136 .add_field("task_struct", "state", 4, "long")
137 .add_field("task_struct", "tasks", 16, "list_head")
138 .add_field("task_struct", "comm", 32, "char")
139 .add_field("task_struct", "mm", 48, "pointer")
140 .add_struct("list_head", 16)
141 .add_field("list_head", "next", 0, "pointer")
142 .add_field("list_head", "prev", 8, "pointer")
143 .add_struct("mm_struct", 128)
144 .add_field("mm_struct", "pgd", 0, "pointer")
145 .add_field("mm_struct", "env_start", 64, "unsigned long")
146 .add_field("mm_struct", "env_end", 72, "unsigned long")
147 .add_symbol("init_task", vaddr)
148 .build_json();
149
150 let resolver = IsfResolver::from_value(&isf).unwrap();
151 let mut builder = PageTableBuilder::new()
152 .map_4k(vaddr, paddr, flags::WRITABLE)
153 .write_phys(paddr, data);
154
155 for &(ev, ep, edata) in extra_mappings {
156 builder = builder
157 .map_4k(ev, ep, flags::WRITABLE)
158 .write_phys(ep, edata);
159 }
160
161 let (cr3, mem) = builder.build();
162 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
163 ObjectReader::new(vas, Box::new(resolver))
164 }
165
166 #[test]
167 fn walk_single_process_envvars() {
168 let vaddr: u64 = 0xFFFF_8000_0010_0000;
169 let paddr: u64 = 0x0080_0000;
170 let mut data = vec![0u8; 4096];
171
172 data[0..4].copy_from_slice(&1u32.to_le_bytes());
173 let tasks_addr = vaddr + 16;
174 data[16..24].copy_from_slice(&tasks_addr.to_le_bytes());
175 data[24..32].copy_from_slice(&tasks_addr.to_le_bytes());
176 data[32..36].copy_from_slice(b"bash");
177 let mm_addr = vaddr + 0x200;
178 data[48..56].copy_from_slice(&mm_addr.to_le_bytes());
179
180 let env_vaddr: u64 = 0xFFFF_8000_0020_0000;
181 let env_paddr: u64 = 0x0090_0000;
182 data[0x200..0x208].copy_from_slice(&0x1000u64.to_le_bytes());
183 data[0x240..0x248].copy_from_slice(&env_vaddr.to_le_bytes());
184 let env_data = b"HOME=/root\0PATH=/usr/bin:/bin\0SHELL=/bin/bash\0";
185 let env_end = env_vaddr + env_data.len() as u64;
186 data[0x248..0x250].copy_from_slice(&env_end.to_le_bytes());
187
188 let reader = make_test_reader(
189 &data,
190 vaddr,
191 paddr,
192 &[(env_vaddr, env_paddr, env_data.as_slice())],
193 );
194 let vars = walk_envvars(&reader).unwrap();
195
196 assert_eq!(vars.len(), 3);
197 assert_eq!(vars[0].pid, 1);
198 assert_eq!(vars[0].comm, "bash");
199 assert_eq!(vars[0].key, "HOME");
200 assert_eq!(vars[0].value, "/root");
201 assert_eq!(vars[1].key, "PATH");
202 assert_eq!(vars[1].value, "/usr/bin:/bin");
203 assert_eq!(vars[2].key, "SHELL");
204 assert_eq!(vars[2].value, "/bin/bash");
205 }
206
207 #[test]
208 fn walk_envvars_skips_kernel_threads() {
209 let vaddr: u64 = 0xFFFF_8000_0010_0000;
210 let paddr: u64 = 0x0080_0000;
211 let mut data = vec![0u8; 4096];
212
213 data[0..4].copy_from_slice(&0u32.to_le_bytes());
214 let tasks_addr = vaddr + 16;
215 data[16..24].copy_from_slice(&tasks_addr.to_le_bytes());
216 data[24..32].copy_from_slice(&tasks_addr.to_le_bytes());
217 data[32..41].copy_from_slice(b"swapper/0");
218 data[48..56].copy_from_slice(&0u64.to_le_bytes());
219
220 let reader = make_test_reader(&data, vaddr, paddr, &[]);
221 let vars = walk_envvars(&reader).unwrap();
222 assert!(vars.is_empty());
223 }
224
225 #[test]
226 fn walk_process_envvars_null_mm_returns_error() {
227 let vaddr: u64 = 0xFFFF_8000_0010_0000;
228 let paddr: u64 = 0x0080_0000;
229 let mut data = vec![0u8; 4096];
230
231 data[48..56].copy_from_slice(&0u64.to_le_bytes());
232
233 let reader = make_test_reader(&data, vaddr, paddr, &[]);
234 let result = walk_process_envvars(&reader, vaddr);
235 assert!(result.is_err());
236 }
237
238 #[test]
239 fn parse_env_region_handles_malformed_entries() {
240 let data = b"GOOD=value\0MALFORMED\0ALSO_GOOD=ok\0";
241 let vars = parse_env_region(data, 1, "test");
242
243 assert_eq!(vars.len(), 2);
244 assert_eq!(vars[0].key, "GOOD");
245 assert_eq!(vars[0].value, "value");
246 assert_eq!(vars[1].key, "ALSO_GOOD");
247 assert_eq!(vars[1].value, "ok");
248 }
249
250 #[test]
251 fn parse_env_region_empty() {
252 let vars = parse_env_region(&[], 1, "test");
253 assert!(vars.is_empty());
254 }
255
256 #[test]
257 fn missing_init_task_symbol() {
258 let isf = IsfBuilder::new()
259 .add_struct("task_struct", 64)
260 .add_field("task_struct", "pid", 0, "int")
261 .add_field("task_struct", "tasks", 8, "list_head")
262 .add_struct("list_head", 16)
263 .add_field("list_head", "next", 0, "pointer")
264 .add_field("list_head", "prev", 8, "pointer")
265 .build_json();
266
267 let resolver = IsfResolver::from_value(&isf).unwrap();
268 let (cr3, mem) = PageTableBuilder::new().build();
269 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
270 let reader = ObjectReader::new(vas, Box::new(resolver));
271
272 let result = walk_envvars(&reader);
273 assert!(
274 matches!(result, Err(crate::Error::MissingKernelSymbol { ref name }) if name == "init_task"),
275 "expected MissingKernelSymbol {{name: \"init_task\"}}, got {result:?}"
276 );
277 }
278
279 #[test]
280 fn missing_tasks_field_returns_missing_field() {
281 let isf = IsfBuilder::new()
282 .add_struct("task_struct", 64)
283 .add_field("task_struct", "pid", 0, "int")
284 .add_struct("list_head", 16)
286 .add_field("list_head", "next", 0, "pointer")
287 .add_field("list_head", "prev", 8, "pointer")
288 .add_symbol("init_task", 0xFFFF_8000_0010_0000)
289 .build_json();
290 let resolver = IsfResolver::from_value(&isf).unwrap();
291 let (cr3, mem) = PageTableBuilder::new().build();
292 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
293 let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
294 let result = walk_envvars(&reader);
295 assert!(
296 matches!(result, Err(crate::Error::MissingField { ref struct_name, ref field_name }) if struct_name == "task_struct" && field_name == "tasks"),
297 "expected MissingField task_struct.tasks, got {result:?}"
298 );
299 }
300}