memflow_qemu/
lib.rs

1use log::{error, info};
2
3use memflow::cglue;
4use memflow::connector::cpu_state::*;
5use memflow::mem::memory_view::RemapView;
6use memflow::mem::phys_mem::*;
7use memflow::os::root::Os;
8use memflow::prelude::v1::*;
9
10mod qemu_args;
11use qemu_args::{is_qemu, qemu_arg_opt};
12
13#[cfg(all(target_os = "linux", feature = "qmp"))]
14#[macro_use]
15extern crate scan_fmt;
16
17mod mem_map;
18use mem_map::qemu_mem_mappings;
19
20cglue_impl_group!(QemuProcfs<P: MemoryView + Clone>, ConnectorInstance, {
21    ConnectorCpuState
22});
23cglue_impl_group!(QemuProcfs<P: MemoryView + Clone>, IntoCpuState);
24
25#[derive(Clone)]
26pub struct QemuProcfs<P: MemoryView> {
27    view: RemapView<P>,
28}
29
30impl<P: MemoryView + Process> QemuProcfs<P> {
31    pub fn new<O: Os<IntoProcessType = P>>(
32        mut os: O,
33        map_override: Option<CTup2<Address, umem>>,
34    ) -> Result<Self> {
35        let mut proc = None;
36
37        let callback = &mut |info: ProcessInfo| {
38            if proc.is_none() && is_qemu(&info) {
39                proc = Some(info);
40            }
41
42            proc.is_none()
43        };
44
45        os.process_info_list_callback(callback.into())?;
46
47        Self::with_process(
48            os,
49            proc.ok_or_else(|| {
50                Error(ErrorOrigin::Connector, ErrorKind::TargetNotFound)
51                    .log_error("No QEMU process could be found. Is QEMU running?")
52            })?,
53            map_override,
54        )
55    }
56
57    pub fn with_guest_name<O: Os<IntoProcessType = P>>(
58        mut os: O,
59        name: &str,
60        map_override: Option<CTup2<Address, umem>>,
61    ) -> Result<Self> {
62        let mut proc = None;
63
64        let callback = &mut |info: ProcessInfo| {
65            if proc.is_none()
66                && is_qemu(&info)
67                && qemu_arg_opt(info.command_line.split_whitespace(), "-name", "guest").as_deref()
68                    == Some(name)
69            {
70                proc = Some(info);
71            }
72
73            proc.is_none()
74        };
75
76        os.process_info_list_callback(callback.into())?;
77
78        Self::with_process(
79            os,
80            proc.ok_or_else(||
81                Error(ErrorOrigin::Connector, ErrorKind::TargetNotFound)
82                    .log_error("A QEMU process for the specified guest name could not be found. Is the QEMU process running?")
83            )?,
84            map_override,
85        )
86    }
87
88    pub fn with_pid<O: Os<IntoProcessType = P>>(
89        mut os: O,
90        pid: Pid,
91        map_override: Option<CTup2<Address, umem>>,
92    ) -> Result<Self> {
93        let proc = os.process_info_by_pid(pid)?;
94
95        Self::with_process(os, proc, map_override)
96    }
97
98    fn with_process<O: Os<IntoProcessType = P>>(
99        os: O,
100        info: ProcessInfo,
101        map_override: Option<CTup2<Address, umem>>,
102    ) -> Result<Self> {
103        info!(
104            "qemu process with name {} found with pid {:?}",
105            info.name, info.pid
106        );
107
108        let cmdline: String = info.command_line.to_string();
109
110        let mut prc = os.into_process_by_info(info)?;
111
112        let mut biggest_map = map_override;
113
114        let callback = &mut |range: MemoryRange| {
115            if biggest_map
116                .map(|CTup2(_, oldsize)| oldsize < range.1)
117                .unwrap_or(true)
118            {
119                biggest_map = Some(CTup2(range.0, range.1));
120            }
121
122            true
123        };
124
125        if map_override.is_none() {
126            prc.mapped_mem_range(
127                smem::mb(-1),
128                Address::NULL,
129                Address::INVALID,
130                callback.into(),
131            );
132        }
133
134        let qemu_map = biggest_map.ok_or_else(|| Error(ErrorOrigin::Connector, ErrorKind::NotFound)
135            .log_error("Unable to find the QEMU guest memory map. This usually indicates insufficient permissions to acquire the QEMU memory maps. Are you running with appropiate access rights?")
136        )?;
137
138        info!("qemu memory map found {:?}", qemu_map);
139
140        Self::with_cmdline_and_mem(prc, &cmdline, qemu_map)
141    }
142
143    fn with_cmdline_and_mem(prc: P, cmdline: &str, qemu_map: CTup2<Address, umem>) -> Result<Self> {
144        let mem_map = qemu_mem_mappings(cmdline, &qemu_map)?;
145        info!("qemu machine mem_map: {:?}", mem_map);
146
147        Ok(Self {
148            view: prc.into_remap_view(mem_map),
149        })
150    }
151}
152
153impl<P: MemoryView> PhysicalMemory for QemuProcfs<P> {
154    fn phys_read_raw_iter(
155        &mut self,
156        MemOps { inp, out, out_fail }: PhysicalReadMemOps,
157    ) -> Result<()> {
158        let inp = inp.map(|CTup3(addr, meta_addr, data)| CTup3(addr.into(), meta_addr, data));
159        MemOps::with_raw(inp, out, out_fail, |data| self.view.read_raw_iter(data))
160    }
161
162    fn phys_write_raw_iter(
163        &mut self,
164        MemOps { inp, out, out_fail }: PhysicalWriteMemOps,
165    ) -> Result<()> {
166        let inp = inp.map(|CTup3(addr, meta_addr, data)| CTup3(addr.into(), meta_addr, data));
167        MemOps::with_raw(inp, out, out_fail, |data| self.view.write_raw_iter(data))
168    }
169
170    fn metadata(&self) -> PhysicalMemoryMetadata {
171        let md = self.view.metadata();
172
173        PhysicalMemoryMetadata {
174            max_address: md.max_address,
175            real_size: md.real_size,
176            readonly: md.readonly,
177            ideal_batch_size: 4096,
178        }
179    }
180}
181
182impl<P: MemoryView + 'static> ConnectorCpuState for QemuProcfs<P> {
183    type CpuStateType<'a> = Fwd<&'a mut QemuProcfs<P>>;
184    type IntoCpuStateType = QemuProcfs<P>;
185
186    fn cpu_state(&mut self) -> Result<Self::CpuStateType<'_>> {
187        Ok(self.forward_mut())
188    }
189
190    fn into_cpu_state(self) -> Result<Self::IntoCpuStateType> {
191        Ok(self)
192    }
193}
194
195impl<P: MemoryView> CpuState for QemuProcfs<P> {
196    fn pause(&mut self) {}
197
198    fn resume(&mut self) {}
199}
200
201fn validator() -> ArgsValidator {
202    ArgsValidator::new()
203        .arg(ArgDescriptor::new("map_base").description("override of VM memory base"))
204        .arg(ArgDescriptor::new("map_size").description("override of VM memory size"))
205}
206
207/// Creates a new Qemu Procfs instance.
208#[connector(
209    name = "qemu",
210    help_fn = "help",
211    target_list_fn = "target_list",
212    accept_input = true,
213    return_wrapped = true
214)]
215fn create_plugin(
216    args: &ConnectorArgs,
217    os: Option<OsInstanceArcBox<'static>>,
218    lib: LibArc,
219) -> Result<ConnectorInstanceArcBox<'static>> {
220    let os = os.map(Result::Ok).unwrap_or_else(|| {
221        memflow_native::create_os(
222            &Default::default(),
223            Option::<std::sync::Arc<_>>::None.into(),
224        )
225    })?;
226
227    let qemu = create_connector_with_os(args, os)?;
228    Ok(memflow::plugins::connector::create_instance(
229        qemu, lib, args, false,
230    ))
231}
232
233pub fn create_connector(
234    args: &ConnectorArgs,
235) -> Result<QemuProcfs<IntoProcessInstanceArcBox<'static>>> {
236    create_connector_with_os(
237        args,
238        memflow_native::create_os(
239            &Default::default(),
240            Option::<std::sync::Arc<_>>::None.into(),
241        )?,
242    )
243}
244
245pub fn create_connector_with_os<O: Os>(
246    args: &ConnectorArgs,
247    os: O,
248) -> Result<QemuProcfs<O::IntoProcessType>> {
249    let validator = validator();
250
251    let name = args.target.as_deref();
252
253    let args = &args.extra_args;
254
255    let qemu = match validator.validate(args) {
256        Ok(_) => {
257            let map_override = args
258                .get("map_base")
259                .and_then(|base| umem::from_str_radix(base, 16).ok())
260                .zip(
261                    args.get("map_size")
262                        .and_then(|size| umem::from_str_radix(size, 16).ok()),
263                )
264                .map(|(start, size)| CTup2(Address::from(start), size));
265
266            if let Some(name) = name.or_else(|| args.get("name")) {
267                if let Ok(pid) = Pid::from_str_radix(name, 10) {
268                    QemuProcfs::with_pid(os, pid, map_override)
269                } else {
270                    QemuProcfs::with_guest_name(os, name, map_override)
271                }
272            } else {
273                QemuProcfs::new(os, map_override)
274            }
275        }
276        Err(err) => {
277            error!(
278                "unable to validate provided arguments, valid arguments are:\n{}",
279                validator
280            );
281            Err(err)
282        }
283    }?;
284
285    Ok(qemu)
286}
287
288/// Retrieve the help text for the Qemu Procfs Connector.
289pub fn help() -> String {
290    let validator = validator();
291    format!(
292        "\
293The `qemu` connector implements a memflow plugin interface
294for QEMU on top of the Process Filesystem on Linux.
295
296This connector requires access to the qemu process via the linux procfs.
297This means any process which loads this connector requires
298to have at least ptrace permissions set.
299
300The `target` argument specifies the target qemu virtual machine.
301The qemu virtual machine name can be specified when starting qemu with the -name flag.
302
303Alternatively, if `target` is a number, qemu process by PID will be accessed.
304
305Available arguments are:
306{validator}"
307    )
308}
309
310/// Retrieve a list of all currently available Qemu targets.
311pub fn target_list() -> Result<Vec<TargetInfo>> {
312    let mut os = memflow_native::create_os(
313        &Default::default(),
314        Option::<std::sync::Arc<_>>::None.into(),
315    )?;
316
317    let mut out = vec![];
318
319    let callback = &mut |info: ProcessInfo| {
320        if is_qemu(&info) {
321            if let Some(n) = qemu_arg_opt(info.command_line.split_whitespace(), "-name", "guest") {
322                out.push(TargetInfo {
323                    name: ReprCString::from(n),
324                });
325            }
326        }
327
328        true
329    };
330
331    os.process_info_list_callback(callback.into())?;
332
333    Ok(out)
334}