Skip to content

What sandboxing does and does not give you

A sandbox in mcqemu is a QEMU virtual machine with two defaults changed: its disk is a copy-on-write overlay over a base image, and its outbound networking is switched off. sandbox_vm does the overlay, the launch, and the wait for the guest agent in one call; sandbox_destroy tears the whole thing down. That is the entire mechanism. Everything below is about what those two defaults buy you and where they stop.

The isolation that matters here is not something mcqemu implements. It is the fact that a guest runs its own kernel on emulated hardware. Guest processes make syscalls into the guest kernel, which drives virtual devices, which QEMU implements in userspace on the host. Nothing in the guest addresses host memory, host processes, or host files, because there is no path from guest userspace to those things that does not go through the device model.

That is a much stronger position than a container, where the guest and the host share one kernel and isolation is a matter of namespaces and seccomp filters being configured correctly. If you are running software you have reason to distrust, the kernel boundary is the reason to reach for a VM.

The cost is that you pay for it. A VM boots a whole operating system, wants its own memory, and (when the guest architecture does not match the host) runs under TCG emulation, which is correct but slow.

The overlay: the base image is never written

Section titled “The overlay: the base image is never written”

sandbox_vm creates a qcow2 overlay whose backing file is your base image, and gives the VM only the overlay. Every write the guest makes lands in the overlay. The base image is opened read-only through the backing chain and comes out byte-identical no matter what happens inside.

This is what makes sandboxes cheap enough to be disposable. Creating one is writing a small file with a pointer to a big one, so a fresh sandbox from a 20 GB base costs kilobytes and a second or two. It is also what makes sandbox_destroy safe: it deletes the overlay and the VM’s state and runtime directories, and reports the base image path back under base_image_untouched so the answer is visible rather than assumed.

Two consequences worth knowing. The overlay is recreated fresh on every sandbox_vm call for a given name, so a stale overlay from a dead sandbox never resurrects old state. And because the base is genuinely in use while a sandbox runs, mcqemu counts it among the disks in use, so an attempt to modify the base underneath a running sandbox is refused.

If you want state to survive, take a snapshot (see Snapshots) or launch a normal VM against a real disk with launch_vm instead. Sandboxes are for work you intend to throw away. Disposable sandboxes walks through the loop.

sandbox_vm launches with QEMU’s user-mode networking (SLIRP) in restricted mode. Guest-initiated traffic is dropped. Inbound port forwards keep working, so the default forward from a free host port to guest port 22 is still how you get in, and guest_exec over the agent socket is unaffected because it never touches the network at all.

The reason this is the default is specific, and it is worth understanding rather than taking on faith. SLIRP synthesizes a small private network for the guest, and in that network the address 10.0.2.2 is the host’s loopback interface. A guest with unrestricted user-mode networking can therefore open connections to services listening on the host’s 127.0.0.1: a development database that never bothered with authentication because it only listens locally, an SSH agent forwarding socket, an unauthenticated Ollama or Redis, a docker API on a TCP port. The mental model of “it is only on localhost, so it is private” is exactly the model SLIRP breaks. restrict=on closes that path along with internet access.

When a guest legitimately needs to fetch packages, pass allow_network=True, and be aware you are re-opening the host loopback path at the same time. There is no setting that gives internet access while blocking 10.0.2.2, because SLIRP does not offer that distinction. If you need it, give the VM a real bridged or TAP interface and enforce the policy in your host firewall, which means starting QEMU yourself and registering it with attach_vm.

launch_vm takes the same restrict_net flag, plus no_net=True to remove the network card entirely. A VM with no NIC has no forwards either, so it is only reachable through the guest agent and the display.

Everything above is real. None of it makes this a hardened security boundary against a determined attacker.

QEMU escapes exist. The device model is a large body of C code parsing input that a hostile guest controls, and it has had exploitable bugs, some of them serious. mcqemu changes nothing about that surface. If your threat model includes an adversary willing to burn a QEMU escape on you, a mcqemu sandbox is not the control you want on its own. Real defense in depth for that case means running QEMU as a dedicated unprivileged user, on a host that holds nothing valuable, behind whatever seccomp and MAC policy your distribution offers, and not on your workstation.

The QEMU process runs as you. It has your file permissions, your network access, and your ability to write to your own home directory. The boundary being defended is “the guest cannot reach the host”, and it holds against the guest playing by the rules of the device model. It does not hold against an escape, because the thing that escapes lands with your privileges.

extra_args can weaken isolation. launch_vm accepts raw QEMU flags, because there will always be a machine type or device you need and no wrapper anticipated. That escape hatch is an operator tool. Values you wrote yourself are fine; values derived from anything untrusted are not, because a few flags turn a VM into a hole straight through to the host. Those are rejected outright:

RejectedWhy
-fsdev, -virtfsHost filesystem passthrough into the guest
-drive / -blockdev naming /dev/...Attaches a host block device
-chardev with spawnRuns a host command
-runasChanges the user the QEMU process runs as
-monitorExposes the human monitor outside the managed socket
-qmp, -pidfile, -daemonizeCollide with the sockets and process model mcqemu manages

Read that list as what it is: a guardrail against an agent constructing arguments from untrusted input, not a security perimeter. It is a fixed list of known-bad flags, not a proof that everything else is safe, and QEMU has a very large flag surface. An operator who genuinely wants host passthrough can start QEMU by hand and manage it with attach_vm, and nothing stops them, which is correct. The point of the check is that the path to weakening isolation should require a person deciding to do it.

The guest agent is root inside the guest, by design. guest_exec runs commands as the agent’s user, which is normally root, and guest_file_write will write anywhere the agent can reach. That is not a leak, it is the feature: you already control the VM’s power button, its disks and its display from the host, so declining to give you a shell would be theatre. What it does mean is that installing the guest agent is a decision about the guest, not a neutral convenience. Do not install it in a VM whose contents you are supposed to be analysing at arm’s length, and remember that a sandbox with the agent running has no meaningful defense against the host, only against the guest reaching out.

Protected: your host filesystem from guest processes, your host services on loopback (with the default restricted networking), your base images from any guest writes, and your host kernel from guest syscalls.

Not protected: anything, if QEMU itself is exploited; anything, if you pass isolation-weakening extra_args; the guest’s own contents from you, since the agent and the monitor give you full control; and the host loopback path, if you pass allow_network=True.

Use a sandbox to run software you have not read, to test an installer, to try a package that wants to touch your whole system, or to give an agent somewhere to make a mess. Do not use one as the only thing standing between you and code written specifically to attack you.