mrkeyoor.com_
Tue 01 Sept 17:45 UTC
Tech6 min read

Qubes Fixes a dom0 Command Injection Dating to 2015

A compromised destination qube could turn a file-copy error into code execution in dom0. The vulnerable shell call arrived with the tool in 2015.

The dangerous call had been sitting in Qubes OS's file-copy error dialog since November 2015. A compromised qube could send a crafted filename back during an outbound copy, and code in dom0 would place that name inside a shell command. Qubes disclosed the result on August 29: arbitrary command execution in its most trusted domain, enough to take control of the operating system.

That direction of travel makes Qubes Security Bulletin 118 unusual. The user is copying a file from dom0 into a less trusted virtual machine. The data appears to be moving away from the sensitive side. Yet the transfer protocol sends a status message in the opposite direction, and the old error handler treated part of that reply as safe enough for a shell.

The bulletin says every Qubes OS release is affected. Exploitation has two stated preconditions: an attacker must already control a qube, and the user must run qvm-copy-to-vm from dom0 with that compromised qube as the destination. The flaw does not turn any compromised virtual machine into an automatic dom0 escape. It turns one specific user action into an escape path.

A reply from the destination crossed the boundary

Qubes divides work into virtual machines called qubes. Its architecture documentation describes dom0 as the privileged domain, with no networking code and no user applications. Networking, USB handling and ordinary applications are pushed into less privileged qubes so that one compromise does not spread across the system.

qvm-copy-to-vm exists for the uncommon case in which a user needs to copy a file from dom0 to a qube. The official copying guide shows the command and says the file arrives under /home/user/QubesIncoming/dom0/ in the destination:

qvm-copy-to-vm <target_vm> <file>

Qubes uses a simplified archive format called qfile for the transfer. At the end, the target sends a confirmation containing a checksum, an error code and the name of the last file it received. If the target reports an error, dom0 displays a dialog with the error and filename. That small return message is where the isolation failed.

The source-side library did recognize that the filename was untrusted. Its sanitize_remote_filename() function replaced control characters, non-ASCII bytes and double quotation marks. It left shell metacharacters alone. The dom0 error handler then assembled a kdialog or zenity command as text and passed the result to system(), which invokes a shell. A malicious destination could therefore put shell syntax in the reported filename and have dom0 interpret it as a command.

This was a context error rather than an absence of filtering. The sanitizer produced printable text suitable for display, but the next function used that text as shell input. Printable is not the same property as inert inside a command string. The code moved data from a protocol field to a GUI message and, finally, into a command interpreter. Each step looked local; together they crossed the system's strongest trust boundary.

The shell was the unnecessary part

The relevant old and new behavior can be reduced to two calls:

/* vulnerable: dialog text is parsed by a shell */
system(dialog_cmd);

/* fixed: the message is passed as one argument */
execlp("kdialog", "kdialog", "--title", title, "--sorry", msg, NULL);

The August 29 fix stops constructing one command string. The process forks and calls kdialog or zenity directly with execlp(), so the filename remains data in a single argument. No shell gets a chance to parse metacharacters. The patch changes one file, adding 33 lines and deleting 11. Most of the extra code handles process creation and reaps the child process after the dialog exits.

Qubes points out that the VM-side version of the copy tool was already safe from this flaw. That implementation drops privileges when necessary and calls the dialog program with execlp() instead of system(). The vulnerable dom0 path had a safer counterpart in the same codebase, but the two error reporters handled process execution differently.

The repository history gives the bug a long tail. The original November 2015 implementation added qvm-copy-to-vm, the dom0 agent and the system(dialog_cmd) pattern in the same commit. Qubes says all releases are affected; the public history shows the risky call was present from the tool's introduction. Tim C. found the vulnerability and supplied the basis for the patch, according to the bulletin and commit credit.

That history is useful for developers working outside Qubes too. Escaping a string for one output context does not make it safe for another. A filename cleaned for a notification may still be hostile to a shell, SQL parser, HTML renderer or log processor. The tighter fix is to avoid the interpreter when the job only requires launching a program with arguments.

Why the preconditions still matter

Arbitrary code execution in dom0 is the worst stated outcome in Qubes' security model, but the attack chain is constrained. The bulletin requires prior compromise of the destination qube. It also requires the user to initiate a copy from dom0 into that same qube. Normal file copying between app qubes is outside the affected path, and the VM variant's error reporter does not use the vulnerable shell call.

Those limits affect exposure without erasing the problem. A compromised qube is exactly the condition Qubes is designed to contain. Users may reasonably expect sending a benign file out of dom0 to expose the destination to that file, rather than expose dom0 to a reply controlled by the destination. The checksum and status fields are part of completing the transfer, but they are still messages from an untrusted machine.

The flaw also sits in an error path. Successful transfers do not need to show the vulnerable dialog. An attacker controlling the target can deliberately return a nonzero error code and choose the last-filename field, according to the protocol flow described in QSB-118. Error handling deserves the same boundary checks as the main operation because a hostile peer can often select the failure path on demand.

Qubes' documentation discourages copying data in the opposite direction, from a qube into dom0, because dom0 is meant to remain a thin trusted terminal. This vulnerability did not require that explicitly discouraged workflow. It used the supported command for copying out of dom0 and exploited the destination's confirmation message. The lesson is narrower and more demanding than a blanket warning about file transfers: every response from a less trusted compartment has to remain untrusted, even when the privileged side initiated the operation.

The update is moving through Qubes' repositories

QSB-118 lists qubes-core-dom0-linux version 4.3.22 as the corrected package for Qubes 4.3. At publication, the package was in the security-testing repository and was due to move to the current stable repository after a short community testing period. The bulletin tells users to continue updating normally and says no other action is required.

The Qubes update guide recommends the graphical Qubes Update tool or its command-line equivalents. It warns against treating direct dnf update or apt update commands as substitutes because those bypass Qubes' update protections. For this issue, the bulletin does not instruct users to enable testing repositories, restart a named component or apply a manual workaround.

Users should therefore check the signed bulletin and their normal update channel rather than copy an improvised installation command from secondary coverage. The bulletin includes two OpenPGP signatures and links to Qubes' authentication instructions. That matters for an operating system whose security notices can tell people to install privileged code in dom0.

The next facts to watch are operational: when version 4.3.22 reaches the stable repository, whether Qubes publishes packages for any other supported release channels, and whether the project adds tests that feed shell metacharacters through qfile error replies. The code change closes the known path by removing the shell. Its lasting value will depend on keeping untrusted protocol fields out of interpreters elsewhere in the dom0 toolchain.

We reviewed this

  1. terminal — our honest review

Sources

  1. QSB-118: Dom0 arbitrary code execution in qvm-copy-to-vm error reporting
  2. Qubes OS patch: Do not use system() with untrusted parts of the command
  3. Original qvm-copy-to-vm implementation
  4. Qubes OS architecture
  5. How to copy from dom0
  6. How to update Qubes OS