I spent a couple hours today debugging a bug in an older branch of
libvirt (0.10.2) - one where libvirt only knew how to parse version 2
qcow2 images. Alas, that version of libvirt can be coerced into reading
the header of a random file under the assumption that it is qcow2 data,
and where it tries to find the backing format information in the header
extensions without first validating that the file format actually
matches qcow2 (that is, it looks for header extensions starting at
offset 72 without first checking the version field (bytes 4-7) contains 2).
So far, we've been lucky: since libvirt is trying to parse 72-75 as a
version 2 extension header magic number, and the number is stored in big
endian format, we end up reading the first 4 bytes of the version 3
incompatible_features field, which so far are always 0 (we don't have
that many incompatible features yet), and that happens to be the magic
number for end of extension headers; and thus libvirt quit trying to
look for any further extension headers. Furthermore, since libvirt
already refuses to probe backing file format unless explicitly allowed
by user action (since mis-probing a raw file is CVE-worthy if done
automatically), the damage stopped there, and the worst I could do
(without intentionally bypassing the CVE safety valve) was fail a test
in the libvirt testsuite when using version 3 files. But it DOES mean
that libvirt fails to find the backing header format extension of a v3
file, even when one is present, because libvirt fails to start looking
at the right offset.
But it does raise a question: What happens if we eventually DO reach bit
32, so that bytes 72-75 is no longer all 0s and therefore no longer the
end-of-extension marker? It might be possible to write a file that has
one interpretation as a valid qcow2v3, while having a different
interpretation by the older libvirt, such that libvirt tries to treat a
backing file as an incorrect format if it is probing.
Is it worth tweaking docs/specs/qcow2.txt to permanently change the
incompatible_features field to be 4 bytes (76-79) and mandate that bytes
72-75 always be 0, as a conservative way to prevent other misbehavior of
programs like libvirt 0.10.2 that have code paths that don't correctly
validate for version 2 files? And if we ever really did hit a situation
of having 31 or more incompatible features, I could envision using bit
31 as an incompatible_feature witness that tells new enough qemu to look
at some other offset for remaining feature bits (thankfully, the
semantics of the incompatible_feature field mean that older qemu that
honors version 3 formats will reject a file with incompatible_feature
bit 31 set), so this is no real loss in the number of potential
incompatible features being added.
If we had a time machine, I would suggest that we change 72-75 to have
constant contents as a new magic number, and 76-79 encode the length of
the remaining header, and delay the location of incompatible_features
into the remaining header; such that this situation of libvirt looking
for extensions would still find known could have still found the backing
file format extension header. But that ship has sailed - all we can do
now is decide to encode 72-75 as permanent 0s.
--
Eric Blake eblake redhat com +1-919-301-3266
Libvirt virtualization library
http://libvirt.org