I have a question regarding the security of storing a VM’s output into a dom0 variable.
I wrote a shell script in dom0 to automate my backup process. It interacts with a backup VM (sys-backup) and a USB VM (sys-usb) with a YubiKey. To make the automation work, the script captures the command output from the VM (like encryption keys or backup targets) and stores it into a dom0 variable, which is then passed as an argument to subsequent dom0 backup commands (like qvm-backup).
However, I am feeling a bit anxious about this approach. Conceptually, storing a VM’s output into a dom0 variable feels almost identical to copy-pasting untrusted data directly into dom0. If the VM were to be compromised, this could potentially open the door to a command injection attack or shell exploitation in dom0.
My questions are:
Is this variable assignment pattern inherently dangerous in Qubes OS?
Since I absolutely need these variables to run dom0-specific backup commands, is there any way to make this process safer?
How can I properly sanitize or validate the VM’s output within dom0 before storing it as a variable to prevent any malicious shell execution?
foo="$(qvm-run -p ...)" is not inherently dangerous. (Assumption: no ancient, exploitable pipe → buffer bug in bash.)
You could write a simple qrexec API with strict constraints on arguments and input/output (if you write the API that way) to do a safer, checked equivalent of qvm-run. You could set up /etc/qubes/policy.d permissions to offload some of the work to an intermediary “backup management” VM, for less direct involvement of dom0.
After doing foo="$(qvm-run -p ...)", "$foo" is just a string. Always keep it quoted. grep bar <<< "$foo" is safe. [[ "$foo" =~ bar ]] is safe, stuff like "${foo/baz/qux}" is safe. Be careful about using it in a context where it could be interpreted as a command flag, e.g. echo "$foo" | ... is a little suss.
Each time I found myself in that situation, there was a way to avoid the variable issue. Here you can put your backup in a specific directory or make sys-backup move the file?
Reading arbitrary bytes from a lesser trusted context into a Bash variable in a higher trusted one (especially dom0) does already feel a bit iffy to me, even before considering how to use that variable later.
I don’t have a specific Bash vulnerability in mind, but here’s some paranoid thinking for example: Bash has the language construct ${#var}, which returns the length of the contents of var. That length is not about bytes but characters, so Bash must do character set related parsing - dealing with malformed Unicode and other horrors. Does it do all that only when the ${#var} construct is actually used, or is the length immediately precomputed upon reading into var? I hope it’s the former, but this seems like the kind of implementation detail that could be changed at any point for performance reasons. (Bash releases tend to be pretty casual about much more significant changes.)
To make reading untrusted data into a Bash variable safer, I use qvm-run --filter-escape-chars (or for VM-to-VM calls: qrexec-client-vm -t) to restrict the output to a subset of ASCII. If an even narrower set of characters is sufficient, I might pipe that output into tr, e.g. to throw away everything except numbers and lowercase letters: qvm-run --filter-escape-chars ... | LC_ALL=C tr -dc 0-9a-z. Maybe also append e.g. | head -c 1M to prevent the VM from sending gigabytes of data and exhausting all memory.
Exactly, every piece of data you avoid passing across a VM boundary is one that you don’t have to worry about sanitizing.
It’s possible to pass a command instead of a filename to qvm-backup and qvm-backup-restore. You could use that to generate the backup’s filename in dom0, then you don’t have to query it from sys-backup at all: