@samitouri / QOSamiQemu / commits / 32a90850e1

virtio-net: validate RSS indirections_len in post_load

virtio_net_handle_rss() enforces that indirections_len is a non-zero power of two no larger than VIRTIO_NET_RSS_MAX_TABLE_LEN, but virtio_net_rss_post_load() applies none of these checks to values restored from the migration stream. A corrupted save file or crafted migration stream can set indirections_len to 0. Even if it also clears redirect, virtio_load() calls set_features_nocheck() after the device vmstate (including the RSS subsection and its post_load) has already been loaded, re-deriving redirect from the negotiated guest features. When VIRTIO_NET_F_RSS was negotiated, redirect is set back to true regardless of the migration stream value. The receive path then computes hash & (indirections_len - 1) /* wraps to 0xFFFFFFFF via int promotion */ and uses the result to index into indirections_table, which was not allocated by the VMState loader when the element count is zero (see vmstate_handle_alloc()), resulting in a NULL pointer dereference that crashes QEMU: #0 virtio_net_process_rss ../hw/net/virtio-net.c:1901 #1 virtio_net_receive_rcu ../hw/net/virtio-net.c:1921 #2 virtio_net_do_receive ../hw/net/virtio-net.c:2061 #3 nc_sendv_compat ../net/net.c:823 #4 qemu_deliver_packet_iov ../net/net.c:870 The RSS subsection is only loaded when rss_data.enabled is true (via virtio_net_rss_needed()), and the command path always produces indirections_len in {1, 2, 4, …, 128}, so an unconditional check cannot reject a legitimate migration stream. Factor the validation into virtio_net_rss_indirections_len_valid() and call it from both virtio_net_handle_rss() and virtio_net_rss_post_load(). Fixes: e41b711485e5 ("virtio-net: add migration support for RSS and hash report") Cc: qemu-stable@nongnu.org Signed-off-by: Junjie Cao <junjie.cao@intel.com> Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com> Message-ID: <20260324060100.1997-1-junjie.cao@intel.com>

Junjie Cao committed Mar 24, 2026 at 14:01 UTC 32a90850e1e4222091df07b4b46d5f46a8b90d24
1 file changed +14 -7
hw/net/virtio-net.c
+14 -7
@@ -1374,6 +1374,11 @@ static void virtio_net_unload_ebpf(VirtIONet *n)
1374 ebpf_rss_unload(&n->ebpf_rss);
1375 }
1376
1377 +static bool virtio_net_rss_indirections_len_valid(uint16_t len)
1378 +{
1379 + return is_power_of_2(len) && len <= VIRTIO_NET_RSS_MAX_TABLE_LEN;
1380 +}
1381 +
1382 static uint16_t virtio_net_handle_rss(VirtIONet *n,
1383 struct iovec *iov,
1384 unsigned int iov_cnt,
@@ -1411,14 +1416,9 @@ static uint16_t virtio_net_handle_rss(VirtIONet *n,
1416 if (!do_rss) {
1417 n->rss_data.indirections_len = 0;
1418 }
1414 - if (n->rss_data.indirections_len >= VIRTIO_NET_RSS_MAX_TABLE_LEN) {
1415 - err_msg = "Too large indirection table";
1416 - err_value = n->rss_data.indirections_len;
1417 - goto error;
1418 - }
1419 n->rss_data.indirections_len++;
1420 - if (!is_power_of_2(n->rss_data.indirections_len)) {
1421 - err_msg = "Invalid size of indirection table";
1420 + if (!virtio_net_rss_indirections_len_valid(n->rss_data.indirections_len)) {
1421 + err_msg = "Invalid indirection table length";
1422 err_value = n->rss_data.indirections_len;
1423 goto error;
1424 }
@@ -3427,6 +3427,13 @@ static int virtio_net_rss_post_load(void *opaque, int version_id)
3427 n->rss_data.supported_hash_types = VIRTIO_NET_RSS_SUPPORTED_HASHES;
3428 }
3429
3430 + if (!virtio_net_rss_indirections_len_valid(n->rss_data.indirections_len)) {
3431 + error_report("virtio-net: saved image has invalid RSS "
3432 + "indirections_len: %u",
3433 + n->rss_data.indirections_len);
3434 + return -EINVAL;
3435 + }
3436 +
3437 return 0;
3438 }
3439