⏱️ Lectura: 8 min
An infected qube can, with a single failed attempt to copy a file, end up executing an arbitrary command in Qubes OS’s administrative domain. That’s how the project’s security team summarizes the flaw it fixed on August 28, 2026 with bulletin QSB-118.
📑 En este artículo
The problem isn’t in the file copy itself, but in how dom0 reports an error when that copy fails: the filename returned by the destination qube ends up, not fully filtered, inside a shell command that runs with dom0 privileges.
TL;DR
- QSB-118, published on August 28, 2026, affects all versions of Qubes OS
- A compromised qube can inject an arbitrary command into dom0 via qvm-copy-to-vm
- The flaw lies in sanitize_remote_filename(), which doesn’t filter all shell metacharacters
- display_error(), in qfile-dom0-agent.c, builds the error dialog with system() without fully sanitizing the name
- The qube-side variant isn’t vulnerable because it uses execlp() instead of system()
- The patch ships in the qubes-core-dom0-linux package, version 4.3.22, for Qubes 4.3
- The vulnerability was reported by a researcher identified as Tim C.
- Updating with the Qubes Update tool is enough; no extra manual action is needed
What happened
The QSB-118 bulletin describes a concrete scenario: if a user runs qvm-copy-to-vm from dom0 to send a file to a qube that’s already compromised, that qube can return a manipulated response that injects an arbitrary command into dom0. The result is full control over Qubes OS, since dom0 is the domain that administers the rest of the system.
The bulletin itself clarifies that no special user action is required beyond updating normally: the fix is already packaged and available through the usual update channels.
Background
Qubes OS is based on compartmentalizing the system into multiple qubes (lightweight virtual machines) isolated from each other, while dom0 remains the privileged domain that administers the GUI, storage, and the rest of the qubes. If dom0 is compromised, the whole system is compromised; that’s why the project treats any code path that exposes dom0 to untrusted data as critical attack surface.
One of those contact points is qvm-copy-to-vm, which uses the qfile protocol: a simplified file format, simpler than tar or cpio, designed to transfer files between dom0 and a qube. At the end of the transfer, the destination sends dom0 a confirmation with a checksum, an error code (if any), and the name of the last file received. That last field, controlled by the destination qube, is exactly the one dom0 wasn’t fully sanitizing before displaying it in an error dialog.
Technical Details and Performance
The flaw’s chain has two links. The first is sanitize_remote_filename(), in linux-utils/qrexec-lib/pack.c, which is supposed to clean the filename before displaying it:
static void sanitize_remote_filename(char *untrusted_filename)
{
for (; *untrusted_filename; ++untrusted_filename) {
if (*untrusted_filename < ' ' ||
*untrusted_filename > '~' ||
*untrusted_filename == '"')
*untrusted_filename = '_';
}
}
This function only replaces non-printable characters, non-ASCII characters, and double quotes with underscores. Everything else, including single quotes, semicolons, $() signs, and pipes, passes through untouched.
The second link is in core-admin-linux/file-copy-vm/qfile-dom0-agent.c. The display_error() function builds the error dialog command with asprintf() using a format like "%s '%s: %s (error type: %s)'", where the full message (which includes the already “sanitized” filename) gets wrapped in single quotes, and that command is executed with system(), which passes it to a shell.
That’s the real hole: since sanitize_remote_filename() never touches the single-quote character (0x27), a filename that includes one breaks the quoting context that asprintf() builds and exposes the rest of the text to the shell interpreter as executable code.
# Filename returned by the compromised qube (illustrative):
malicious_name="x'; touch /tmp/dom0-owned; echo '"
# sanitize_remote_filename() doesn't touch the single quote, so
# that value reaches display_error() intact and breaks the quoted
# string passed to system(), injecting the command
# 'touch /tmp/dom0-owned' with dom0 privileges.
⚠️ Heads up: building a shell command with asprintf() and interpolating untrusted data, even when wrapped in quotes, is still dangerous if the sanitization doesn’t cover exactly the characters that delimit those quotes.
The variant that runs on the qube side, in core-agent-linux/qubes-rpc/gui-fatal.c, doesn’t suffer from this problem. Instead of building a string and passing it through system(), it calls fork() and then execlp("/usr/bin/zenity", ...) directly, without invoking an intermediate shell that could reinterpret metacharacters.
| Variant | Error function | Vulnerable? | Why |
|---|---|---|---|
| Dom0 (qfile-dom0-agent.c) | display_error() with system() | Yes | Builds a string with the filename and passes it to a shell via system() |
| Destination qube (gui-fatal.c) | produce_message() with execlp() | No | Runs zenity or kdialog directly with execlp(), without an intermediate shell |
sequenceDiagram
participant Q as Compromised qube
participant D as Dom0
Q->>D: qfile confirmation with malicious filename
D->>D: sanitize_remote_filename filters only non-ASCII and double quotes
D->>D: display_error builds the dialog with asprintf and system
D-->>Q: dom0's shell executes the injected command
Note over Q,D: the single quote breaks the quoting context
How to Protect Yourself and Verify the Patch
The fix is in the qubes-core-dom0-linux package, version 4.3.22, for Qubes 4.3. At the time of the bulletin, the package was in the security-testing repository before migrating to the stable repository.
# Update dom0 including the security-testing repo
sudo qubes-dom0-update --enablerepo=security-testing
# Check the installed version
rpm -q qubes-core-dom0-linux
That second command should return something like qubes-core-dom0-linux-4.3.22-1.fc37 or later. If you prefer the graphical interface, the Qubes Update tool (accessible from the dom0 menu) applies the same package without touching the terminal.
Once the package moves from security-testing to the stable repository, a normal update (sudo qubes-dom0-update, with no extra flags) is enough to receive it.
Impact and Analysis
This flaw is what’s usually called a compromise escalator: on its own it doesn’t give an attacker access to anything, because it requires the destination qube to already be compromised beforehand. The trigger is the user themselves, from dom0, deciding to copy a file to that qube with qvm-copy-to-vm.
That detail matters for understanding Qubes OS’s design: the compartment isolation model assumes that individual qubes can be compromised without that affecting the rest of the system, as long as the channels between dom0 and the qubes are well shielded. QSB-118 is exactly the kind of breach that model seeks to eliminate: a legitimate channel (copying a file) that ends up leaking control to dom0.
💭 Key point: in a system designed so that no individual qube can touch dom0, any error path that ends up executing code with dom0 privileges undoes part of that guarantee.
The bulletin notes that all versions of Qubes OS are affected, confirming that the code pattern (incomplete sanitization plus system()) had been present in qvm-copy-to-vm‘s codebase for a while.
What’s Next
The immediate next step is migrating the qubes-core-dom0-linux 4.3.22 package from security-testing to the stable repository, after the usual community testing period. For those managing Qubes OS installations in production, it’s worth reviewing whether other parts of the codebase reuse the same asprintf() plus system() pattern to build commands with data coming from a qube, since that’s exactly the antipattern that enabled QSB-118.
Credit for the finding goes to the researcher identified as Tim C., according to the bulletin itself, which doesn’t specify whether an associated bug bounty program was involved.
📖 Summary on Telegram: View summary
Try it yourself: run sudo qubes-dom0-update --enablerepo=security-testing on your dom0 and confirm with rpm -q qubes-core-dom0-linux that you already have version 4.3.22.
Frequently Asked Questions
What exactly is QSB-118?
It’s Qubes Security Bulletin 118, published on August 28, 2026, which describes an arbitrary code execution vulnerability in dom0 through qvm-copy-to-vm‘s error handling.
Do I need to do anything besides updating dom0?
No. The bulletin is explicit: continuing to update normally is enough to receive the fixed package; no additional manual action is needed.
Why isn’t the qube-side copy vulnerable but the dom0 one is?
Because the qube’s error handler uses execlp() to launch zenity or kdialog directly, while dom0’s builds a string and runs it with system(), which does go through an interpretable shell.
How do I confirm my Qubes OS already has the patch?
By running rpm -q qubes-core-dom0-linux on dom0: the installed version should be 4.3.22 or later.
Does this vulnerability require an attacker already in my system?
Yes. It requires a qube to already be compromised and the user to run qvm-copy-to-vm from dom0 toward that specific qube; it isn’t remotely exploitable without that prior step.
References
- Qubes Security Bulletin 118: the official bulletin with the full technical analysis and cryptographic signatures
- How to Update Qubes OS: official guide for applying updates to dom0 and the qubes
- Qubes OS Testing Repositories: how to enable security-testing to receive patches before they reach stable
- QubesOS Organization on GitHub: the project’s source repositories, including linux-utils and core-admin-linux
📱 Like this content? Follow @programacion on Telegram for daily tech content in Spanish: quick summaries, fresh content every day.
0 Comments