-
Notifications
You must be signed in to change notification settings - Fork 79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for Qcow2 disk images #237
Conversation
cc @germag |
330ed41
to
1d91568
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work! Some slight nits but overall this LGTM.
Nit: in 8a1795b commit description there is a typo: |
1d91568
to
f4618f1
Compare
I just realized one general thing (because we had that problem in qemu, for at least two CVEs): While it may seem obvious, note that This becomes especially important when it comes to probing the format, as is done here, though: If you use a raw image for a guest, the guest can then write a qcow2 image header into it, and the next time you launch the VM, it will be opened as a qcow2 image. Because of the external file references qcow2 images have, this basically allows the guest access to any file on the host system the disk emulation has access to. QEMU got around this problem by (a) very strongly encouraging users to specify the image format for every image, especially for (supposed) raw images; and (b) if raw was probed as a format, blocking writes to the first 512 bytes of the image that it would recognize as an image format header[1]. So (a) is a bit cumbersome to users, and if it were enforced for raw images, it would break compatibility. You could enforce it for non-raw images, to the same effect (i.e. format not specified = force raw), which keeps compatibility, but may be more cumbersome than you want. That would leave only (b), i.e. to check if the offset is 0, then take the head of the I/O vector and probe it, potentially denying a write. Something like let mut _copied_head = None;
if offset == 0 {
let (head, tail) = iovec.split_at(cmp::max(self.mem_align(), 4) as u64);
_copied_head = Some(head.try_into_owned(self.mem_align())?);
let head_slice = _copied_head.as_ref().unwrap().as_ref().into_slice();
if head_slice.get(0..4) == Some(&QCOW_MAGIC.to_be_bytes()) {
return Err(...);
}
iovec = tail.with_inserted(0, head_slice);
} Problem is that the guest could still write those four bytes one by one. qemu solves this with magic[2], but it may be reasonable to just require the guest to write the first sector as a whole (in case of a raw probed image), or at least the first four bytes, and reject anything else. [1] More detail: When the guest writes to the first 512 bytes of an image that was probed to be raw (i.e. users didn’t specify a format, qemu couldn’t recognize any, so defaulted to using the image as raw), qemu will pass the data that is to be written to every image format driver it has, and let them check whether they would recognize it as an image in their format. If so, the write is denied. Note that for this process, the data must be copied into a bounce buffer, or the guest could exploit a race where it pretends it’s writing a normal boot sector, qemu verifies this, passes the buffer on, and then the guest modifies the buffer to be a qcow2 header right before it’s actually written to the file. [2] For probed raw images, it sets the enforced request alignment to 512, ensuring there’s always a full read-modify-write cycle on the whole first 512 bytes, so it can check it all. Doesn’t work so easily here, because (a) imago does alignment on storage nodes, not format nodes, and (b) this is outside of imago. Now, if you really wanted to, you could emulate the same thing, but it requires a bit of hackiness:
|
Second thought that is incompatible with the idea of a "raw" image... so forget about it |
IMO a strict a) in libkrun, with "format not specified = force raw" in the krunkit side, is better. You will need to add a new api like But, also adding the mitigations that Hanna mentioned for raw images, will be a good idea to prevent any misuse |
Well, easy, because e.g. qemu’s raw “driver” allows you to specify
Yes. Some image formats actually have their header in the first 512 bytes and then the rest is raw (I think there’s a vdi variant to that effect?). You could also create a thin qcow2 image that has the raw file as its external data file, but… It’s just not a raw image then. |
Yes, that would be vital, or users may get the great idea of probing themselves, which would make it all moot. (I.e. the warning must clearly explain why probing is problematic, what the effects of opening an untrusted formatted image (e.g. in qcow2 format) can be (i.e. arbitrary file access), and that probing should be avoided at all costs.) EDIT: To expand on the “avoid probing at all costs” point, a bit of a counterpoint again: If anyone does probing, it should be libkrun, because it controls the guest’s I/O path, and so can actually do what qemu does, which is prevent the guest from turning raw images into formatted ones. So if probing is seen as something that anyone will reasonably want, it may be the most reasonable thing to do it in libkrun and not risk users doing it worse. It is true that nobody should probe, but, you know. (The only safe way a user of libkrun can probe an image’s format is by probing any guest image once before booting the guest for the first time (which is also the point where users should verify the image’s external dependencies (backing file, …) are safe to access), and then remembering and enforcing the format for all future uses of that file.) |
What I'm gathering from the feedback is that there are opportunities for misusing the disk image. Mitigation techniques:
|
Can probing inside libkrun be avoided, expecting the lib user (like krunkit) to do the probing?
Idk if it's a good idea: ...it's not a good idea
...and in any order |
What I gather from Hanna's comment (#237 (comment)) is yes we can. We would just need to ensure that the probe is happening once and before first boot. But it sounds like it would make one of the mitigation options irrelevant.
|
Indeed. However, I personally highly doubt it is reasonable to expect every libkrun user to always ensure they probe in this secure manner. To me, it feels like just pushing the security issue can down the road, so that if something happens we can point at the documentation and say “we warned you”. Then again, that’ll be done anyway for the question of whether a qcow2 image is safe to open, i.e. consider a downloaded qcow2 image that has /etc/shadow as its backing file[1]. libkrun can’t check whether those file references are safe, the user must do that, and who knows whether they will (experience says probably not).
As I described, doable if you always write the header in an RMW manner, i.e. if anything is written to the first sector at all, the whole first sector is read, the new data is embedded, then you check the resulting sector data as a whole, and write it as a whole, thus ensuring you never write an image header into it. [1] Yes, I know, shouldn’t be accessible anyway, it’s just an example. |
good point
I was thinking about an API that can return those paths to the libkrun user, so the user can check if those paths are safe. But following your first point, that can also be pushing the security on the user shoulders. Idk how cumbersome will be adding (and using) a "safe" API, that for instance requires a list of allowed/deny locations/path prefixes for backing files.
|
If we come up with something that seems nice and generic enough, it could be done right in imago so libkrun could continue to use |
I like this idea |
f4618f1
to
007e90c
Compare
In the latest batch of changes I tried to implement the mitigations in order to prevent the guest from misusing the disk image |
0b8eeef
to
60076bd
Compare
I just want to do a quick recap to make sure I'm getting everything:
|
Neither libkrun nor imago (on behalf of libkrun) should do any probing. There's no reasonable use case for that, and the risks are significant (this was a hard-learnt lesson in QEMU). It's up to the library consumers to explicitly tell us the image format.
Please remove UNSPECIFIED but keep RAW. There may be users preferring to always use (BTW, thanks for this work!) |
7593e31
to
46f797e
Compare
Hi everyone, thank you for all of the comments. I pushed a set of changes that should address everything. PTAL when you get a chance. |
46f797e
to
b05ca88
Compare
LGTM |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One general thing: I’d switch commits 3 and 4. Commit 3 introduces the external interface for qcow2 images, but internally, the format is actually ignored. So the internal capability to handle qcow2 images should come before.
I know, it technically doesn’t matter, as long as nobody looks between commits 3 and 4.
b05ca88
to
88f0a89
Compare
I still need to swap commits 2 and 3 which requires a couple changes, but I wanted to push everything else first. |
c110ec7
to
17fc841
Compare
Squashed a commit, reordered them, and updated the commit messages. Let me know if there's anything else :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Besides the comments, looks good to me, thanks!
17fc841
to
0e533aa
Compare
Adds the imago crate which is necessary to allow support for multiple disk image formats. Signed-off-by: Jake Correnti <[email protected]>
Changes the `file` attribute in `DiskProperties` to use `Arc<SyncFormatAccess<ImagoFile>>`. This allows libkrun to take advantage of imago's support for multiple disk image formats. Libkrun now supports the use of Raw or QCOW2 formats for disk images. Implements `FileReadWriteAtVolatile` trait on `DiskProperties` Adds an emum, `ImageType`, that describes the supported disk image variants. Signed-off-by: Jake Correnti <[email protected]>
Adds the `krun_add_disk2` API that requires the user to specify the format of the disk image they're providing. The following formats are supported: - KRUN_DISK_FORMAT_RAW - KRUN_DISK_FORMAT_QCOW2 Signed-off-by: Jake Correnti <[email protected]>
0e533aa
to
e7a5397
Compare
@slp PTAL |
New Code Quality failures are interesting considering no changes to the code were made... Something I should worry about in this PR? |
It seems it's in a file you didn't touched, you can open a new PR to change this:
to this
in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jakecorrenti Don't worry about the CI issues, I'll fix them in a different PR. Let's merge this one already.
@jakecorrenti @XanClic @tylerfanelli @germag Now, the one million question: who volunteers to package imago for Fedora? ;-) |
🙈 I’ll go figure it out. |
I can help you with that |
Use the imago crate to add support for multiple disk image types. This specifically adds support for the use of both Raw and Qcow2 disk images.