memflow_qemu_procfs/
lib.rs

1use log::info;
2
3use memflow::*;
4use memflow_derive::connector;
5
6use core::ffi::c_void;
7use libc::{c_ulong, iovec, pid_t, sysconf, _SC_IOV_MAX};
8
9#[derive(Clone, Copy)]
10#[repr(transparent)]
11struct IoSendVec(iovec);
12
13unsafe impl Send for IoSendVec {}
14
15fn qemu_arg_opt(args: &[String], argname: &str, argopt: &str) -> Option<String> {
16    for (idx, arg) in args.iter().enumerate() {
17        if arg == argname {
18            let name = args[idx + 1].split(',');
19            for (i, kv) in name.clone().enumerate() {
20                let kvsplt = kv.split('=').collect::<Vec<_>>();
21                if kvsplt.len() == 2 {
22                    if kvsplt[0] == argopt {
23                        return Some(kvsplt[1].to_string());
24                    }
25                } else if i == 0 {
26                    return Some(kv.to_string());
27                }
28            }
29        }
30    }
31
32    None
33}
34
35#[derive(Clone)]
36pub struct QemuProcfs {
37    pub pid: pid_t,
38    pub mem_map: MemoryMap<(Address, usize)>,
39    temp_iov: Box<[IoSendVec]>,
40}
41
42impl QemuProcfs {
43    pub fn new() -> Result<Self> {
44        let prcs = procfs::process::all_processes()
45            .map_err(|_| Error::Connector("unable to list procfs processes"))?;
46        let prc = prcs
47            .iter()
48            .find(|p| p.stat.comm == "qemu-system-x86")
49            .ok_or_else(|| Error::Connector("qemu process not found"))?;
50        info!("qemu process found with pid {:?}", prc.stat.pid);
51
52        Self::with_process(prc)
53    }
54
55    pub fn with_guest_name(name: &str) -> Result<Self> {
56        let prcs = procfs::process::all_processes()
57            .map_err(|_| Error::Connector("unable to list procefs processes"))?;
58        let (prc, _) = prcs
59            .iter()
60            .filter(|p| p.stat.comm == "qemu-system-x86")
61            .filter_map(|p| {
62                if let Ok(c) = p.cmdline() {
63                    Some((p, c))
64                } else {
65                    None
66                }
67            })
68            .find(|(_, c)| qemu_arg_opt(c, "-name", "guest").unwrap_or_default() == name)
69            .ok_or_else(|| Error::Connector("qemu process not found"))?;
70        info!(
71            "qemu process with name {} found with pid {:?}",
72            name, prc.stat.pid
73        );
74
75        Self::with_process(prc)
76    }
77
78    fn with_process(prc: &procfs::process::Process) -> Result<Self> {
79        // find biggest memory mapping in qemu process
80        let mut maps = prc
81            .maps()
82            .map_err(|_| Error::Connector("unable to get qemu memory maps"))?;
83        maps.sort_by(|b, a| {
84            (a.address.1 - a.address.0)
85                .partial_cmp(&(b.address.1 - b.address.0))
86                .unwrap()
87        });
88        let map = maps
89            .get(0)
90            .ok_or_else(|| Error::Connector("qemu memory map could not be read"))?;
91        info!("qemu memory map found {:?}", map);
92
93        let map_base = map.address.0 as usize;
94        let map_size = (map.address.1 - map.address.0) as usize;
95        info!("qemu memory map size: {:x}", map_size);
96
97        // TODO: instead of hardcoding the memory regions per machine we could just use the hmp to retrieve the proper ranges:
98        // sudo virsh qemu-monitor-command win10 --hmp 'info mtree -f' | grep pc\.ram
99
100        // find machine architecture
101        let machine = qemu_arg_opt(
102            &prc.cmdline()
103                .map_err(|_| Error::Connector("unable to parse qemu arguments"))?,
104            "-machine",
105            "type",
106        )
107        .unwrap_or_else(|| "pc".into());
108        info!("qemu process started with machine: {}", machine);
109
110        let mut mem_map = MemoryMap::new();
111        if machine.contains("q35") {
112            // q35 -> subtract 2GB
113            /*
114            0000000000000000-000000000009ffff (prio 0, ram): pc.ram KVM
115            00000000000c0000-00000000000c3fff (prio 0, rom): pc.ram @00000000000c0000 KVM
116            0000000000100000-000000007fffffff (prio 0, ram): pc.ram @0000000000100000 KVM
117            0000000100000000-000000047fffffff (prio 0, ram): pc.ram @0000000080000000 KVM
118            */
119            // we add all regions additionally shifted to the proper qemu memory map address
120            mem_map.push_range(Address::NULL, size::kb(640).into(), map_base.into()); // section: [start - 640kb] -> map to start
121                                                                                      // If larger than this specific size, second half after 2 gigs gets moved over past 4gb
122                                                                                      // TODO: Probably the same happens with i1440-fx
123            if map_size >= size::mb(2816) {
124                mem_map.push_range(
125                    size::mb(1).into(),
126                    size::gb(2).into(),
127                    (map_base + size::mb(1)).into(),
128                ); // section: [1mb - 2gb] -> map to 1mb
129                mem_map.push_range(
130                    size::gb(4).into(),
131                    (map_size + size::gb(2)).into(),
132                    (map_base + size::gb(2)).into(),
133                ); // section: [4gb - max] -> map to 2gb
134            } else {
135                mem_map.push_range(
136                    size::mb(1).into(),
137                    map_size.into(),
138                    (map_base + size::mb(1)).into(),
139                ); // section: [1mb - max] -> map to 1mb
140            }
141        } else {
142            // pc-i1440fx
143            /*
144            0000000000000000-00000000000bffff (prio 0, ram): pc.ram KVM
145            00000000000c0000-00000000000cafff (prio 0, rom): pc.ram @00000000000c0000 KVM
146            00000000000cb000-00000000000cdfff (prio 0, ram): pc.ram @00000000000cb000 KVM
147            00000000000ce000-00000000000e7fff (prio 0, rom): pc.ram @00000000000ce000 KVM
148            00000000000e8000-00000000000effff (prio 0, ram): pc.ram @00000000000e8000 KVM
149            00000000000f0000-00000000000fffff (prio 0, rom): pc.ram @00000000000f0000 KVM
150            0000000000100000-00000000bfffffff (prio 0, ram): pc.ram @0000000000100000 KVM
151            0000000100000000-000000023fffffff (prio 0, ram): pc.ram @00000000c0000000 KVM
152            */
153            mem_map.push_range(Address::NULL, size::kb(768).into(), map_base.into()); // section: [start - 768kb] -> map to start
154            mem_map.push_range(
155                size::kb(812).into(),
156                size::kb(824).into(),
157                (map_base + size::kb(812)).into(),
158            ); // section: [768kb - 812kb] -> map to 768kb
159            mem_map.push_range(
160                size::kb(928).into(),
161                size::kb(960).into(),
162                (map_base + size::kb(928)).into(),
163            ); // section: [928kb - 960kb] -> map to 928kb
164            mem_map.push_range(
165                size::mb(1).into(),
166                size::gb(3).into(),
167                (map_base + size::mb(1)).into(),
168            ); // section: [1mb - 3gb] -> map to 1mb
169            mem_map.push_range(
170                size::gb(4).into(),
171                (map_size + size::gb(1)).into(),
172                (map_base + size::gb(3)).into(),
173            ); // section: [4gb - max] -> map to 3gb
174        }
175        info!("qemu machine mem_map: {:?}", mem_map);
176
177        let iov_max = unsafe { sysconf(_SC_IOV_MAX) } as usize;
178
179        Ok(Self {
180            pid: prc.stat.pid,
181            mem_map,
182            temp_iov: vec![
183                IoSendVec {
184                    0: iovec {
185                        iov_base: std::ptr::null_mut::<c_void>(),
186                        iov_len: 0
187                    }
188                };
189                iov_max * 2
190            ]
191            .into_boxed_slice(),
192        })
193    }
194
195    fn fill_iovec(addr: &Address, data: &[u8], liov: &mut IoSendVec, riov: &mut IoSendVec) {
196        let iov_len = data.len();
197
198        liov.0 = iovec {
199            iov_base: data.as_ptr() as *mut c_void,
200            iov_len,
201        };
202
203        riov.0 = iovec {
204            iov_base: addr.as_u64() as *mut c_void,
205            iov_len,
206        };
207    }
208
209    fn vm_error() -> Error {
210        match unsafe { *libc::__errno_location() } {
211            libc::EFAULT => Error::Connector("process_vm_readv failed: EFAULT (remote memory address is invalid)"),
212            libc::ENOMEM => Error::Connector("process_vm_readv failed: ENOMEM (unable to allocate memory for internal copies)"),
213            libc::EPERM => Error::Connector("process_vm_readv failed: EPERM (insifficient permissions to access the target address space)"),
214            libc::ESRCH => Error::Connector("process_vm_readv failed: ESRCH (process not found)"),
215            libc::EINVAL => Error::Connector("process_vm_readv failed: EINVAL (invalid value)"),
216            _ => Error::Connector("process_vm_readv failed: unknown error")
217        }
218    }
219}
220
221impl PhysicalMemory for QemuProcfs {
222    fn phys_read_raw_list(&mut self, data: &mut [PhysicalReadData]) -> Result<()> {
223        let mem_map = &self.mem_map;
224        let temp_iov = &mut self.temp_iov;
225
226        let mut void = FnExtend::void();
227        let mut iter = mem_map.map_iter(
228            data.iter_mut()
229                .map(|PhysicalReadData(addr, buf)| (*addr, &mut **buf)),
230            &mut void,
231        );
232
233        let max_iov = temp_iov.len() / 2;
234        let (iov_local, iov_remote) = temp_iov.split_at_mut(max_iov);
235
236        let mut elem = iter.next();
237
238        let mut iov_iter = iov_local.iter_mut().zip(iov_remote.iter_mut()).enumerate();
239        let mut iov_next = iov_iter.next();
240
241        while let Some(((addr, _), out)) = elem {
242            let (cnt, (liov, riov)) = iov_next.unwrap();
243
244            Self::fill_iovec(&addr, out, liov, riov);
245
246            iov_next = iov_iter.next();
247            elem = iter.next();
248
249            if elem.is_none() || iov_next.is_none() {
250                if unsafe {
251                    libc::process_vm_readv(
252                        self.pid,
253                        iov_local.as_ptr().cast(),
254                        (cnt + 1) as c_ulong,
255                        iov_remote.as_ptr().cast(),
256                        (cnt + 1) as c_ulong,
257                        0,
258                    )
259                } == -1
260                {
261                    return Err(Self::vm_error());
262                }
263
264                iov_iter = iov_local.iter_mut().zip(iov_remote.iter_mut()).enumerate();
265                iov_next = iov_iter.next();
266            }
267        }
268
269        Ok(())
270    }
271
272    fn phys_write_raw_list(&mut self, data: &[PhysicalWriteData]) -> Result<()> {
273        let mem_map = &self.mem_map;
274        let temp_iov = &mut self.temp_iov;
275
276        let mut void = FnExtend::void();
277        let mut iter = mem_map.map_iter(data.iter().copied().map(<_>::from), &mut void);
278        //let mut iter = mem_map.map_iter(data.iter(), &mut FnExtend::new(|_|{}));
279
280        let max_iov = temp_iov.len() / 2;
281        let (iov_local, iov_remote) = temp_iov.split_at_mut(max_iov);
282
283        let mut elem = iter.next();
284
285        let mut iov_iter = iov_local.iter_mut().zip(iov_remote.iter_mut()).enumerate();
286        let mut iov_next = iov_iter.next();
287
288        while let Some(((addr, _), out)) = elem {
289            let (cnt, (liov, riov)) = iov_next.unwrap();
290
291            Self::fill_iovec(&addr, out, liov, riov);
292
293            iov_next = iov_iter.next();
294            elem = iter.next();
295
296            if elem.is_none() || iov_next.is_none() {
297                if unsafe {
298                    libc::process_vm_writev(
299                        self.pid,
300                        iov_local.as_ptr().cast(),
301                        (cnt + 1) as c_ulong,
302                        iov_remote.as_ptr().cast(),
303                        (cnt + 1) as c_ulong,
304                        0,
305                    )
306                } == -1
307                {
308                    return Err(Self::vm_error());
309                }
310
311                iov_iter = iov_local.iter_mut().zip(iov_remote.iter_mut()).enumerate();
312                iov_next = iov_iter.next();
313            }
314        }
315
316        Ok(())
317    }
318
319    fn metadata(&self) -> PhysicalMemoryMetadata {
320        PhysicalMemoryMetadata {
321            size: self
322                .mem_map
323                .as_ref()
324                .iter()
325                .last()
326                .map(|map| map.base().as_usize() + map.output().1)
327                .unwrap(),
328            readonly: false,
329        }
330    }
331}
332
333/// Creates a new Qemu Procfs Connector instance.
334#[connector(name = "qemu_procfs")]
335pub fn create_connector(args: &ConnectorArgs) -> Result<QemuProcfs> {
336    if let Some(name) = args.get("name").or_else(|| args.get_default()) {
337        QemuProcfs::with_guest_name(name)
338    } else {
339        QemuProcfs::new()
340    }
341}
342
343#[cfg(test)]
344mod tests {
345    use super::*;
346
347    #[test]
348    fn test_name() {
349        assert_eq!(
350            qemu_arg_opt(
351                &["-name".to_string(), "win10-test".to_string()],
352                "-name",
353                "guest"
354            ),
355            Some("win10-test".into())
356        );
357        assert_eq!(
358            qemu_arg_opt(
359                &[
360                    "-test".to_string(),
361                    "-name".to_string(),
362                    "win10-test".to_string()
363                ],
364                "-name",
365                "guest"
366            ),
367            Some("win10-test".into())
368        );
369        assert_eq!(
370            qemu_arg_opt(
371                &["-name".to_string(), "win10-test,arg=opt".to_string()],
372                "-name",
373                "guest"
374            ),
375            Some("win10-test".into())
376        );
377        assert_eq!(
378            qemu_arg_opt(
379                &["-name".to_string(), "guest=win10-test,arg=opt".to_string()],
380                "-name",
381                "guest"
382            ),
383            Some("win10-test".into())
384        );
385        assert_eq!(
386            qemu_arg_opt(
387                &["-name".to_string(), "arg=opt,guest=win10-test".to_string()],
388                "-name",
389                "guest"
390            ),
391            Some("win10-test".into())
392        );
393        assert_eq!(
394            qemu_arg_opt(
395                &["-name".to_string(), "arg=opt".to_string()],
396                "-name",
397                "guest"
398            ),
399            None
400        );
401    }
402
403    #[test]
404    fn test_machine() {
405        assert_eq!(
406            qemu_arg_opt(
407                &["-machine".to_string(), "q35".to_string()],
408                "-machine",
409                "type"
410            ),
411            Some("q35".into())
412        );
413        assert_eq!(
414            qemu_arg_opt(
415                &[
416                    "-test".to_string(),
417                    "-machine".to_string(),
418                    "q35".to_string()
419                ],
420                "-machine",
421                "type"
422            ),
423            Some("q35".into())
424        );
425        assert_eq!(
426            qemu_arg_opt(
427                &["-machine".to_string(), "q35,arg=opt".to_string()],
428                "-machine",
429                "type"
430            ),
431            Some("q35".into())
432        );
433        assert_eq!(
434            qemu_arg_opt(
435                &["-machine".to_string(), "type=pc,arg=opt".to_string()],
436                "-machine",
437                "type"
438            ),
439            Some("pc".into())
440        );
441        assert_eq!(
442            qemu_arg_opt(
443                &[
444                    "-machine".to_string(),
445                    "arg=opt,type=pc-i1440fx".to_string()
446                ],
447                "-machine",
448                "type"
449            ),
450            Some("pc-i1440fx".into())
451        );
452        assert_eq!(
453            qemu_arg_opt(
454                &["-machine".to_string(), "arg=opt".to_string()],
455                "-machine",
456                "type"
457            ),
458            None
459        );
460    }
461}