-
Notifications
You must be signed in to change notification settings - Fork 11
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
Kafl/fuzz 5.15 4 add virtio cache #8
base: kafl/fuzz-5.15-4
Are you sure you want to change the base?
Kafl/fuzz 5.15 4 add virtio cache #8
Conversation
a347a9d
to
f1e5795
Compare
0777c96
to
e283f8d
Compare
When fuzzing TDX, the endianness conversions from the bounce buffer results in unique values every time, which is impossible since the buffer is copied and not modified until invalidated later. IE a read at offset X within the buffer should always yield the same value but currently results in a new random fuzz value. To correct this, implement a cache for endianness conversions from the bounce buffer based on the unique device pointer. Just cache one u64 since all the values are smaller than u64. Invalidate the cache when the buffer is invalidated. Implement this cache within the virtio_device struct so each virtio_device gets its own cache and use an atomic. This avoids the need to share a global cache and any associated locking. Signed-off-by: William Roberts <william.c.roberts@intel.com>
e283f8d
to
0f80f76
Compare
@williamcroberts thank you! I have taken a look on the code and wondering if doing it this way would limit the fuzzing options considerably. You set a single u64 variable in per device cache, which is reset upon swiotlb bounce event. However, even the same device can consume more than u64 of input from swiotlb buffer to be used for different purposes. See here for example virtio-console handling: https://elixir.bootlin.com/linux/latest/source/drivers/char/virtio_console.c#L1578: it would first read the cpkt->event and then later on cpkt->id (this is all part of the same bounce buffer) and then does some processing on these values. But with the current cache implementation it would get the same fuzz value for both event and id (ok not the same exactly given that they are u16 and u32, but you get the idea). However, i dont know how to fix this easily: making bigger cache with filled fuzz input wont be an issue, but it would also require tracking what value has been asked from this cache.... |
One silly idea I got maybe in tdx_fuzz_virtio_cache_get_64 keep the mapping orig_v --> index into fuzz buffer of u64s, i.e. allocate big enough fuzz buffer on switlb bounce and then every time a value is asked from this fuzz buffer use orig_v as index into it? I think it might work without requiring of keeping track of proper swiotlb buffer indexes |
Yes I was thinking about how this limits granularity of the fuzzing, we could even do a get16, get32, get64 so we have 3 values per virtio buffer and then we could map from there as well. A initial goal was to avoid locking as I was unsure of the locking model in this device tree, so a simple atomic64 avoided that. i'd have to take a look deeper into the locking mechanism. |
@ereshetova that was a good thought, not silly. So now I see better fuzz granularity and it throwing more virtio errors:
Let me do some cleanups and ill update the PR. |
So in test BPH_VIRTIO_CONSOLE_INIT, I see it init the cache three times but the buffer never gets refreshed through swiotlb_bounce so our cache never updates which, is what folks say should be correct, but you'll definitely lose the amount of stuff you can actually fuzz per VM start. I also don't think we need any locking as init is guaranteed to run before anything else because it happens in virtio device creation, and since swiotlb_bounce and endian converting (reads) from the buffer have to mutually exclusive, I think we're safe. |
When fuzzing TDX, the endianness conversions from the bounce buffer results in unique values every time, which is impossible since the buffer is copied and not modified until invalidated later. IE a read at offset X within the buffer should always yield the same value but currently results in a new random fuzz value. To correct this, implement a cache for endianness conversions from the DMA bounce buffer based on the original value used as in index into a buffer of random fuzz data. Additionally, allow for index granularities into this array based on data type. For example, if the buffer is 512 bytes, it would support 64 unique u64s, 128 u32s and 256 u16s. Implement this cache within the virtio_device struct so each virtio_device gets its own cache and use an atomic. This avoids the need to share a global cache and any associated locking. Signed-off-by: William Roberts <william.c.roberts@intel.com>
2935dd9
to
7f9e569
Compare
The new code looks great to me! I think it provides both correct way of synching cache and also enough fuzzing granularity. |
@sirmc, one more thing I noticed now while reading virtio-console code in details is that in some cases (like for that control message handling), we already populate the virtqueue_get_buf with fuzz input and then this buffer arrives to handle_control_message, some of its parts like id and event are fuzz injected again via virtioxx_to wrappers. This feels like another problem with our virtio fuzzing: such thing cannot happen in real life interaction with the host. The correct way seems to be fuzzing virtqueue_get_buf (and it takes care of double fetches itself because virtio driver code doesnt seem to be reading the buffers twice, too heavy on its own) only for virtio drivers themselves, but then fuzzing virtioxx_to* wrappers for virtio ring itself (using proper cache that Bill implemented now to avoid double fetches). Or did I misunderstand smth on why we were simultaneously fuzzing both virtqueue_get_buf & virtioxx_to_* in virtio drivers? |
And even one more issue, now about accessing virtio config space for virtio devices. When a virtio device (like virtio-console) uses virtio_cread to get data from its config space, it will go down to virtio core and eventually invoke config->get which is mapped to vp_get and eventually ends up in different type of ioread. We already fuzz ioreads from a non-virtio hooks, so the fuzz input will be consumed correctly here. But when this fuzz input propagates up all the way to virtio_cread macro, it will get substituted by another fuzz input from virtio_to_cpu as the last thing before returning this input to virtio device. Here the substitute itself was not a problem previously since we always consumed a new input on each invocation of virtio_to_cpu (it was just silly i guess that we substituted one fuzz input with another one, but harmless), but now when we limit the fuzz input via the swiotlb cache it becomes not correct imo, because ioreads down are not done via swiotlb but straight with tdvmcalls, so we loose some fuzzing granularity here. In scenarios when host will be constantly updating config space for a given virtio driver but not using swiotlb for any other activity, we will see that virtio fuzz cache wont refresh and we will end up using the same buffer for fuzzing. On the positive side, since the ioreads underneath will be feeding different fuzz values, we will be indexing different offsets in virtio fuzz cache, so we will be somewhat varied here nonetheless. That said, I guess the best would not to fuzz the virtioxx_to_ wrappers for config space access anyhow. |
@ereshetova I agree with your points. I really think the virtio_to_XX interception is not the ideal way to fuzz this, but we added it a a simple, low-effort way. The proper way would be to mark certain memory ranges as DMA/ host-shared and then trap on any access to those memory ranges (i.e., enforce a page fault by unmapping the region). But at the time we deemed that too much work for something that we might not need to use. Re control messages, a similar setup to the VSOCK fuzzing (as described here https://intel.github.io/ccc-linux-guest-hardening-docs/tdx-guest-hardening.html#perform-driver-fuzzing), should be able to generate some control messages from the host. However, this fuzzing setup is a bit finnicky, since it requires an external stimulus in the form of a client/ server program on the host and guest. I added support for virtqueue_get_buf fuzzing, since there were a bunch of parts not really covered by the virtioxx_to_ wrappers. It might make sense to run harnesses with these hooks separately, individually enabled. I think our previous assumption was that virtioxx_to_ wrappers always accessed shared/ host memory, so could be changed at any time, but since this assumption is not true we should probably be a bit more conservative and use caches as you suggested above. |
@sirmc thank you for confirming my understanding! I think the assumption that virtioxx_to* wrappers access shared/dma memory mostly holds, the only clear exception i can see so far are config space access. So, i think when we enable boot style harnesses and want to cover various virtio devices probe functions, the best way is probably disabling input injection via virtioxx_to_* wrappers fully and rely on config space being injected via ioreads and virtqueue_get_buf for the rest (exchange of control messages during initialization and actual buffer data). But then we want to fuzz some virtio runtime interaction, then we should enable imo only virtioxx_to* wrappers (with Bill's cache) for higher fuzzing efficiency because both virtio core and virtio drivers seem to be using only the parts that they take out of these drivers and convert using these wrappers. Rest goes as payload towards upper layers and it is pointless to fuzz it since it should be protected by other means and no subject to host malicious input. I will double check this logic with rest of virtio drivers we use and then create a jira to adjust virtio harnesses accordingly. |
But the main bottom line is that both virtioxx_to* and virtqueue_get_fuzz injection should not be enabled at the same time because it will generate us false positives also. |
@ereshetova do you need an substantive code changes or can I drop this and move on :-p |
@williamcroberts I think this is good! The rest i will handle via configs, etc. Thank you very much for helping! |
Updated and ready for review