* [OE-core][scarthgap 01/12] qemu: fix CVE-2024-4467
2024-08-29 13:32 [OE-core][scarthgap 00/12] Patch review Steve Sakoman
@ 2024-08-29 13:32 ` Steve Sakoman
2024-08-29 13:32 ` [OE-core][scarthgap 02/12] libyaml: Ignore CVE-2024-35325 Steve Sakoman
` (10 subsequent siblings)
11 siblings, 0 replies; 19+ messages in thread
From: Steve Sakoman @ 2024-08-29 13:32 UTC (permalink / raw)
To: openembedded-core
From: Yogita Urade <yogita.urade@windriver.com>
A flaw was found in the QEMU disk image utility (qemu-img) 'info'
command. A specially crafted image file containing a `json:{}`
value describing block devices in QMP could cause the qemu-img
process on the host to consume large amounts of memory or CPU time,
leading to denial of service or read/write to an existing external file
Reference:
https://nvd.nist.gov/vuln/detail/CVE-2024-4467
Upstream Patches:
https://gitlab.com/qemu-project/qemu/-/commit/bd385a5298d7062668e804d73944d52aec9549f1
https://gitlab.com/qemu-project/qemu/-/commit/2eb42a728d27a43fdcad5f37d3f65706ce6deba5
https://gitlab.com/qemu-project/qemu/-/commit/7e1110664ecbc4826f3c978ccb06b6c1bce823e6
https://gitlab.com/qemu-project/qemu/-/commit/6bc30f19498547fac9cef98316a65cf6c1f14205
https://gitlab.com/qemu-project/qemu/-/commit/7ead946998610657d38d1a505d5f25300d4ca613
Signed-off-by: Yogita Urade <yogita.urade@windriver.com>
Signed-off-by: Steve Sakoman <steve@sakoman.com>
---
meta/recipes-devtools/qemu/qemu.inc | 5 +
.../qemu/qemu/CVE-2024-4467-0001.patch | 112 ++
.../qemu/qemu/CVE-2024-4467-0002.patch | 55 +
.../qemu/qemu/CVE-2024-4467-0003.patch | 57 +
.../qemu/qemu/CVE-2024-4467-0004.patch | 1187 +++++++++++++++++
.../qemu/qemu/CVE-2024-4467-0005.patch | 239 ++++
6 files changed, 1655 insertions(+)
create mode 100644 meta/recipes-devtools/qemu/qemu/CVE-2024-4467-0001.patch
create mode 100644 meta/recipes-devtools/qemu/qemu/CVE-2024-4467-0002.patch
create mode 100644 meta/recipes-devtools/qemu/qemu/CVE-2024-4467-0003.patch
create mode 100644 meta/recipes-devtools/qemu/qemu/CVE-2024-4467-0004.patch
create mode 100644 meta/recipes-devtools/qemu/qemu/CVE-2024-4467-0005.patch
diff --git a/meta/recipes-devtools/qemu/qemu.inc b/meta/recipes-devtools/qemu/qemu.inc
index 3643b9a544..50d92b04bd 100644
--- a/meta/recipes-devtools/qemu/qemu.inc
+++ b/meta/recipes-devtools/qemu/qemu.inc
@@ -40,6 +40,11 @@ SRC_URI = "https://download.qemu.org/${BPN}-${PV}.tar.xz \
file://0005-tests-tcg-Check-that-shmat-does-not-break-proc-self-.patch \
file://qemu-guest-agent.init \
file://qemu-guest-agent.udev \
+ file://CVE-2024-4467-0001.patch \
+ file://CVE-2024-4467-0002.patch \
+ file://CVE-2024-4467-0003.patch \
+ file://CVE-2024-4467-0004.patch \
+ file://CVE-2024-4467-0005.patch \
"
UPSTREAM_CHECK_REGEX = "qemu-(?P<pver>\d+(\.\d+)+)\.tar"
diff --git a/meta/recipes-devtools/qemu/qemu/CVE-2024-4467-0001.patch b/meta/recipes-devtools/qemu/qemu/CVE-2024-4467-0001.patch
new file mode 100644
index 0000000000..dbcc71bb4e
--- /dev/null
+++ b/meta/recipes-devtools/qemu/qemu/CVE-2024-4467-0001.patch
@@ -0,0 +1,112 @@
+From bd385a5298d7062668e804d73944d52aec9549f1 Mon Sep 17 00:00:00 2001
+From: Kevin Wolf <kwolf@redhat.com>
+Date: Fri, 16 Aug 2024 08:29:04 +0000
+Subject: [PATCH] qcow2: Don't open data_file with BDRV_O_NO_IO
+
+One use case for 'qemu-img info' is verifying that untrusted images
+don't reference an unwanted external file, be it as a backing file or an
+external data file. To make sure that calling 'qemu-img info' can't
+already have undesired side effects with a malicious image, just don't
+open the data file at all with BDRV_O_NO_IO. If nothing ever tries to do
+I/O, we don't need to have it open.
+
+This changes the output of iotests case 061, which used 'qemu-img info'
+to show that opening an image with an invalid data file fails. After
+this patch, it succeeds. Replace this part of the test with a qemu-io
+call, but keep the final 'qemu-img info' to show that the invalid data
+file is correctly displayed in the output.
+
+Fixes: CVE-2024-4467
+Cc: qemu-stable@nongnu.org
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+Reviewed-by: Eric Blake <eblake@redhat.com>
+Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
+Reviewed-by: Hanna Czenczek <hreitz@redhat.com>
+
+CVE: CVE-2024-4667
+Upstream-Status: Backport [https://gitlab.com/qemu-project/qemu/-/commit/bd385a5298d7062668e804d73944d52aec9549f1]
+
+Signed-off-by: Yogita Urade <yogita.urade@windriver.com>
+---
+ block/qcow2.c | 17 ++++++++++++++++-
+ tests/qemu-iotests/061 | 6 ++++--
+ tests/qemu-iotests/061.out | 8 ++++++--
+ 3 files changed, 26 insertions(+), 5 deletions(-)
+
+diff --git a/block/qcow2.c b/block/qcow2.c
+index 13e032bd5..7af7c0bee 100644
+--- a/block/qcow2.c
++++ b/block/qcow2.c
+@@ -1636,7 +1636,22 @@ qcow2_do_open(BlockDriverState *bs, QDict *options, int flags,
+ goto fail;
+ }
+
+- if (open_data_file) {
++ if (open_data_file && (flags & BDRV_O_NO_IO)) {
++ /*
++ * Don't open the data file for 'qemu-img info' so that it can be used
++ * to verify that an untrusted qcow2 image doesn't refer to external
++ * files.
++ *
++ * Note: This still makes has_data_file() return true.
++ */
++ if (s->incompatible_features & QCOW2_INCOMPAT_DATA_FILE) {
++ s->data_file = NULL;
++ } else {
++ s->data_file = bs->file;
++ }
++ qdict_extract_subqdict(options, NULL, "data-file.");
++ qdict_del(options, "data-file");
++ } else if (open_data_file) {
+ /* Open external data file */
+ bdrv_graph_co_rdunlock();
+ s->data_file = bdrv_co_open_child(NULL, options, "data-file", bs,
+diff --git a/tests/qemu-iotests/061 b/tests/qemu-iotests/061
+index 53c7d428e..b71ac097d 100755
+--- a/tests/qemu-iotests/061
++++ b/tests/qemu-iotests/061
+@@ -326,12 +326,14 @@ $QEMU_IMG amend -o "data_file=foo" "$TEST_IMG"
+ echo
+ _make_test_img -o "compat=1.1,data_file=$TEST_IMG.data" 64M
+ $QEMU_IMG amend -o "data_file=foo" "$TEST_IMG"
+-_img_info --format-specific
++$QEMU_IO -c "read 0 4k" "$TEST_IMG" 2>&1 | _filter_testdir | _filter_imgfmt
++$QEMU_IO -c "open -o data-file.filename=$TEST_IMG.data,file.filename=$TEST_IMG" -c "read 0 4k" | _filter_qemu_io
+ TEST_IMG="data-file.filename=$TEST_IMG.data,file.filename=$TEST_IMG" _img_info --format-specific --image-opts
+
+ echo
+ $QEMU_IMG amend -o "data_file=" --image-opts "data-file.filename=$TEST_IMG.data,file.filename=$TEST_IMG"
+-_img_info --format-specific
++$QEMU_IO -c "read 0 4k" "$TEST_IMG" 2>&1 | _filter_testdir | _filter_imgfmt
++$QEMU_IO -c "open -o data-file.filename=$TEST_IMG.data,file.filename=$TEST_IMG" -c "read 0 4k" | _filter_qemu_io
+ TEST_IMG="data-file.filename=$TEST_IMG.data,file.filename=$TEST_IMG" _img_info --format-specific --image-opts
+
+ echo
+diff --git a/tests/qemu-iotests/061.out b/tests/qemu-iotests/061.out
+index 139fc6817..24c33add7 100644
+--- a/tests/qemu-iotests/061.out
++++ b/tests/qemu-iotests/061.out
+@@ -545,7 +545,9 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
+ qemu-img: data-file can only be set for images that use an external data file
+
+ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 data_file=TEST_DIR/t.IMGFMT.data
+-qemu-img: Could not open 'TEST_DIR/t.IMGFMT': Could not open 'foo': No such file or directory
++qemu-io: can't open device TEST_DIR/t.IMGFMT: Could not open 'foo': No such file or directory
++read 4096/4096 bytes at offset 0
++4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+ image: TEST_DIR/t.IMGFMT
+ file format: IMGFMT
+ virtual size: 64 MiB (67108864 bytes)
+@@ -560,7 +562,9 @@ Format specific information:
+ corrupt: false
+ extended l2: false
+
+-qemu-img: Could not open 'TEST_DIR/t.IMGFMT': 'data-file' is required for this image
++qemu-io: can't open device TEST_DIR/t.IMGFMT: 'data-file' is required for this image
++read 4096/4096 bytes at offset 0
++4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+ image: TEST_DIR/t.IMGFMT
+ file format: IMGFMT
+ virtual size: 64 MiB (67108864 bytes)
+--
+2.40.0
diff --git a/meta/recipes-devtools/qemu/qemu/CVE-2024-4467-0002.patch b/meta/recipes-devtools/qemu/qemu/CVE-2024-4467-0002.patch
new file mode 100644
index 0000000000..686176189c
--- /dev/null
+++ b/meta/recipes-devtools/qemu/qemu/CVE-2024-4467-0002.patch
@@ -0,0 +1,55 @@
+From 2eb42a728d27a43fdcad5f37d3f65706ce6deba5 Mon Sep 17 00:00:00 2001
+From: Kevin Wolf <kwolf@redhat.com>
+Date: Fri, 16 Aug 2024 09:35:24 +0000
+Subject: [PATCH] iotests/244: Don't store data-file with protocol in image
+
+We want to disable filename parsing for data files because it's too easy
+to abuse in malicious image files. Make the test ready for the change by
+passing the data file explicitly in command line options.
+
+Cc: qemu-stable@nongnu.org
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+Reviewed-by: Eric Blake <eblake@redhat.com>
+Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
+Reviewed-by: Hanna Czenczek <hreitz@redhat.com>
+
+CVE: CVE-2024-4467
+Upstream-Status: Backport [https://gitlab.com/qemu-project/qemu/-/commit/2eb42a728d27a43fdcad5f37d3f65706ce6deba5]
+
+Signed-off-by: Yogita Urade <yogita.urade@windriver.com>
+---
+ tests/qemu-iotests/244 | 19 ++++++++++++++++---
+ 1 file changed, 16 insertions(+), 3 deletions(-)
+
+diff --git a/tests/qemu-iotests/244 b/tests/qemu-iotests/244
+index 3e61fa25b..bb9cc6512 100755
+--- a/tests/qemu-iotests/244
++++ b/tests/qemu-iotests/244
+@@ -215,9 +215,22 @@ $QEMU_IMG convert -f $IMGFMT -O $IMGFMT -n -C "$TEST_IMG.src" "$TEST_IMG"
+ $QEMU_IMG compare -f $IMGFMT -F $IMGFMT "$TEST_IMG.src" "$TEST_IMG"
+
+ # blkdebug doesn't support copy offloading, so this tests the error path
+-$QEMU_IMG amend -f $IMGFMT -o "data_file=blkdebug::$TEST_IMG.data" "$TEST_IMG"
+-$QEMU_IMG convert -f $IMGFMT -O $IMGFMT -n -C "$TEST_IMG.src" "$TEST_IMG"
+-$QEMU_IMG compare -f $IMGFMT -F $IMGFMT "$TEST_IMG.src" "$TEST_IMG"
++test_img_with_blkdebug="json:{
++ 'driver': 'qcow2',
++ 'file': {
++ 'driver': 'file',
++ 'filename': '$TEST_IMG'
++ },
++ 'data-file': {
++ 'driver': 'blkdebug',
++ 'image': {
++ 'driver': 'file',
++ 'filename': '$TEST_IMG.data'
++ }
++ }
++}"
++$QEMU_IMG convert -f $IMGFMT -O $IMGFMT -n -C "$TEST_IMG.src" "$test_img_with_blkdebug"
++$QEMU_IMG compare -f $IMGFMT -F $IMGFMT "$TEST_IMG.src" "$test_img_with_blkdebug"
+
+ echo
+ echo "=== Flushing should flush the data file ==="
+--
+2.40.0
diff --git a/meta/recipes-devtools/qemu/qemu/CVE-2024-4467-0003.patch b/meta/recipes-devtools/qemu/qemu/CVE-2024-4467-0003.patch
new file mode 100644
index 0000000000..02611d6732
--- /dev/null
+++ b/meta/recipes-devtools/qemu/qemu/CVE-2024-4467-0003.patch
@@ -0,0 +1,57 @@
+From 7e1110664ecbc4826f3c978ccb06b6c1bce823e6 Mon Sep 17 00:00:00 2001
+From: Kevin Wolf <kwolf@redhat.com>
+Date: Fri, 16 Aug 2024 10:24:58 +0000
+Subject: [PATCH] iotests/270: Don't store data-file with json: prefix in image
+
+We want to disable filename parsing for data files because it's too easy
+to abuse in malicious image files. Make the test ready for the change by
+passing the data file explicitly in command line options.
+
+Cc: qemu-stable@nongnu.org
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+Reviewed-by: Eric Blake <eblake@redhat.com>
+Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
+Reviewed-by: Hanna Czenczek <hreitz@redhat.com>
+
+CVE: CVE-2024-4467
+Upstream-Status: Backport [https://gitlab.com/qemu-project/qemu/-/commit/7e1110664ecbc4826f3c978ccb06b6c1bce823e6]
+
+Signed-off-by: Yogita Urade <yogita.urade@windriver.com>
+---
+ tests/qemu-iotests/270 | 14 +++++++++++---
+ 1 file changed, 11 insertions(+), 3 deletions(-)
+
+diff --git a/tests/qemu-iotests/270 b/tests/qemu-iotests/270
+index 74352342d..c37b674aa 100755
+--- a/tests/qemu-iotests/270
++++ b/tests/qemu-iotests/270
+@@ -60,8 +60,16 @@ _make_test_img -o cluster_size=2M,data_file="$TEST_IMG.orig" \
+ # "write" 2G of data without using any space.
+ # (qemu-img create does not like it, though, because null-co does not
+ # support image creation.)
+-$QEMU_IMG amend -o data_file="json:{'driver':'null-co',,'size':'4294967296'}" \
+- "$TEST_IMG"
++test_img_with_null_data="json:{
++ 'driver': '$IMGFMT',
++ 'file': {
++ 'filename': '$TEST_IMG'
++ },
++ 'data-file': {
++ 'driver': 'null-co',
++ 'size':'4294967296'
++ }
++}"
+
+ # This gives us a range of:
+ # 2^31 - 512 + 768 - 1 = 2^31 + 255 > 2^31
+@@ -74,7 +82,7 @@ $QEMU_IMG amend -o data_file="json:{'driver':'null-co',,'size':'4294967296'}" \
+ # on L2 boundaries, we need large L2 tables; hence the cluster size of
+ # 2 MB. (Anything from 256 kB should work, though, because then one L2
+ # table covers 8 GB.)
+-$QEMU_IO -c "write 768 $((2 ** 31 - 512))" "$TEST_IMG" | _filter_qemu_io
++$QEMU_IO -c "write 768 $((2 ** 31 - 512))" "$test_img_with_null_data" | _filter_qemu_io
+
+ _check_test_img
+
+--
+2.40.0
diff --git a/meta/recipes-devtools/qemu/qemu/CVE-2024-4467-0004.patch b/meta/recipes-devtools/qemu/qemu/CVE-2024-4467-0004.patch
new file mode 100644
index 0000000000..7568a453c4
--- /dev/null
+++ b/meta/recipes-devtools/qemu/qemu/CVE-2024-4467-0004.patch
@@ -0,0 +1,1187 @@
+From 6bc30f19498547fac9cef98316a65cf6c1f14205 Mon Sep 17 00:00:00 2001
+From: Stefan Hajnoczi <stefanha@redhat.com>
+Date: Tue, 5 Dec 2023 13:20:02 -0500
+Subject: [PATCH] graph-lock: remove AioContext locking
+
+Stop acquiring/releasing the AioContext lock in
+bdrv_graph_wrlock()/bdrv_graph_unlock() since the lock no longer has any
+effect.
+
+The distinction between bdrv_graph_wrunlock() and
+bdrv_graph_wrunlock_ctx() becomes meaningless and they can be collapsed
+into one function.
+
+Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
+Reviewed-by: Eric Blake <eblake@redhat.com>
+Reviewed-by: Kevin Wolf <kwolf@redhat.com>
+Message-ID: <20231205182011.1976568-6-stefanha@redhat.com>
+Signed-off-by: Kevin Wolf <kwolf@redhat.com>
+
+CVE: CVE-2024-4467
+Upstream-Status: Backport [https://gitlab.com/qemu-project/qemu/-/commit/6bc30f19498547fac9cef98316a65cf6c1f14205]
+
+Signed-off-by: Yogita Urade <yogita.urade@windriver.com>
+---
+ block.c | 50 +++++++++++++++---------------
+ block/backup.c | 4 +--
+ block/blklogwrites.c | 8 ++---
+ block/blkverify.c | 4 +--
+ block/block-backend.c | 11 +++----
+ block/commit.c | 16 +++++-----
+ block/graph-lock.c | 44 ++------------------------
+ block/mirror.c | 22 ++++++-------
+ block/qcow2.c | 4 +--
+ block/quorum.c | 8 ++---
+ block/replication.c | 14 ++++-----
+ block/snapshot.c | 4 +--
+ block/stream.c | 12 +++----
+ block/vmdk.c | 20 ++++++------
+ blockdev.c | 8 ++---
+ blockjob.c | 12 +++----
+ include/block/graph-lock.h | 21 ++-----------
+ scripts/block-coroutine-wrapper.py | 4 +--
+ tests/unit/test-bdrv-drain.c | 40 ++++++++++++------------
+ tests/unit/test-bdrv-graph-mod.c | 20 ++++++------
+ 20 files changed, 133 insertions(+), 193 deletions(-)
+
+diff --git a/block.c b/block.c
+index bfb0861ec..25e1ebc60 100644
+--- a/block.c
++++ b/block.c
+@@ -1708,12 +1708,12 @@ bdrv_open_driver(BlockDriverState *bs, BlockDriver *drv, const char *node_name,
+ open_failed:
+ bs->drv = NULL;
+
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ if (bs->file != NULL) {
+ bdrv_unref_child(bs, bs->file);
+ assert(!bs->file);
+ }
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+
+ g_free(bs->opaque);
+ bs->opaque = NULL;
+@@ -3575,9 +3575,9 @@ int bdrv_set_backing_hd(BlockDriverState *bs, BlockDriverState *backing_hd,
+
+ bdrv_ref(drain_bs);
+ bdrv_drained_begin(drain_bs);
+- bdrv_graph_wrlock(backing_hd);
++ bdrv_graph_wrlock();
+ ret = bdrv_set_backing_hd_drained(bs, backing_hd, errp);
+- bdrv_graph_wrunlock(backing_hd);
++ bdrv_graph_wrunlock();
+ bdrv_drained_end(drain_bs);
+ bdrv_unref(drain_bs);
+
+@@ -3790,13 +3790,13 @@ BdrvChild *bdrv_open_child(const char *filename,
+ return NULL;
+ }
+
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ ctx = bdrv_get_aio_context(bs);
+ aio_context_acquire(ctx);
+ child = bdrv_attach_child(parent, bs, bdref_key, child_class, child_role,
+ errp);
+ aio_context_release(ctx);
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+
+ return child;
+ }
+@@ -4650,9 +4650,9 @@ int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp)
+ aio_context_release(ctx);
+ }
+
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ tran_commit(tran);
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+
+ QTAILQ_FOREACH_REVERSE(bs_entry, bs_queue, entry) {
+ BlockDriverState *bs = bs_entry->state.bs;
+@@ -4669,9 +4669,9 @@ int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp)
+ goto cleanup;
+
+ abort:
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ tran_abort(tran);
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+
+ QTAILQ_FOREACH_SAFE(bs_entry, bs_queue, entry, next) {
+ if (bs_entry->prepared) {
+@@ -4852,12 +4852,12 @@ bdrv_reopen_parse_file_or_backing(BDRVReopenState *reopen_state,
+ }
+
+ bdrv_graph_rdunlock_main_loop();
+- bdrv_graph_wrlock(new_child_bs);
++ bdrv_graph_wrlock();
+
+ ret = bdrv_set_file_or_backing_noperm(bs, new_child_bs, is_backing,
+ tran, errp);
+
+- bdrv_graph_wrunlock_ctx(ctx);
++ bdrv_graph_wrunlock();
+
+ if (old_ctx != ctx) {
+ aio_context_release(ctx);
+@@ -5209,14 +5209,14 @@ static void bdrv_close(BlockDriverState *bs)
+ bs->drv = NULL;
+ }
+
+- bdrv_graph_wrlock(bs);
++ bdrv_graph_wrlock();
+ QLIST_FOREACH_SAFE(child, &bs->children, next, next) {
+ bdrv_unref_child(bs, child);
+ }
+
+ assert(!bs->backing);
+ assert(!bs->file);
+- bdrv_graph_wrunlock(bs);
++ bdrv_graph_wrunlock();
+
+ g_free(bs->opaque);
+ bs->opaque = NULL;
+@@ -5509,9 +5509,9 @@ int bdrv_drop_filter(BlockDriverState *bs, Error **errp)
+ bdrv_graph_rdunlock_main_loop();
+
+ bdrv_drained_begin(child_bs);
+- bdrv_graph_wrlock(bs);
++ bdrv_graph_wrlock();
+ ret = bdrv_replace_node_common(bs, child_bs, true, true, errp);
+- bdrv_graph_wrunlock(bs);
++ bdrv_graph_wrunlock();
+ bdrv_drained_end(child_bs);
+
+ return ret;
+@@ -5561,7 +5561,7 @@ int bdrv_append(BlockDriverState *bs_new, BlockDriverState *bs_top,
+ aio_context_acquire(old_context);
+ new_context = NULL;
+
+- bdrv_graph_wrlock(bs_top);
++ bdrv_graph_wrlock();
+
+ child = bdrv_attach_child_noperm(bs_new, bs_top, "backing",
+ &child_of_bds, bdrv_backing_role(bs_new),
+@@ -5593,7 +5593,7 @@ out:
+ tran_finalize(tran, ret);
+
+ bdrv_refresh_limits(bs_top, NULL, NULL);
+- bdrv_graph_wrunlock(bs_top);
++ bdrv_graph_wrunlock();
+
+ bdrv_drained_end(bs_top);
+ bdrv_drained_end(bs_new);
+@@ -5620,7 +5620,7 @@ int bdrv_replace_child_bs(BdrvChild *child, BlockDriverState *new_bs,
+ bdrv_ref(old_bs);
+ bdrv_drained_begin(old_bs);
+ bdrv_drained_begin(new_bs);
+- bdrv_graph_wrlock(new_bs);
++ bdrv_graph_wrlock();
+
+ bdrv_replace_child_tran(child, new_bs, tran);
+
+@@ -5631,7 +5631,7 @@ int bdrv_replace_child_bs(BdrvChild *child, BlockDriverState *new_bs,
+
+ tran_finalize(tran, ret);
+
+- bdrv_graph_wrunlock(new_bs);
++ bdrv_graph_wrunlock();
+ bdrv_drained_end(old_bs);
+ bdrv_drained_end(new_bs);
+ bdrv_unref(old_bs);
+@@ -5718,9 +5718,9 @@ BlockDriverState *bdrv_insert_node(BlockDriverState *bs, QDict *options,
+ bdrv_ref(bs);
+ bdrv_drained_begin(bs);
+ bdrv_drained_begin(new_node_bs);
+- bdrv_graph_wrlock(new_node_bs);
++ bdrv_graph_wrlock();
+ ret = bdrv_replace_node(bs, new_node_bs, errp);
+- bdrv_graph_wrunlock(new_node_bs);
++ bdrv_graph_wrunlock();
+ bdrv_drained_end(new_node_bs);
+ bdrv_drained_end(bs);
+ bdrv_unref(bs);
+@@ -5975,7 +5975,7 @@ int bdrv_drop_intermediate(BlockDriverState *top, BlockDriverState *base,
+
+ bdrv_ref(top);
+ bdrv_drained_begin(base);
+- bdrv_graph_wrlock(base);
++ bdrv_graph_wrlock();
+
+ if (!top->drv || !base->drv) {
+ goto exit_wrlock;
+@@ -6015,7 +6015,7 @@ int bdrv_drop_intermediate(BlockDriverState *top, BlockDriverState *base,
+ * That's a FIXME.
+ */
+ bdrv_replace_node_common(top, base, false, false, &local_err);
+- bdrv_graph_wrunlock(base);
++ bdrv_graph_wrunlock();
+
+ if (local_err) {
+ error_report_err(local_err);
+@@ -6052,7 +6052,7 @@ int bdrv_drop_intermediate(BlockDriverState *top, BlockDriverState *base,
+ goto exit;
+
+ exit_wrlock:
+- bdrv_graph_wrunlock(base);
++ bdrv_graph_wrunlock();
+ exit:
+ bdrv_drained_end(base);
+ bdrv_unref(top);
+diff --git a/block/backup.c b/block/backup.c
+index 8aae5836d..ec29d6b81 100644
+--- a/block/backup.c
++++ b/block/backup.c
+@@ -496,10 +496,10 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
+ block_copy_set_speed(bcs, speed);
+
+ /* Required permissions are taken by copy-before-write filter target */
+- bdrv_graph_wrlock(target);
++ bdrv_graph_wrlock();
+ block_job_add_bdrv(&job->common, "target", target, 0, BLK_PERM_ALL,
+ &error_abort);
+- bdrv_graph_wrunlock(target);
++ bdrv_graph_wrunlock();
+
+ return &job->common;
+
+diff --git a/block/blklogwrites.c b/block/blklogwrites.c
+index 84e03f309..ba717dab4 100644
+--- a/block/blklogwrites.c
++++ b/block/blklogwrites.c
+@@ -251,9 +251,9 @@ static int blk_log_writes_open(BlockDriverState *bs, QDict *options, int flags,
+ ret = 0;
+ fail_log:
+ if (ret < 0) {
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ bdrv_unref_child(bs, s->log_file);
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+ s->log_file = NULL;
+ }
+ fail:
+@@ -265,10 +265,10 @@ static void blk_log_writes_close(BlockDriverState *bs)
+ {
+ BDRVBlkLogWritesState *s = bs->opaque;
+
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ bdrv_unref_child(bs, s->log_file);
+ s->log_file = NULL;
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+ }
+
+ static int64_t coroutine_fn GRAPH_RDLOCK
+diff --git a/block/blkverify.c b/block/blkverify.c
+index 9b17c4664..ec45d8335 100644
+--- a/block/blkverify.c
++++ b/block/blkverify.c
+@@ -151,10 +151,10 @@ static void blkverify_close(BlockDriverState *bs)
+ {
+ BDRVBlkverifyState *s = bs->opaque;
+
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ bdrv_unref_child(bs, s->test_file);
+ s->test_file = NULL;
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+ }
+
+ static int64_t coroutine_fn GRAPH_RDLOCK
+diff --git a/block/block-backend.c b/block/block-backend.c
+index 86315d62c..a2348b31e 100644
+--- a/block/block-backend.c
++++ b/block/block-backend.c
+@@ -885,7 +885,6 @@ void blk_remove_bs(BlockBackend *blk)
+ {
+ ThrottleGroupMember *tgm = &blk->public.throttle_group_member;
+ BdrvChild *root;
+- AioContext *ctx;
+
+ GLOBAL_STATE_CODE();
+
+@@ -915,10 +914,9 @@ void blk_remove_bs(BlockBackend *blk)
+ root = blk->root;
+ blk->root = NULL;
+
+- ctx = bdrv_get_aio_context(root->bs);
+- bdrv_graph_wrlock(root->bs);
++ bdrv_graph_wrlock();
+ bdrv_root_unref_child(root);
+- bdrv_graph_wrunlock_ctx(ctx);
++ bdrv_graph_wrunlock();
+ }
+
+ /*
+@@ -929,16 +927,15 @@ void blk_remove_bs(BlockBackend *blk)
+ int blk_insert_bs(BlockBackend *blk, BlockDriverState *bs, Error **errp)
+ {
+ ThrottleGroupMember *tgm = &blk->public.throttle_group_member;
+- AioContext *ctx = bdrv_get_aio_context(bs);
+
+ GLOBAL_STATE_CODE();
+ bdrv_ref(bs);
+- bdrv_graph_wrlock(bs);
++ bdrv_graph_wrlock();
+ blk->root = bdrv_root_attach_child(bs, "root", &child_root,
+ BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
+ blk->perm, blk->shared_perm,
+ blk, errp);
+- bdrv_graph_wrunlock_ctx(ctx);
++ bdrv_graph_wrunlock();
+ if (blk->root == NULL) {
+ return -EPERM;
+ }
+diff --git a/block/commit.c b/block/commit.c
+index 69cc75be0..1dd7a65ff 100644
+--- a/block/commit.c
++++ b/block/commit.c
+@@ -100,9 +100,9 @@ static void commit_abort(Job *job)
+ bdrv_graph_rdunlock_main_loop();
+
+ bdrv_drained_begin(commit_top_backing_bs);
+- bdrv_graph_wrlock(commit_top_backing_bs);
++ bdrv_graph_wrlock();
+ bdrv_replace_node(s->commit_top_bs, commit_top_backing_bs, &error_abort);
+- bdrv_graph_wrunlock(commit_top_backing_bs);
++ bdrv_graph_wrunlock();
+ bdrv_drained_end(commit_top_backing_bs);
+
+ bdrv_unref(s->commit_top_bs);
+@@ -339,7 +339,7 @@ void commit_start(const char *job_id, BlockDriverState *bs,
+ * this is the responsibility of the interface (i.e. whoever calls
+ * commit_start()).
+ */
+- bdrv_graph_wrlock(top);
++ bdrv_graph_wrlock();
+ s->base_overlay = bdrv_find_overlay(top, base);
+ assert(s->base_overlay);
+
+@@ -370,19 +370,19 @@ void commit_start(const char *job_id, BlockDriverState *bs,
+ ret = block_job_add_bdrv(&s->common, "intermediate node", iter, 0,
+ iter_shared_perms, errp);
+ if (ret < 0) {
+- bdrv_graph_wrunlock(top);
++ bdrv_graph_wrunlock();
+ goto fail;
+ }
+ }
+
+ if (bdrv_freeze_backing_chain(commit_top_bs, base, errp) < 0) {
+- bdrv_graph_wrunlock(top);
++ bdrv_graph_wrunlock();
+ goto fail;
+ }
+ s->chain_frozen = true;
+
+ ret = block_job_add_bdrv(&s->common, "base", base, 0, BLK_PERM_ALL, errp);
+- bdrv_graph_wrunlock(top);
++ bdrv_graph_wrunlock();
+
+ if (ret < 0) {
+ goto fail;
+@@ -434,9 +434,9 @@ fail:
+ * otherwise this would fail because of lack of permissions. */
+ if (commit_top_bs) {
+ bdrv_drained_begin(top);
+- bdrv_graph_wrlock(top);
++ bdrv_graph_wrlock();
+ bdrv_replace_node(commit_top_bs, top, &error_abort);
+- bdrv_graph_wrunlock(top);
++ bdrv_graph_wrunlock();
+ bdrv_drained_end(top);
+ }
+ }
+diff --git a/block/graph-lock.c b/block/graph-lock.c
+index 079e878d9..c81162b14 100644
+--- a/block/graph-lock.c
++++ b/block/graph-lock.c
+@@ -106,27 +106,12 @@ static uint32_t reader_count(void)
+ return rd;
+ }
+
+-void no_coroutine_fn bdrv_graph_wrlock(BlockDriverState *bs)
++void no_coroutine_fn bdrv_graph_wrlock(void)
+ {
+- AioContext *ctx = NULL;
+-
+ GLOBAL_STATE_CODE();
+ assert(!qatomic_read(&has_writer));
+ assert(!qemu_in_coroutine());
+
+- /*
+- * Release only non-mainloop AioContext. The mainloop often relies on the
+- * BQL and doesn't lock the main AioContext before doing things.
+- */
+- if (bs) {
+- ctx = bdrv_get_aio_context(bs);
+- if (ctx != qemu_get_aio_context()) {
+- aio_context_release(ctx);
+- } else {
+- ctx = NULL;
+- }
+- }
+-
+ /* Make sure that constantly arriving new I/O doesn't cause starvation */
+ bdrv_drain_all_begin_nopoll();
+
+@@ -155,27 +140,13 @@ void no_coroutine_fn bdrv_graph_wrlock(BlockDriverState *bs)
+ } while (reader_count() >= 1);
+
+ bdrv_drain_all_end();
+-
+- if (ctx) {
+- aio_context_acquire(bdrv_get_aio_context(bs));
+- }
+ }
+
+-void no_coroutine_fn bdrv_graph_wrunlock_ctx(AioContext *ctx)
++void no_coroutine_fn bdrv_graph_wrunlock(void)
+ {
+ GLOBAL_STATE_CODE();
+ assert(qatomic_read(&has_writer));
+
+- /*
+- * Release only non-mainloop AioContext. The mainloop often relies on the
+- * BQL and doesn't lock the main AioContext before doing things.
+- */
+- if (ctx && ctx != qemu_get_aio_context()) {
+- aio_context_release(ctx);
+- } else {
+- ctx = NULL;
+- }
+-
+ WITH_QEMU_LOCK_GUARD(&aio_context_list_lock) {
+ /*
+ * No need for memory barriers, this works in pair with
+@@ -197,17 +168,6 @@ void no_coroutine_fn bdrv_graph_wrunlock_ctx(AioContext *ctx)
+ * progress.
+ */
+ aio_bh_poll(qemu_get_aio_context());
+-
+- if (ctx) {
+- aio_context_acquire(ctx);
+- }
+-}
+-
+-void no_coroutine_fn bdrv_graph_wrunlock(BlockDriverState *bs)
+-{
+- AioContext *ctx = bs ? bdrv_get_aio_context(bs) : NULL;
+-
+- bdrv_graph_wrunlock_ctx(ctx);
+ }
+
+ void coroutine_fn bdrv_graph_co_rdlock(void)
+diff --git a/block/mirror.c b/block/mirror.c
+index abbddb39e..f9db6f0f7 100644
+--- a/block/mirror.c
++++ b/block/mirror.c
+@@ -768,7 +768,7 @@ static int mirror_exit_common(Job *job)
+ * check for an op blocker on @to_replace, and we have our own
+ * there.
+ */
+- bdrv_graph_wrlock(target_bs);
++ bdrv_graph_wrlock();
+ if (bdrv_recurse_can_replace(src, to_replace)) {
+ bdrv_replace_node(to_replace, target_bs, &local_err);
+ } else {
+@@ -777,7 +777,7 @@ static int mirror_exit_common(Job *job)
+ "would not lead to an abrupt change of visible data",
+ to_replace->node_name, target_bs->node_name);
+ }
+- bdrv_graph_wrunlock(target_bs);
++ bdrv_graph_wrunlock();
+ bdrv_drained_end(to_replace);
+ if (local_err) {
+ error_report_err(local_err);
+@@ -800,9 +800,9 @@ static int mirror_exit_common(Job *job)
+ * valid.
+ */
+ block_job_remove_all_bdrv(bjob);
+- bdrv_graph_wrlock(mirror_top_bs);
++ bdrv_graph_wrlock();
+ bdrv_replace_node(mirror_top_bs, mirror_top_bs->backing->bs, &error_abort);
+- bdrv_graph_wrunlock(mirror_top_bs);
++ bdrv_graph_wrunlock();
+
+ bdrv_drained_end(target_bs);
+ bdrv_unref(target_bs);
+@@ -1916,13 +1916,13 @@ static BlockJob *mirror_start_job(
+ */
+ bdrv_disable_dirty_bitmap(s->dirty_bitmap);
+
+- bdrv_graph_wrlock(bs);
++ bdrv_graph_wrlock();
+ ret = block_job_add_bdrv(&s->common, "source", bs, 0,
+ BLK_PERM_WRITE_UNCHANGED | BLK_PERM_WRITE |
+ BLK_PERM_CONSISTENT_READ,
+ errp);
+ if (ret < 0) {
+- bdrv_graph_wrunlock(bs);
++ bdrv_graph_wrunlock();
+ goto fail;
+ }
+
+@@ -1967,17 +1967,17 @@ static BlockJob *mirror_start_job(
+ ret = block_job_add_bdrv(&s->common, "intermediate node", iter, 0,
+ iter_shared_perms, errp);
+ if (ret < 0) {
+- bdrv_graph_wrunlock(bs);
++ bdrv_graph_wrunlock();
+ goto fail;
+ }
+ }
+
+ if (bdrv_freeze_backing_chain(mirror_top_bs, target, errp) < 0) {
+- bdrv_graph_wrunlock(bs);
++ bdrv_graph_wrunlock();
+ goto fail;
+ }
+ }
+- bdrv_graph_wrunlock(bs);
++ bdrv_graph_wrunlock();
+
+ QTAILQ_INIT(&s->ops_in_flight);
+
+@@ -2003,12 +2003,12 @@ fail:
+
+ bs_opaque->stop = true;
+ bdrv_drained_begin(bs);
+- bdrv_graph_wrlock(bs);
++ bdrv_graph_wrlock();
+ assert(mirror_top_bs->backing->bs == bs);
+ bdrv_child_refresh_perms(mirror_top_bs, mirror_top_bs->backing,
+ &error_abort);
+ bdrv_replace_node(mirror_top_bs, bs, &error_abort);
+- bdrv_graph_wrunlock(bs);
++ bdrv_graph_wrunlock();
+ bdrv_drained_end(bs);
+
+ bdrv_unref(mirror_top_bs);
+diff --git a/block/qcow2.c b/block/qcow2.c
+index 7af7c0bee..77dd49d4f 100644
+--- a/block/qcow2.c
++++ b/block/qcow2.c
+@@ -2822,9 +2822,9 @@ qcow2_do_close(BlockDriverState *bs, bool close_data_file)
+ if (close_data_file && has_data_file(bs)) {
+ GLOBAL_STATE_CODE();
+ bdrv_graph_rdunlock_main_loop();
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ bdrv_unref_child(bs, s->data_file);
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+ s->data_file = NULL;
+ bdrv_graph_rdlock_main_loop();
+ }
+diff --git a/block/quorum.c b/block/quorum.c
+index 505b8b3e1..db8fe891c 100644
+--- a/block/quorum.c
++++ b/block/quorum.c
+@@ -1037,14 +1037,14 @@ static int quorum_open(BlockDriverState *bs, QDict *options, int flags,
+
+ close_exit:
+ /* cleanup on error */
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ for (i = 0; i < s->num_children; i++) {
+ if (!opened[i]) {
+ continue;
+ }
+ bdrv_unref_child(bs, s->children[i]);
+ }
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+ g_free(s->children);
+ g_free(opened);
+ exit:
+@@ -1057,11 +1057,11 @@ static void quorum_close(BlockDriverState *bs)
+ BDRVQuorumState *s = bs->opaque;
+ int i;
+
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ for (i = 0; i < s->num_children; i++) {
+ bdrv_unref_child(bs, s->children[i]);
+ }
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+
+ g_free(s->children);
+ }
+diff --git a/block/replication.c b/block/replication.c
+index 5ded5f1ca..424b537ff 100644
+--- a/block/replication.c
++++ b/block/replication.c
+@@ -560,7 +560,7 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode,
+ return;
+ }
+
+- bdrv_graph_wrlock(bs);
++ bdrv_graph_wrlock();
+
+ bdrv_ref(hidden_disk->bs);
+ s->hidden_disk = bdrv_attach_child(bs, hidden_disk->bs, "hidden disk",
+@@ -568,7 +568,7 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode,
+ &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+- bdrv_graph_wrunlock(bs);
++ bdrv_graph_wrunlock();
+ aio_context_release(aio_context);
+ return;
+ }
+@@ -579,7 +579,7 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode,
+ BDRV_CHILD_DATA, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+- bdrv_graph_wrunlock(bs);
++ bdrv_graph_wrunlock();
+ aio_context_release(aio_context);
+ return;
+ }
+@@ -592,7 +592,7 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode,
+ if (!top_bs || !bdrv_is_root_node(top_bs) ||
+ !check_top_bs(top_bs, bs)) {
+ error_setg(errp, "No top_bs or it is invalid");
+- bdrv_graph_wrunlock(bs);
++ bdrv_graph_wrunlock();
+ reopen_backing_file(bs, false, NULL);
+ aio_context_release(aio_context);
+ return;
+@@ -600,7 +600,7 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode,
+ bdrv_op_block_all(top_bs, s->blocker);
+ bdrv_op_unblock(top_bs, BLOCK_OP_TYPE_DATAPLANE, s->blocker);
+
+- bdrv_graph_wrunlock(bs);
++ bdrv_graph_wrunlock();
+
+ s->backup_job = backup_job_create(
+ NULL, s->secondary_disk->bs, s->hidden_disk->bs,
+@@ -691,12 +691,12 @@ static void replication_done(void *opaque, int ret)
+ if (ret == 0) {
+ s->stage = BLOCK_REPLICATION_DONE;
+
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ bdrv_unref_child(bs, s->secondary_disk);
+ s->secondary_disk = NULL;
+ bdrv_unref_child(bs, s->hidden_disk);
+ s->hidden_disk = NULL;
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+
+ s->error = 0;
+ } else {
+diff --git a/block/snapshot.c b/block/snapshot.c
+index c4d40e80d..6fd720aef 100644
+--- a/block/snapshot.c
++++ b/block/snapshot.c
+@@ -292,9 +292,9 @@ int bdrv_snapshot_goto(BlockDriverState *bs,
+ }
+
+ /* .bdrv_open() will re-attach it */
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ bdrv_unref_child(bs, fallback);
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+
+ ret = bdrv_snapshot_goto(fallback_bs, snapshot_id, errp);
+ open_ret = drv->bdrv_open(bs, options, bs->open_flags, &local_err);
+diff --git a/block/stream.c b/block/stream.c
+index 01fe7c0f1..048c2d282 100644
+--- a/block/stream.c
++++ b/block/stream.c
+@@ -99,9 +99,9 @@ static int stream_prepare(Job *job)
+ }
+ }
+
+- bdrv_graph_wrlock(s->target_bs);
++ bdrv_graph_wrlock();
+ bdrv_set_backing_hd_drained(unfiltered_bs, base, &local_err);
+- bdrv_graph_wrunlock(s->target_bs);
++ bdrv_graph_wrunlock();
+
+ /*
+ * This call will do I/O, so the graph can change again from here on.
+@@ -366,10 +366,10 @@ void stream_start(const char *job_id, BlockDriverState *bs,
+ * already have our own plans. Also don't allow resize as the image size is
+ * queried only at the job start and then cached.
+ */
+- bdrv_graph_wrlock(bs);
++ bdrv_graph_wrlock();
+ if (block_job_add_bdrv(&s->common, "active node", bs, 0,
+ basic_flags | BLK_PERM_WRITE, errp)) {
+- bdrv_graph_wrunlock(bs);
++ bdrv_graph_wrunlock();
+ goto fail;
+ }
+
+@@ -389,11 +389,11 @@ void stream_start(const char *job_id, BlockDriverState *bs,
+ ret = block_job_add_bdrv(&s->common, "intermediate node", iter, 0,
+ basic_flags, errp);
+ if (ret < 0) {
+- bdrv_graph_wrunlock(bs);
++ bdrv_graph_wrunlock();
+ goto fail;
+ }
+ }
+- bdrv_graph_wrunlock(bs);
++ bdrv_graph_wrunlock();
+
+ s->base_overlay = base_overlay;
+ s->above_base = above_base;
+diff --git a/block/vmdk.c b/block/vmdk.c
+index d6971c706..bf78e1238 100644
+--- a/block/vmdk.c
++++ b/block/vmdk.c
+@@ -272,7 +272,7 @@ static void vmdk_free_extents(BlockDriverState *bs)
+ BDRVVmdkState *s = bs->opaque;
+ VmdkExtent *e;
+
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ for (i = 0; i < s->num_extents; i++) {
+ e = &s->extents[i];
+ g_free(e->l1_table);
+@@ -283,7 +283,7 @@ static void vmdk_free_extents(BlockDriverState *bs)
+ bdrv_unref_child(bs, e->file);
+ }
+ }
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+
+ g_free(s->extents);
+ }
+@@ -1247,9 +1247,9 @@ vmdk_parse_extents(const char *desc, BlockDriverState *bs, QDict *options,
+ 0, 0, 0, 0, 0, &extent, errp);
+ if (ret < 0) {
+ bdrv_graph_rdunlock_main_loop();
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ bdrv_unref_child(bs, extent_file);
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+ bdrv_graph_rdlock_main_loop();
+ goto out;
+ }
+@@ -1266,9 +1266,9 @@ vmdk_parse_extents(const char *desc, BlockDriverState *bs, QDict *options,
+ g_free(buf);
+ if (ret) {
+ bdrv_graph_rdunlock_main_loop();
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ bdrv_unref_child(bs, extent_file);
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+ bdrv_graph_rdlock_main_loop();
+ goto out;
+ }
+@@ -1277,9 +1277,9 @@ vmdk_parse_extents(const char *desc, BlockDriverState *bs, QDict *options,
+ ret = vmdk_open_se_sparse(bs, extent_file, bs->open_flags, errp);
+ if (ret) {
+ bdrv_graph_rdunlock_main_loop();
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ bdrv_unref_child(bs, extent_file);
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+ bdrv_graph_rdlock_main_loop();
+ goto out;
+ }
+@@ -1287,9 +1287,9 @@ vmdk_parse_extents(const char *desc, BlockDriverState *bs, QDict *options,
+ } else {
+ error_setg(errp, "Unsupported extent type '%s'", type);
+ bdrv_graph_rdunlock_main_loop();
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ bdrv_unref_child(bs, extent_file);
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+ bdrv_graph_rdlock_main_loop();
+ ret = -ENOTSUP;
+ goto out;
+diff --git a/blockdev.c b/blockdev.c
+index c91f49e7b..9e1381169 100644
+--- a/blockdev.c
++++ b/blockdev.c
+@@ -1611,9 +1611,9 @@ static void external_snapshot_abort(void *opaque)
+ }
+
+ bdrv_drained_begin(state->new_bs);
+- bdrv_graph_wrlock(state->old_bs);
++ bdrv_graph_wrlock();
+ bdrv_replace_node(state->new_bs, state->old_bs, &error_abort);
+- bdrv_graph_wrunlock(state->old_bs);
++ bdrv_graph_wrunlock();
+ bdrv_drained_end(state->new_bs);
+
+ bdrv_unref(state->old_bs); /* bdrv_replace_node() ref'ed old_bs */
+@@ -3657,7 +3657,7 @@ void qmp_x_blockdev_change(const char *parent, const char *child,
+ BlockDriverState *parent_bs, *new_bs = NULL;
+ BdrvChild *p_child;
+
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+
+ parent_bs = bdrv_lookup_bs(parent, parent, errp);
+ if (!parent_bs) {
+@@ -3693,7 +3693,7 @@ void qmp_x_blockdev_change(const char *parent, const char *child,
+ }
+
+ out:
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+ }
+
+ BlockJobInfoList *qmp_query_block_jobs(Error **errp)
+diff --git a/blockjob.c b/blockjob.c
+index b7a29052b..731041231 100644
+--- a/blockjob.c
++++ b/blockjob.c
+@@ -199,7 +199,7 @@ void block_job_remove_all_bdrv(BlockJob *job)
+ * to process an already freed BdrvChild.
+ */
+ aio_context_release(job->job.aio_context);
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ aio_context_acquire(job->job.aio_context);
+ while (job->nodes) {
+ GSList *l = job->nodes;
+@@ -212,7 +212,7 @@ void block_job_remove_all_bdrv(BlockJob *job)
+
+ g_slist_free_1(l);
+ }
+- bdrv_graph_wrunlock_ctx(job->job.aio_context);
++ bdrv_graph_wrunlock();
+ }
+
+ bool block_job_has_bdrv(BlockJob *job, BlockDriverState *bs)
+@@ -514,7 +514,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
+ int ret;
+ GLOBAL_STATE_CODE();
+
+- bdrv_graph_wrlock(bs);
++ bdrv_graph_wrlock();
+
+ if (job_id == NULL && !(flags & JOB_INTERNAL)) {
+ job_id = bdrv_get_device_name(bs);
+@@ -523,7 +523,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
+ job = job_create(job_id, &driver->job_driver, txn, bdrv_get_aio_context(bs),
+ flags, cb, opaque, errp);
+ if (job == NULL) {
+- bdrv_graph_wrunlock(bs);
++ bdrv_graph_wrunlock();
+ return NULL;
+ }
+
+@@ -563,11 +563,11 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
+ goto fail;
+ }
+
+- bdrv_graph_wrunlock(bs);
++ bdrv_graph_wrunlock();
+ return job;
+
+ fail:
+- bdrv_graph_wrunlock(bs);
++ bdrv_graph_wrunlock();
+ job_early_fail(&job->job);
+ return NULL;
+ }
+diff --git a/include/block/graph-lock.h b/include/block/graph-lock.h
+index 22b5db1ed..d7545e82d 100644
+--- a/include/block/graph-lock.h
++++ b/include/block/graph-lock.h
+@@ -110,34 +110,17 @@ void unregister_aiocontext(AioContext *ctx);
+ *
+ * The wrlock can only be taken from the main loop, with BQL held, as only the
+ * main loop is allowed to modify the graph.
+- *
+- * If @bs is non-NULL, its AioContext is temporarily released.
+- *
+- * This function polls. Callers must not hold the lock of any AioContext other
+- * than the current one and the one of @bs.
+ */
+ void no_coroutine_fn TSA_ACQUIRE(graph_lock) TSA_NO_TSA
+-bdrv_graph_wrlock(BlockDriverState *bs);
++bdrv_graph_wrlock(void);
+
+ /*
+ * bdrv_graph_wrunlock:
+ * Write finished, reset global has_writer to 0 and restart
+ * all readers that are waiting.
+- *
+- * If @bs is non-NULL, its AioContext is temporarily released.
+- */
+-void no_coroutine_fn TSA_RELEASE(graph_lock) TSA_NO_TSA
+-bdrv_graph_wrunlock(BlockDriverState *bs);
+-
+-/*
+- * bdrv_graph_wrunlock_ctx:
+- * Write finished, reset global has_writer to 0 and restart
+- * all readers that are waiting.
+- *
+- * If @ctx is non-NULL, its lock is temporarily released.
+ */
+ void no_coroutine_fn TSA_RELEASE(graph_lock) TSA_NO_TSA
+-bdrv_graph_wrunlock_ctx(AioContext *ctx);
++bdrv_graph_wrunlock(void);
+
+ /*
+ * bdrv_graph_co_rdlock:
+diff --git a/scripts/block-coroutine-wrapper.py b/scripts/block-coroutine-wrapper.py
+index a38e5833f..38364fa55 100644
+--- a/scripts/block-coroutine-wrapper.py
++++ b/scripts/block-coroutine-wrapper.py
+@@ -261,8 +261,8 @@ def gen_no_co_wrapper(func: FuncDecl) -> str:
+ graph_lock=' bdrv_graph_rdlock_main_loop();'
+ graph_unlock=' bdrv_graph_rdunlock_main_loop();'
+ elif func.graph_wrlock:
+- graph_lock=' bdrv_graph_wrlock(NULL);'
+- graph_unlock=' bdrv_graph_wrunlock(NULL);'
++ graph_lock=' bdrv_graph_wrlock();'
++ graph_unlock=' bdrv_graph_wrunlock();'
+
+ return f"""\
+ /*
+diff --git a/tests/unit/test-bdrv-drain.c b/tests/unit/test-bdrv-drain.c
+index 704d1a3f3..d9754dfeb 100644
+--- a/tests/unit/test-bdrv-drain.c
++++ b/tests/unit/test-bdrv-drain.c
+@@ -807,9 +807,9 @@ static void test_blockjob_common_drain_node(enum drain_type drain_type,
+ tjob->bs = src;
+ job = &tjob->common;
+
+- bdrv_graph_wrlock(target);
++ bdrv_graph_wrlock();
+ block_job_add_bdrv(job, "target", target, 0, BLK_PERM_ALL, &error_abort);
+- bdrv_graph_wrunlock(target);
++ bdrv_graph_wrunlock();
+
+ switch (result) {
+ case TEST_JOB_SUCCESS:
+@@ -991,11 +991,11 @@ static void bdrv_test_top_close(BlockDriverState *bs)
+ {
+ BdrvChild *c, *next_c;
+
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ QLIST_FOREACH_SAFE(c, &bs->children, next, next_c) {
+ bdrv_unref_child(bs, c);
+ }
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+ }
+
+ static int coroutine_fn GRAPH_RDLOCK
+@@ -1085,10 +1085,10 @@ static void do_test_delete_by_drain(bool detach_instead_of_delete,
+
+ null_bs = bdrv_open("null-co://", NULL, NULL, BDRV_O_RDWR | BDRV_O_PROTOCOL,
+ &error_abort);
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ bdrv_attach_child(bs, null_bs, "null-child", &child_of_bds,
+ BDRV_CHILD_DATA, &error_abort);
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+
+ /* This child will be the one to pass to requests through to, and
+ * it will stall until a drain occurs */
+@@ -1096,21 +1096,21 @@ static void do_test_delete_by_drain(bool detach_instead_of_delete,
+ &error_abort);
+ child_bs->total_sectors = 65536 >> BDRV_SECTOR_BITS;
+ /* Takes our reference to child_bs */
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ tts->wait_child = bdrv_attach_child(bs, child_bs, "wait-child",
+ &child_of_bds,
+ BDRV_CHILD_DATA | BDRV_CHILD_PRIMARY,
+ &error_abort);
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+
+ /* This child is just there to be deleted
+ * (for detach_instead_of_delete == true) */
+ null_bs = bdrv_open("null-co://", NULL, NULL, BDRV_O_RDWR | BDRV_O_PROTOCOL,
+ &error_abort);
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ bdrv_attach_child(bs, null_bs, "null-child", &child_of_bds, BDRV_CHILD_DATA,
+ &error_abort);
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+
+ blk = blk_new(qemu_get_aio_context(), BLK_PERM_ALL, BLK_PERM_ALL);
+ blk_insert_bs(blk, bs, &error_abort);
+@@ -1193,14 +1193,14 @@ static void no_coroutine_fn detach_indirect_bh(void *opaque)
+
+ bdrv_dec_in_flight(data->child_b->bs);
+
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ bdrv_unref_child(data->parent_b, data->child_b);
+
+ bdrv_ref(data->c);
+ data->child_c = bdrv_attach_child(data->parent_b, data->c, "PB-C",
+ &child_of_bds, BDRV_CHILD_DATA,
+ &error_abort);
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+ }
+
+ static void coroutine_mixed_fn detach_by_parent_aio_cb(void *opaque, int ret)
+@@ -1298,7 +1298,7 @@ static void TSA_NO_TSA test_detach_indirect(bool by_parent_cb)
+ /* Set child relationships */
+ bdrv_ref(b);
+ bdrv_ref(a);
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ child_b = bdrv_attach_child(parent_b, b, "PB-B", &child_of_bds,
+ BDRV_CHILD_DATA, &error_abort);
+ child_a = bdrv_attach_child(parent_b, a, "PB-A", &child_of_bds,
+@@ -1308,7 +1308,7 @@ static void TSA_NO_TSA test_detach_indirect(bool by_parent_cb)
+ bdrv_attach_child(parent_a, a, "PA-A",
+ by_parent_cb ? &child_of_bds : &detach_by_driver_cb_class,
+ BDRV_CHILD_DATA, &error_abort);
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+
+ g_assert_cmpint(parent_a->refcnt, ==, 1);
+ g_assert_cmpint(parent_b->refcnt, ==, 1);
+@@ -1727,7 +1727,7 @@ static void test_drop_intermediate_poll(void)
+ * Establish the chain last, so the chain links are the first
+ * elements in the BDS.parents lists
+ */
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ for (i = 0; i < 3; i++) {
+ if (i) {
+ /* Takes the reference to chain[i - 1] */
+@@ -1735,7 +1735,7 @@ static void test_drop_intermediate_poll(void)
+ &chain_child_class, BDRV_CHILD_COW, &error_abort);
+ }
+ }
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+
+ job = block_job_create("job", &test_simple_job_driver, NULL, job_node,
+ 0, BLK_PERM_ALL, 0, 0, NULL, NULL, &error_abort);
+@@ -1982,10 +1982,10 @@ static void do_test_replace_child_mid_drain(int old_drain_count,
+ new_child_bs->total_sectors = 1;
+
+ bdrv_ref(old_child_bs);
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ bdrv_attach_child(parent_bs, old_child_bs, "child", &child_of_bds,
+ BDRV_CHILD_COW, &error_abort);
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+ parent_s->setup_completed = true;
+
+ for (i = 0; i < old_drain_count; i++) {
+@@ -2016,9 +2016,9 @@ static void do_test_replace_child_mid_drain(int old_drain_count,
+ g_assert(parent_bs->quiesce_counter == old_drain_count);
+ bdrv_drained_begin(old_child_bs);
+ bdrv_drained_begin(new_child_bs);
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ bdrv_replace_node(old_child_bs, new_child_bs, &error_abort);
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+ bdrv_drained_end(new_child_bs);
+ bdrv_drained_end(old_child_bs);
+ g_assert(parent_bs->quiesce_counter == new_drain_count);
+diff --git a/tests/unit/test-bdrv-graph-mod.c b/tests/unit/test-bdrv-graph-mod.c
+index 074adcbb9..8ee6ef38d 100644
+--- a/tests/unit/test-bdrv-graph-mod.c
++++ b/tests/unit/test-bdrv-graph-mod.c
+@@ -137,10 +137,10 @@ static void test_update_perm_tree(void)
+
+ blk_insert_bs(root, bs, &error_abort);
+
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ bdrv_attach_child(filter, bs, "child", &child_of_bds,
+ BDRV_CHILD_DATA, &error_abort);
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+
+ aio_context_acquire(qemu_get_aio_context());
+ ret = bdrv_append(filter, bs, NULL);
+@@ -206,11 +206,11 @@ static void test_should_update_child(void)
+
+ bdrv_set_backing_hd(target, bs, &error_abort);
+
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ g_assert(target->backing->bs == bs);
+ bdrv_attach_child(filter, target, "target", &child_of_bds,
+ BDRV_CHILD_DATA, &error_abort);
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+ aio_context_acquire(qemu_get_aio_context());
+ bdrv_append(filter, bs, &error_abort);
+ aio_context_release(qemu_get_aio_context());
+@@ -248,7 +248,7 @@ static void test_parallel_exclusive_write(void)
+ bdrv_ref(base);
+ bdrv_ref(fl1);
+
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ bdrv_attach_child(top, fl1, "backing", &child_of_bds,
+ BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
+ &error_abort);
+@@ -260,7 +260,7 @@ static void test_parallel_exclusive_write(void)
+ &error_abort);
+
+ bdrv_replace_node(fl1, fl2, &error_abort);
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+
+ bdrv_drained_end(fl2);
+ bdrv_drained_end(fl1);
+@@ -367,7 +367,7 @@ static void test_parallel_perm_update(void)
+ */
+ bdrv_ref(base);
+
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ bdrv_attach_child(top, ws, "file", &child_of_bds, BDRV_CHILD_DATA,
+ &error_abort);
+ c_fl1 = bdrv_attach_child(ws, fl1, "first", &child_of_bds,
+@@ -380,7 +380,7 @@ static void test_parallel_perm_update(void)
+ bdrv_attach_child(fl2, base, "backing", &child_of_bds,
+ BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
+ &error_abort);
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+
+ /* Select fl1 as first child to be active */
+ s->selected = c_fl1;
+@@ -434,11 +434,11 @@ static void test_append_greedy_filter(void)
+ BlockDriverState *base = no_perm_node("base");
+ BlockDriverState *fl = exclusive_writer_node("fl1");
+
+- bdrv_graph_wrlock(NULL);
++ bdrv_graph_wrlock();
+ bdrv_attach_child(top, base, "backing", &child_of_bds,
+ BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
+ &error_abort);
+- bdrv_graph_wrunlock(NULL);
++ bdrv_graph_wrunlock();
+
+ aio_context_acquire(qemu_get_aio_context());
+ bdrv_append(fl, base, &error_abort);
+--
+2.40.0
diff --git a/meta/recipes-devtools/qemu/qemu/CVE-2024-4467-0005.patch b/meta/recipes-devtools/qemu/qemu/CVE-2024-4467-0005.patch
new file mode 100644
index 0000000000..bcdd0fbed8
--- /dev/null
+++ b/meta/recipes-devtools/qemu/qemu/CVE-2024-4467-0005.patch
@@ -0,0 +1,239 @@
+From 7ead946998610657d38d1a505d5f25300d4ca613 Mon Sep 17 00:00:00 2001
+From: Kevin Wolf <kwolf@redhat.com>
+Date: Thu, 25 Apr 2024 14:56:02 +0000
+Subject: [PATCH] block: Parse filenames only when explicitly requested
+
+When handling image filenames from legacy options such as -drive or from
+tools, these filenames are parsed for protocol prefixes, including for
+the json:{} pseudo-protocol.
+
+This behaviour is intended for filenames that come directly from the
+command line and for backing files, which may come from the image file
+itself. Higher level management tools generally take care to verify that
+untrusted images don't contain a bad (or any) backing file reference;
+'qemu-img info' is a suitable tool for this.
+
+However, for other files that can be referenced in images, such as
+qcow2 data files or VMDK extents, the string from the image file is
+usually not verified by management tools - and 'qemu-img info' wouldn't
+be suitable because in contrast to backing files, it already opens these
+other referenced files. So here the string should be interpreted as a
+literal local filename. More complex configurations need to be specified
+explicitly on the command line or in QMP...
+
+CVE: CVE-2024-4467
+Upstream-Status: Backport [https://gitlab.com/qemu-project/qemu/-/commit/7ead946998610657d38d1a505d5f25300d4ca613]
+
+Signed-off-by: Yogita Urade <yogita.urade@windriver.com>
+---
+ block.c | 94 ++++++++++++++++++++++++++++++++++-----------------------
+ 1 file changed, 57 insertions(+), 37 deletions(-)
+
+diff --git a/block.c b/block.c
+index 25e1ebc60..f3cb32cd7 100644
+--- a/block.c
++++ b/block.c
+@@ -86,6 +86,7 @@ static BlockDriverState *bdrv_open_inherit(const char *filename,
+ BlockDriverState *parent,
+ const BdrvChildClass *child_class,
+ BdrvChildRole child_role,
++ bool parse_filename,
+ Error **errp);
+
+ static bool bdrv_recurse_has_child(BlockDriverState *bs,
+@@ -2047,7 +2048,8 @@ static void parse_json_protocol(QDict *options, const char **pfilename,
+ * block driver has been specified explicitly.
+ */
+ static int bdrv_fill_options(QDict **options, const char *filename,
+- int *flags, Error **errp)
++ int *flags, bool allow_parse_filename,
++ Error **errp)
+ {
+ const char *drvname;
+ bool protocol = *flags & BDRV_O_PROTOCOL;
+@@ -2089,7 +2091,7 @@ static int bdrv_fill_options(QDict **options, const char *filename,
+ if (protocol && filename) {
+ if (!qdict_haskey(*options, "filename")) {
+ qdict_put_str(*options, "filename", filename);
+- parse_filename = true;
++ parse_filename = allow_parse_filename;
+ } else {
+ error_setg(errp, "Can't specify 'file' and 'filename' options at "
+ "the same time");
+@@ -3675,7 +3677,8 @@ int bdrv_open_backing_file(BlockDriverState *bs, QDict *parent_options,
+ }
+
+ backing_hd = bdrv_open_inherit(backing_filename, reference, options, 0, bs,
+- &child_of_bds, bdrv_backing_role(bs), errp);
++ &child_of_bds, bdrv_backing_role(bs), true,
++ errp);
+ if (!backing_hd) {
+ bs->open_flags |= BDRV_O_NO_BACKING;
+ error_prepend(errp, "Could not open backing file: ");
+@@ -3712,7 +3715,8 @@ free_exit:
+ static BlockDriverState *
+ bdrv_open_child_bs(const char *filename, QDict *options, const char *bdref_key,
+ BlockDriverState *parent, const BdrvChildClass *child_class,
+- BdrvChildRole child_role, bool allow_none, Error **errp)
++ BdrvChildRole child_role, bool allow_none,
++ bool parse_filename, Error **errp)
+ {
+ BlockDriverState *bs = NULL;
+ QDict *image_options;
+@@ -3743,7 +3747,8 @@ bdrv_open_child_bs(const char *filename, QDict *options, const char *bdref_key,
+ }
+
+ bs = bdrv_open_inherit(filename, reference, image_options, 0,
+- parent, child_class, child_role, errp);
++ parent, child_class, child_role, parse_filename,
++ errp);
+ if (!bs) {
+ goto done;
+ }
+@@ -3753,6 +3758,33 @@ done:
+ return bs;
+ }
+
++static BdrvChild *bdrv_open_child_common(const char *filename,
++ QDict *options, const char *bdref_key,
++ BlockDriverState *parent,
++ const BdrvChildClass *child_class,
++ BdrvChildRole child_role,
++ bool allow_none, bool parse_filename,
++ Error **errp)
++{
++ BlockDriverState *bs;
++ BdrvChild *child;
++
++ GLOBAL_STATE_CODE();
++
++ bs = bdrv_open_child_bs(filename, options, bdref_key, parent, child_class,
++ child_role, allow_none, parse_filename, errp);
++ if (bs == NULL) {
++ return NULL;
++ }
++
++ bdrv_graph_wrlock();
++ child = bdrv_attach_child(parent, bs, bdref_key, child_class, child_role,
++ errp);
++ bdrv_graph_wrunlock();
++
++ return child;
++}
++
+ /*
+ * Opens a disk image whose options are given as BlockdevRef in another block
+ * device's options.
+@@ -3778,31 +3810,15 @@ BdrvChild *bdrv_open_child(const char *filename,
+ BdrvChildRole child_role,
+ bool allow_none, Error **errp)
+ {
+- BlockDriverState *bs;
+- BdrvChild *child;
+- AioContext *ctx;
+-
+- GLOBAL_STATE_CODE();
+-
+- bs = bdrv_open_child_bs(filename, options, bdref_key, parent, child_class,
+- child_role, allow_none, errp);
+- if (bs == NULL) {
+- return NULL;
+- }
+-
+- bdrv_graph_wrlock();
+- ctx = bdrv_get_aio_context(bs);
+- aio_context_acquire(ctx);
+- child = bdrv_attach_child(parent, bs, bdref_key, child_class, child_role,
+- errp);
+- aio_context_release(ctx);
+- bdrv_graph_wrunlock();
+-
+- return child;
++ return bdrv_open_child_common(filename, options, bdref_key, parent,
++ child_class, child_role, allow_none, false,
++ errp);
+ }
+
+ /*
+- * Wrapper on bdrv_open_child() for most popular case: open primary child of bs.
++ * This does mostly the same as bdrv_open_child(), but for opening the primary
++ * child of a node. A notable difference from bdrv_open_child() is that it
++ * enables filename parsing for protocol names (including json:).
+ *
+ * The caller must hold the lock of the main AioContext and no other AioContext.
+ * @parent can move to a different AioContext in this function. Callers must
+@@ -3819,8 +3835,8 @@ int bdrv_open_file_child(const char *filename,
+ role = parent->drv->is_filter ?
+ (BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY) : BDRV_CHILD_IMAGE;
+
+- if (!bdrv_open_child(filename, options, bdref_key, parent,
+- &child_of_bds, role, false, errp))
++ if (!bdrv_open_child_common(filename, options, bdref_key, parent,
++ &child_of_bds, role, false, true, errp))
+ {
+ return -EINVAL;
+ }
+@@ -3865,7 +3881,8 @@ BlockDriverState *bdrv_open_blockdev_ref(BlockdevRef *ref, Error **errp)
+
+ }
+
+- bs = bdrv_open_inherit(NULL, reference, qdict, 0, NULL, NULL, 0, errp);
++ bs = bdrv_open_inherit(NULL, reference, qdict, 0, NULL, NULL, 0, false,
++ errp);
+ obj = NULL;
+ qobject_unref(obj);
+ visit_free(v);
+@@ -3962,7 +3979,7 @@ static BlockDriverState * no_coroutine_fn
+ bdrv_open_inherit(const char *filename, const char *reference, QDict *options,
+ int flags, BlockDriverState *parent,
+ const BdrvChildClass *child_class, BdrvChildRole child_role,
+- Error **errp)
++ bool parse_filename, Error **errp)
+ {
+ int ret;
+ BlockBackend *file = NULL;
+@@ -4011,9 +4028,11 @@ bdrv_open_inherit(const char *filename, const char *reference, QDict *options,
+ }
+
+ /* json: syntax counts as explicit options, as if in the QDict */
+- parse_json_protocol(options, &filename, &local_err);
+- if (local_err) {
+- goto fail;
++ if (parse_filename) {
++ parse_json_protocol(options, &filename, &local_err);
++ if (local_err) {
++ goto fail;
++ }
+ }
+
+ bs->explicit_options = qdict_clone_shallow(options);
+@@ -4038,7 +4057,8 @@ bdrv_open_inherit(const char *filename, const char *reference, QDict *options,
+ parent->open_flags, parent->options);
+ }
+
+- ret = bdrv_fill_options(&options, filename, &flags, &local_err);
++ ret = bdrv_fill_options(&options, filename, &flags, parse_filename,
++ &local_err);
+ if (ret < 0) {
+ goto fail;
+ }
+@@ -4107,7 +4127,7 @@ bdrv_open_inherit(const char *filename, const char *reference, QDict *options,
+
+ file_bs = bdrv_open_child_bs(filename, options, "file", bs,
+ &child_of_bds, BDRV_CHILD_IMAGE,
+- true, &local_err);
++ true, true, &local_err);
+ if (local_err) {
+ goto fail;
+ }
+@@ -4270,7 +4290,7 @@ BlockDriverState *bdrv_open(const char *filename, const char *reference,
+ GLOBAL_STATE_CODE();
+
+ return bdrv_open_inherit(filename, reference, options, flags, NULL,
+- NULL, 0, errp);
++ NULL, 0, true, errp);
+ }
+
+ /* Return true if the NULL-terminated @list contains @str */
+--
+2.40.0
--
2.34.1
^ permalink raw reply related [flat|nested] 19+ messages in thread* [OE-core][scarthgap 03/12] curl: Patch CVE-2024-7264
2024-08-29 13:32 [OE-core][scarthgap 00/12] Patch review Steve Sakoman
2024-08-29 13:32 ` [OE-core][scarthgap 01/12] qemu: fix CVE-2024-4467 Steve Sakoman
2024-08-29 13:32 ` [OE-core][scarthgap 02/12] libyaml: Ignore CVE-2024-35325 Steve Sakoman
@ 2024-08-29 13:32 ` Steve Sakoman
2024-08-29 13:32 ` [OE-core][scarthgap 04/12] curl: Ignore CVE-2024-32928 Steve Sakoman
` (8 subsequent siblings)
11 siblings, 0 replies; 19+ messages in thread
From: Steve Sakoman @ 2024-08-29 13:32 UTC (permalink / raw)
To: openembedded-core
From: Peter Marko <peter.marko@siemens.com>
Pick commits per https://curl.se/docs/CVE-2024-7264.html
Signed-off-by: Peter Marko <peter.marko@siemens.com>
Signed-off-by: Steve Sakoman <steve@sakoman.com>
---
.../curl/curl/CVE-2024-7264-1.patch | 61 ++++
.../curl/curl/CVE-2024-7264-2.patch | 316 ++++++++++++++++++
meta/recipes-support/curl/curl_8.7.1.bb | 2 +
3 files changed, 379 insertions(+)
create mode 100644 meta/recipes-support/curl/curl/CVE-2024-7264-1.patch
create mode 100644 meta/recipes-support/curl/curl/CVE-2024-7264-2.patch
diff --git a/meta/recipes-support/curl/curl/CVE-2024-7264-1.patch b/meta/recipes-support/curl/curl/CVE-2024-7264-1.patch
new file mode 100644
index 0000000000..7101fcfe35
--- /dev/null
+++ b/meta/recipes-support/curl/curl/CVE-2024-7264-1.patch
@@ -0,0 +1,61 @@
+From 3c914bc680155b32178f1f15ca8d47c7f4640afe Mon Sep 17 00:00:00 2001
+From: Daniel Stenberg <daniel@haxx.se>
+Date: Tue, 30 Jul 2024 10:05:17 +0200
+Subject: [PATCH] x509asn1: clean up GTime2str
+
+Co-authored-by: Stefan Eissing
+Reported-by: Dov Murik
+
+Closes #14307
+
+CVE: CVE-2024-7264
+Upstream-Status: Backport [https://github.com/curl/curl/commit/3c914bc680155b32178f1f15ca8d47c7f4640afe.patch]
+Signed-off-by: Peter Marko <peter.marko@siemens.com>
+---
+ lib/vtls/x509asn1.c | 23 ++++++++++++++---------
+ 1 file changed, 14 insertions(+), 9 deletions(-)
+
+diff --git a/lib/vtls/x509asn1.c b/lib/vtls/x509asn1.c
+index 1bc4243ddae343..e3a9fe4232a4ea 100644
+--- a/lib/vtls/x509asn1.c
++++ b/lib/vtls/x509asn1.c
+@@ -488,7 +488,7 @@ static CURLcode GTime2str(struct dynbuf *store,
+ /* Convert an ASN.1 Generalized time to a printable string.
+ Return the dynamically allocated string, or NULL if an error occurs. */
+
+- for(fracp = beg; fracp < end && *fracp >= '0' && *fracp <= '9'; fracp++)
++ for(fracp = beg; fracp < end && ISDIGIT(*fracp); fracp++)
+ ;
+
+ /* Get seconds digits. */
+@@ -507,17 +507,22 @@ static CURLcode GTime2str(struct dynbuf *store,
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ }
+
+- /* Scan for timezone, measure fractional seconds. */
++ /* timezone follows optional fractional seconds. */
+ tzp = fracp;
+- fracl = 0;
++ fracl = 0; /* no fractional seconds detected so far */
+ if(fracp < end && (*fracp == '.' || *fracp == ',')) {
+- fracp++;
+- do
++ /* Have fractional seconds, e.g. "[.,]\d+". How many? */
++ tzp = fracp++; /* should be a digit char or BAD ARGUMENT */
++ while(tzp < end && ISDIGIT(*tzp))
+ tzp++;
+- while(tzp < end && *tzp >= '0' && *tzp <= '9');
+- /* Strip leading zeroes in fractional seconds. */
+- for(fracl = tzp - fracp - 1; fracl && fracp[fracl - 1] == '0'; fracl--)
+- ;
++ if(tzp == fracp) /* never looped, no digit after [.,] */
++ return CURLE_BAD_FUNCTION_ARGUMENT;
++ fracl = tzp - fracp - 1; /* number of fractional sec digits */
++ DEBUGASSERT(fracl > 0);
++ /* Strip trailing zeroes in fractional seconds.
++ * May reduce fracl to 0 if only '0's are present. */
++ while(fracl && fracp[fracl - 1] == '0')
++ fracl--;
+ }
+
+ /* Process timezone. */
diff --git a/meta/recipes-support/curl/curl/CVE-2024-7264-2.patch b/meta/recipes-support/curl/curl/CVE-2024-7264-2.patch
new file mode 100644
index 0000000000..ab24911712
--- /dev/null
+++ b/meta/recipes-support/curl/curl/CVE-2024-7264-2.patch
@@ -0,0 +1,316 @@
+From 27959ecce75cdb2809c0bdb3286e60e08fadb519 Mon Sep 17 00:00:00 2001
+From: Stefan Eissing <stefan@eissing.org>
+Date: Tue, 30 Jul 2024 16:40:48 +0200
+Subject: [PATCH] x509asn1: unittests and fixes for gtime2str
+
+Fix issues in GTime2str() and add unit test cases to verify correct
+behaviour.
+
+Follow-up to 3c914bc6801
+
+Closes #14316
+
+CVE: CVE-2024-7264
+Upstream-Status: Backport [https://github.com/curl/curl/commit/27959ecce75cdb2809c0bdb3286e60e08fadb519.patch]
+Signed-off-by: Peter Marko <peter.marko@siemens.com>
+---
+ lib/vtls/x509asn1.c | 32 +++++++---
+ lib/vtls/x509asn1.h | 11 ++++
+ tests/data/Makefile.inc | 2 +-
+ tests/data/test1656 | 22 +++++++
+ tests/unit/Makefile.inc | 4 +-
+ tests/unit/unit1656.c | 133 ++++++++++++++++++++++++++++++++++++++++
+ 6 files changed, 194 insertions(+), 10 deletions(-)
+ create mode 100644 tests/data/test1656
+ create mode 100644 tests/unit/unit1656.c
+
+diff --git a/lib/vtls/x509asn1.c b/lib/vtls/x509asn1.c
+index e3a9fe4232a4ea..7f04af3b9778c5 100644
+--- a/lib/vtls/x509asn1.c
++++ b/lib/vtls/x509asn1.c
+@@ -512,12 +512,13 @@ static CURLcode GTime2str(struct dynbuf *store,
+ fracl = 0; /* no fractional seconds detected so far */
+ if(fracp < end && (*fracp == '.' || *fracp == ',')) {
+ /* Have fractional seconds, e.g. "[.,]\d+". How many? */
+- tzp = fracp++; /* should be a digit char or BAD ARGUMENT */
++ fracp++; /* should be a digit char or BAD ARGUMENT */
++ tzp = fracp;
+ while(tzp < end && ISDIGIT(*tzp))
+ tzp++;
+ if(tzp == fracp) /* never looped, no digit after [.,] */
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+- fracl = tzp - fracp - 1; /* number of fractional sec digits */
++ fracl = tzp - fracp; /* number of fractional sec digits */
+ DEBUGASSERT(fracl > 0);
+ /* Strip trailing zeroes in fractional seconds.
+ * May reduce fracl to 0 if only '0's are present. */
+@@ -526,18 +527,24 @@ static CURLcode GTime2str(struct dynbuf *store,
+ }
+
+ /* Process timezone. */
+- if(tzp >= end)
+- ; /* Nothing to do. */
++ if(tzp >= end) {
++ tzp = "";
++ tzl = 0;
++ }
+ else if(*tzp == 'Z') {
+- tzp = " GMT";
+- end = tzp + 4;
++ sep = " ";
++ tzp = "GMT";
++ tzl = 3;
++ }
++ else if((*tzp == '+') || (*tzp == '-')) {
++ sep = " UTC";
++ tzl = end - tzp;
+ }
+ else {
+ sep = " ";
+- tzp++;
++ tzl = end - tzp;
+ }
+
+- tzl = end - tzp;
+ return Curl_dyn_addf(store,
+ "%.4s-%.2s-%.2s %.2s:%.2s:%c%c%s%.*s%s%.*s",
+ beg, beg + 4, beg + 6,
+@@ -546,6 +553,15 @@ static CURLcode GTime2str(struct dynbuf *store,
+ sep, (int)tzl, tzp);
+ }
+
++#ifdef UNITTESTS
++/* used by unit1656.c */
++CURLcode Curl_x509_GTime2str(struct dynbuf *store,
++ const char *beg, const char *end)
++{
++ return GTime2str(store, beg, end);
++}
++#endif
++
+ /*
+ * Convert an ASN.1 UTC time to a printable string.
+ *
+diff --git a/lib/vtls/x509asn1.h b/lib/vtls/x509asn1.h
+index 5844460467ccef..5b48596c75910a 100644
+--- a/lib/vtls/x509asn1.h
++++ b/lib/vtls/x509asn1.h
+@@ -76,5 +76,16 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, int certnum,
+ const char *beg, const char *end);
+ CURLcode Curl_verifyhost(struct Curl_cfilter *cf, struct Curl_easy *data,
+ const char *beg, const char *end);
++
++#ifdef UNITTESTS
++#if defined(USE_GNUTLS) || defined(USE_SCHANNEL) || defined(USE_SECTRANSP) || \
++ defined(USE_MBEDTLS)
++
++/* used by unit1656.c */
++CURLcode Curl_x509_GTime2str(struct dynbuf *store,
++ const char *beg, const char *end);
++#endif
++#endif
++
+ #endif /* USE_GNUTLS or USE_WOLFSSL or USE_SCHANNEL or USE_SECTRANSP */
+ #endif /* HEADER_CURL_X509ASN1_H */
+diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc
+index d0e20df4b900c8..792cb16eef20ad 100644
+--- a/tests/data/Makefile.inc
++++ b/tests/data/Makefile.inc
+@@ -210,7 +210,7 @@ test1620 test1621 \
+ \
+ test1630 test1631 test1632 test1633 test1634 test1635 \
+ \
+-test1650 test1651 test1652 test1653 test1654 test1655 \
++test1650 test1651 test1652 test1653 test1654 test1655 test1656 \
+ test1660 test1661 test1662 \
+ \
+ test1670 test1671 \
+diff --git a/tests/data/test1656 b/tests/data/test1656
+new file mode 100644
+index 00000000000000..2fab21be63d7e3
+--- /dev/null
++++ b/tests/data/test1656
+@@ -0,0 +1,22 @@
++<testcase>
++<info>
++<keywords>
++unittest
++Curl_x509_GTime2str
++</keywords>
++</info>
++
++#
++# Client-side
++<client>
++<server>
++none
++</server>
++<features>
++unittest
++</features>
++<name>
++Curl_x509_GTime2str unit tests
++</name>
++</client>
++</testcase>
+diff --git a/tests/unit/Makefile.inc b/tests/unit/Makefile.inc
+index c402f803509c8a..5b23c2559280f0 100644
+--- a/tests/unit/Makefile.inc
++++ b/tests/unit/Makefile.inc
+@@ -36,7 +36,7 @@ UNITPROGS = unit1300 unit1302 unit1303 unit1304 unit1305 unit1307 \
+ unit1600 unit1601 unit1602 unit1603 unit1604 unit1605 unit1606 unit1607 \
+ unit1608 unit1609 unit1610 unit1611 unit1612 unit1614 unit1615 \
+ unit1620 unit1621 \
+- unit1650 unit1651 unit1652 unit1653 unit1654 unit1655 \
++ unit1650 unit1651 unit1652 unit1653 unit1654 unit1655 unit1656 \
+ unit1660 unit1661 \
+ unit2600 unit2601 unit2602 unit2603 \
+ unit3200
+@@ -119,6 +119,8 @@ unit1654_SOURCES = unit1654.c $(UNITFILES)
+
+ unit1655_SOURCES = unit1655.c $(UNITFILES)
+
++unit1656_SOURCES = unit1656.c $(UNITFILES)
++
+ unit1660_SOURCES = unit1660.c $(UNITFILES)
+
+ unit1661_SOURCES = unit1661.c $(UNITFILES)
+diff --git a/tests/unit/unit1656.c b/tests/unit/unit1656.c
+new file mode 100644
+index 00000000000000..644e72fc7d6577
+--- /dev/null
++++ b/tests/unit/unit1656.c
+@@ -0,0 +1,133 @@
++/***************************************************************************
++ * _ _ ____ _
++ * Project ___| | | | _ \| |
++ * / __| | | | |_) | |
++ * | (__| |_| | _ <| |___
++ * \___|\___/|_| \_\_____|
++ *
++ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
++ *
++ * This software is licensed as described in the file COPYING, which
++ * you should have received as part of this distribution. The terms
++ * are also available at https://curl.se/docs/copyright.html.
++ *
++ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
++ * copies of the Software, and permit persons to whom the Software is
++ * furnished to do so, under the terms of the COPYING file.
++ *
++ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
++ * KIND, either express or implied.
++ *
++ * SPDX-License-Identifier: curl
++ *
++ ***************************************************************************/
++#include "curlcheck.h"
++
++#include "vtls/x509asn1.h"
++
++static CURLcode unit_setup(void)
++{
++ return CURLE_OK;
++}
++
++static void unit_stop(void)
++{
++
++}
++
++#if defined(USE_GNUTLS) || defined(USE_SCHANNEL) || defined(USE_SECTRANSP) || \
++ defined(USE_MBEDTLS)
++
++#ifndef ARRAYSIZE
++#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0]))
++#endif
++
++struct test_spec {
++ const char *input;
++ const char *exp_output;
++ CURLcode exp_result;
++};
++
++static struct test_spec test_specs[] = {
++ { "190321134340", "1903-21-13 43:40:00", CURLE_OK },
++ { "", NULL, CURLE_BAD_FUNCTION_ARGUMENT },
++ { "WTF", NULL, CURLE_BAD_FUNCTION_ARGUMENT },
++ { "0WTF", NULL, CURLE_BAD_FUNCTION_ARGUMENT },
++ { "19032113434", NULL, CURLE_BAD_FUNCTION_ARGUMENT },
++ { "19032113434WTF", NULL, CURLE_BAD_FUNCTION_ARGUMENT },
++ { "190321134340.", NULL, CURLE_BAD_FUNCTION_ARGUMENT },
++ { "190321134340.1", "1903-21-13 43:40:00.1", CURLE_OK },
++ { "19032113434017.0", "1903-21-13 43:40:17", CURLE_OK },
++ { "19032113434017.01", "1903-21-13 43:40:17.01", CURLE_OK },
++ { "19032113434003.001", "1903-21-13 43:40:03.001", CURLE_OK },
++ { "19032113434003.090", "1903-21-13 43:40:03.09", CURLE_OK },
++ { "190321134340Z", "1903-21-13 43:40:00 GMT", CURLE_OK },
++ { "19032113434017.0Z", "1903-21-13 43:40:17 GMT", CURLE_OK },
++ { "19032113434017.01Z", "1903-21-13 43:40:17.01 GMT", CURLE_OK },
++ { "19032113434003.001Z", "1903-21-13 43:40:03.001 GMT", CURLE_OK },
++ { "19032113434003.090Z", "1903-21-13 43:40:03.09 GMT", CURLE_OK },
++ { "190321134340CET", "1903-21-13 43:40:00 CET", CURLE_OK },
++ { "19032113434017.0CET", "1903-21-13 43:40:17 CET", CURLE_OK },
++ { "19032113434017.01CET", "1903-21-13 43:40:17.01 CET", CURLE_OK },
++ { "190321134340+02:30", "1903-21-13 43:40:00 UTC+02:30", CURLE_OK },
++ { "19032113434017.0+02:30", "1903-21-13 43:40:17 UTC+02:30", CURLE_OK },
++ { "19032113434017.01+02:30", "1903-21-13 43:40:17.01 UTC+02:30", CURLE_OK },
++ { "190321134340-3", "1903-21-13 43:40:00 UTC-3", CURLE_OK },
++ { "19032113434017.0-04", "1903-21-13 43:40:17 UTC-04", CURLE_OK },
++ { "19032113434017.01-01:10", "1903-21-13 43:40:17.01 UTC-01:10", CURLE_OK },
++};
++
++static bool do_test(struct test_spec *spec, size_t i, struct dynbuf *dbuf)
++{
++ CURLcode result;
++ const char *in = spec->input;
++
++ Curl_dyn_reset(dbuf);
++ result = Curl_x509_GTime2str(dbuf, in, in + strlen(in));
++ if(result != spec->exp_result) {
++ fprintf(stderr, "test %zu: expect result %d, got %d\n",
++ i, spec->exp_result, result);
++ return FALSE;
++ }
++ else if(!result && strcmp(spec->exp_output, Curl_dyn_ptr(dbuf))) {
++ fprintf(stderr, "test %zu: input '%s', expected output '%s', got '%s'\n",
++ i, in, spec->exp_output, Curl_dyn_ptr(dbuf));
++ return FALSE;
++ }
++
++ return TRUE;
++}
++
++UNITTEST_START
++{
++ size_t i;
++ struct dynbuf dbuf;
++ bool all_ok = TRUE;
++
++ Curl_dyn_init(&dbuf, 32*1024);
++
++ if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
++ fprintf(stderr, "curl_global_init() failed\n");
++ return TEST_ERR_MAJOR_BAD;
++ }
++
++ for(i = 0; i < ARRAYSIZE(test_specs); ++i) {
++ if(!do_test(&test_specs[i], i, &dbuf))
++ all_ok = FALSE;
++ }
++ fail_unless(all_ok, "some tests of Curl_x509_GTime2str() fails");
++
++ Curl_dyn_free(&dbuf);
++ curl_global_cleanup();
++}
++UNITTEST_STOP
++
++#else
++
++UNITTEST_START
++{
++ puts("not tested since Curl_x509_GTime2str() is not built-in");
++}
++UNITTEST_STOP
++
++#endif
diff --git a/meta/recipes-support/curl/curl_8.7.1.bb b/meta/recipes-support/curl/curl_8.7.1.bb
index 6d2886f70c..a2cee8ba23 100644
--- a/meta/recipes-support/curl/curl_8.7.1.bb
+++ b/meta/recipes-support/curl/curl_8.7.1.bb
@@ -16,6 +16,8 @@ SRC_URI = " \
file://disable-tests \
file://no-test-timeout.patch \
file://CVE-2024-6197.patch \
+ file://CVE-2024-7264-1.patch \
+ file://CVE-2024-7264-2.patch \
"
SRC_URI[sha256sum] = "6fea2aac6a4610fbd0400afb0bcddbe7258a64c63f1f68e5855ebc0c659710cd"
--
2.34.1
^ permalink raw reply related [flat|nested] 19+ messages in thread* [OE-core][scarthgap 06/12] qemu: fix CVE-2024-7409
2024-08-29 13:32 [OE-core][scarthgap 00/12] Patch review Steve Sakoman
` (4 preceding siblings ...)
2024-08-29 13:32 ` [OE-core][scarthgap 05/12] ffmpeg: fix CVE-2024-32230 Steve Sakoman
@ 2024-08-29 13:32 ` Steve Sakoman
2024-08-29 13:32 ` [OE-core][scarthgap 07/12] vim: Upgrade 9.1.0114 -> 9.1.0682 Steve Sakoman
` (5 subsequent siblings)
11 siblings, 0 replies; 19+ messages in thread
From: Steve Sakoman @ 2024-08-29 13:32 UTC (permalink / raw)
To: openembedded-core
From: Archana Polampalli <archana.polampalli@windriver.com>
A flaw was found in the QEMU NBD Server. This vulnerability allows a denial of service (DoS) attack
via improper synchronization during socket closure when a client keeps a socket open as the server
is taken offline.
Signed-off-by: Archana Polampalli <archana.polampalli@windriver.com>
Signed-off-by: Steve Sakoman <steve@sakoman.com>
---
meta/recipes-devtools/qemu/qemu.inc | 4 +
.../qemu/qemu/CVE-2024-7409-0001.patch | 167 +++++++++++++++++
.../qemu/qemu/CVE-2024-7409-0002.patch | 175 ++++++++++++++++++
.../qemu/qemu/CVE-2024-7409-0003.patch | 126 +++++++++++++
.../qemu/qemu/CVE-2024-7409-0004.patch | 164 ++++++++++++++++
5 files changed, 636 insertions(+)
create mode 100644 meta/recipes-devtools/qemu/qemu/CVE-2024-7409-0001.patch
create mode 100644 meta/recipes-devtools/qemu/qemu/CVE-2024-7409-0002.patch
create mode 100644 meta/recipes-devtools/qemu/qemu/CVE-2024-7409-0003.patch
create mode 100644 meta/recipes-devtools/qemu/qemu/CVE-2024-7409-0004.patch
diff --git a/meta/recipes-devtools/qemu/qemu.inc b/meta/recipes-devtools/qemu/qemu.inc
index 50d92b04bd..a1d8a309a0 100644
--- a/meta/recipes-devtools/qemu/qemu.inc
+++ b/meta/recipes-devtools/qemu/qemu.inc
@@ -45,6 +45,10 @@ SRC_URI = "https://download.qemu.org/${BPN}-${PV}.tar.xz \
file://CVE-2024-4467-0003.patch \
file://CVE-2024-4467-0004.patch \
file://CVE-2024-4467-0005.patch \
+ file://CVE-2024-7409-0001.patch \
+ file://CVE-2024-7409-0002.patch \
+ file://CVE-2024-7409-0003.patch \
+ file://CVE-2024-7409-0004.patch \
"
UPSTREAM_CHECK_REGEX = "qemu-(?P<pver>\d+(\.\d+)+)\.tar"
diff --git a/meta/recipes-devtools/qemu/qemu/CVE-2024-7409-0001.patch b/meta/recipes-devtools/qemu/qemu/CVE-2024-7409-0001.patch
new file mode 100644
index 0000000000..631e93a6d2
--- /dev/null
+++ b/meta/recipes-devtools/qemu/qemu/CVE-2024-7409-0001.patch
@@ -0,0 +1,167 @@
+From fb1c2aaa981e0a2fa6362c9985f1296b74f055ac Mon Sep 17 00:00:00 2001
+From: Eric Blake <eblake@redhat.com>
+Date: Wed, 7 Aug 2024 08:50:01 -0500
+Subject: [PATCH] nbd/server: Plumb in new args to nbd_client_add()
+
+Upcoming patches to fix a CVE need to track an opaque pointer passed
+in by the owner of a client object, as well as request for a time
+limit on how fast negotiation must complete. Prepare for that by
+changing the signature of nbd_client_new() and adding an accessor to
+get at the opaque pointer, although for now the two servers
+(qemu-nbd.c and blockdev-nbd.c) do not change behavior even though
+they pass in a new default timeout value.
+
+Suggested-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
+Signed-off-by: Eric Blake <eblake@redhat.com>
+Message-ID: <20240807174943.771624-11-eblake@redhat.com>
+Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
+[eblake: s/LIMIT/MAX_SECS/ as suggested by Dan]
+Signed-off-by: Eric Blake <eblake@redhat.com>
+
+CVE: CVE-2024-7409
+
+Upstream-Status: Backport [https://github.com/qemu/qemu/commit/fb1c2aaa981e0a2fa6362c9985f1296b74f055ac]
+
+Signed-off-by: Archana Polampalli <archana.polampalli@windriver.com>
+---
+ blockdev-nbd.c | 6 ++++--
+ include/block/nbd.h | 11 ++++++++++-
+ nbd/server.c | 20 +++++++++++++++++---
+ qemu-nbd.c | 4 +++-
+ 4 files changed, 34 insertions(+), 7 deletions(-)
+
+diff --git a/blockdev-nbd.c b/blockdev-nbd.c
+index 213012435..267a1de90 100644
+--- a/blockdev-nbd.c
++++ b/blockdev-nbd.c
+@@ -64,8 +64,10 @@ static void nbd_accept(QIONetListener *listener, QIOChannelSocket *cioc,
+ nbd_update_server_watch(nbd_server);
+
+ qio_channel_set_name(QIO_CHANNEL(cioc), "nbd-server");
+- nbd_client_new(cioc, nbd_server->tlscreds, nbd_server->tlsauthz,
+- nbd_blockdev_client_closed);
++ /* TODO - expose handshake timeout as QMP option */
++ nbd_client_new(cioc, NBD_DEFAULT_HANDSHAKE_MAX_SECS,
++ nbd_server->tlscreds, nbd_server->tlsauthz,
++ nbd_blockdev_client_closed, NULL);
+ }
+
+ static void nbd_update_server_watch(NBDServerData *s)
+diff --git a/include/block/nbd.h b/include/block/nbd.h
+index 4e7bd6342..1d4d65922 100644
+--- a/include/block/nbd.h
++++ b/include/block/nbd.h
+@@ -33,6 +33,12 @@ typedef struct NBDMetaContexts NBDMetaContexts;
+
+ extern const BlockExportDriver blk_exp_nbd;
+
++/*
++ * NBD_DEFAULT_HANDSHAKE_MAX_SECS: Number of seconds in which client must
++ * succeed at NBD_OPT_GO before being forcefully dropped as too slow.
++ */
++#define NBD_DEFAULT_HANDSHAKE_MAX_SECS 10
++
+ /* Handshake phase structs - this struct is passed on the wire */
+
+ typedef struct NBDOption {
+@@ -403,9 +409,12 @@ AioContext *nbd_export_aio_context(NBDExport *exp);
+ NBDExport *nbd_export_find(const char *name);
+
+ void nbd_client_new(QIOChannelSocket *sioc,
++ uint32_t handshake_max_secs,
+ QCryptoTLSCreds *tlscreds,
+ const char *tlsauthz,
+- void (*close_fn)(NBDClient *, bool));
++ void (*close_fn)(NBDClient *, bool),
++ void *owner);
++void *nbd_client_owner(NBDClient *client);
+ void nbd_client_get(NBDClient *client);
+ void nbd_client_put(NBDClient *client);
+
+diff --git a/nbd/server.c b/nbd/server.c
+index 091b57119..f8881936e 100644
+--- a/nbd/server.c
++++ b/nbd/server.c
+@@ -124,12 +124,14 @@ struct NBDMetaContexts {
+ struct NBDClient {
+ int refcount; /* atomic */
+ void (*close_fn)(NBDClient *client, bool negotiated);
++ void *owner;
+
+ QemuMutex lock;
+
+ NBDExport *exp;
+ QCryptoTLSCreds *tlscreds;
+ char *tlsauthz;
++ uint32_t handshake_max_secs;
+ QIOChannelSocket *sioc; /* The underlying data channel */
+ QIOChannel *ioc; /* The current I/O channel which may differ (eg TLS) */
+
+@@ -3160,6 +3162,7 @@ static coroutine_fn void nbd_co_client_start(void *opaque)
+
+ qemu_co_mutex_init(&client->send_lock);
+
++ /* TODO - utilize client->handshake_max_secs */
+ if (nbd_negotiate(client, &local_err)) {
+ if (local_err) {
+ error_report_err(local_err);
+@@ -3174,14 +3177,17 @@ static coroutine_fn void nbd_co_client_start(void *opaque)
+ }
+
+ /*
+- * Create a new client listener using the given channel @sioc.
++ * Create a new client listener using the given channel @sioc and @owner.
+ * Begin servicing it in a coroutine. When the connection closes, call
+- * @close_fn with an indication of whether the client completed negotiation.
++ * @close_fn with an indication of whether the client completed negotiation
++ * within @handshake_max_secs seconds (0 for unbounded).
+ */
+ void nbd_client_new(QIOChannelSocket *sioc,
++ uint32_t handshake_max_secs,
+ QCryptoTLSCreds *tlscreds,
+ const char *tlsauthz,
+- void (*close_fn)(NBDClient *, bool))
++ void (*close_fn)(NBDClient *, bool),
++ void *owner)
+ {
+ NBDClient *client;
+ Coroutine *co;
+@@ -3194,13 +3200,21 @@ void nbd_client_new(QIOChannelSocket *sioc,
+ object_ref(OBJECT(client->tlscreds));
+ }
+ client->tlsauthz = g_strdup(tlsauthz);
++ client->handshake_max_secs = handshake_max_secs;
+ client->sioc = sioc;
+ qio_channel_set_delay(QIO_CHANNEL(sioc), false);
+ object_ref(OBJECT(client->sioc));
+ client->ioc = QIO_CHANNEL(sioc);
+ object_ref(OBJECT(client->ioc));
+ client->close_fn = close_fn;
++ client->owner = owner;
+
+ co = qemu_coroutine_create(nbd_co_client_start, client);
+ qemu_coroutine_enter(co);
+ }
++
++void *
++nbd_client_owner(NBDClient *client)
++{
++ return client->owner;
++}
+diff --git a/qemu-nbd.c b/qemu-nbd.c
+index 186e6468b..5fa399c0b 100644
+--- a/qemu-nbd.c
++++ b/qemu-nbd.c
+@@ -389,7 +389,9 @@ static void nbd_accept(QIONetListener *listener, QIOChannelSocket *cioc,
+
+ nb_fds++;
+ nbd_update_server_watch();
+- nbd_client_new(cioc, tlscreds, tlsauthz, nbd_client_closed);
++ /* TODO - expose handshake timeout as command line option */
++ nbd_client_new(cioc, NBD_DEFAULT_HANDSHAKE_MAX_SECS,
++ tlscreds, tlsauthz, nbd_client_closed, NULL);
+ }
+
+ static void nbd_update_server_watch(void)
+--
+2.40.0
diff --git a/meta/recipes-devtools/qemu/qemu/CVE-2024-7409-0002.patch b/meta/recipes-devtools/qemu/qemu/CVE-2024-7409-0002.patch
new file mode 100644
index 0000000000..ca8ef0b44d
--- /dev/null
+++ b/meta/recipes-devtools/qemu/qemu/CVE-2024-7409-0002.patch
@@ -0,0 +1,175 @@
+From c8a76dbd90c2f48df89b75bef74917f90a59b623 Mon Sep 17 00:00:00 2001
+From: Eric Blake <eblake@redhat.com>
+Date: Tue, 6 Aug 2024 13:53:00 -0500
+Subject: [PATCH] nbd/server: CVE-2024-7409: Cap default max-connections to 100
+
+Allowing an unlimited number of clients to any web service is a recipe
+for a rudimentary denial of service attack: the client merely needs to
+open lots of sockets without closing them, until qemu no longer has
+any more fds available to allocate.
+
+For qemu-nbd, we default to allowing only 1 connection unless more are
+explicitly asked for (-e or --shared); this was historically picked as
+a nice default (without an explicit -t, a non-persistent qemu-nbd goes
+away after a client disconnects, without needing any additional
+follow-up commands), and we are not going to change that interface now
+(besides, someday we want to point people towards qemu-storage-daemon
+instead of qemu-nbd).
+
+But for qemu proper, and the newer qemu-storage-daemon, the QMP
+nbd-server-start command has historically had a default of unlimited
+number of connections, in part because unlike qemu-nbd it is
+inherently persistent until nbd-server-stop. Allowing multiple client
+sockets is particularly useful for clients that can take advantage of
+MULTI_CONN (creating parallel sockets to increase throughput),
+although known clients that do so (such as libnbd's nbdcopy) typically
+use only 8 or 16 connections (the benefits of scaling diminish once
+more sockets are competing for kernel attention). Picking a number
+large enough for typical use cases, but not unlimited, makes it
+slightly harder for a malicious client to perform a denial of service
+merely by opening lots of connections withot progressing through the
+handshake.
+
+This change does not eliminate CVE-2024-7409 on its own, but reduces
+the chance for fd exhaustion or unlimited memory usage as an attack
+surface. On the other hand, by itself, it makes it more obvious that
+with a finite limit, we have the problem of an unauthenticated client
+holding 100 fds opened as a way to block out a legitimate client from
+being able to connect; thus, later patches will further add timeouts
+to reject clients that are not making progress.
+
+This is an INTENTIONAL change in behavior, and will break any client
+of nbd-server-start that was not passing an explicit max-connections
+parameter, yet expects more than 100 simultaneous connections. We are
+not aware of any such client (as stated above, most clients aware of
+MULTI_CONN get by just fine on 8 or 16 connections, and probably cope
+with later connections failing by relying on the earlier connections;
+libvirt has not yet been passing max-connections, but generally
+creates NBD servers with the intent for a single client for the sake
+of live storage migration; meanwhile, the KubeSAN project anticipates
+a large cluster sharing multiple clients [up to 8 per node, and up to
+100 nodes in a cluster], but it currently uses qemu-nbd with an
+explicit --shared=0 rather than qemu-storage-daemon with
+nbd-server-start).
+
+We considered using a deprecation period (declare that omitting
+max-parameters is deprecated, and make it mandatory in 3 releases -
+then we don't need to pick an arbitrary default); that has zero risk
+of breaking any apps that accidentally depended on more than 100
+connections, and where such breakage might not be noticed under unit
+testing but only under the larger loads of production usage. But it
+does not close the denial-of-service hole until far into the future,
+and requires all apps to change to add the parameter even if 100 was
+good enough. It also has a drawback that any app (like libvirt) that
+is accidentally relying on an unlimited default should seriously
+consider their own CVE now, at which point they are going to change to
+pass explicit max-connections sooner than waiting for 3 qemu releases.
+Finally, if our changed default breaks an app, that app can always
+pass in an explicit max-parameters with a larger value.
+
+It is also intentional that the HMP interface to nbd-server-start is
+not changed to expose max-connections (any client needing to fine-tune
+things should be using QMP).
+
+Suggested-by: Daniel P. Berrangé <berrange@redhat.com>
+Signed-off-by: Eric Blake <eblake@redhat.com>
+Message-ID: <20240807174943.771624-12-eblake@redhat.com>
+Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
+[ericb: Expand commit message to summarize Dan's argument for why we
+break corner-case back-compat behavior without a deprecation period]
+Signed-off-by: Eric Blake <eblake@redhat.com>
+
+CVE: CVE-2024-7409
+
+Upstream-Status: Backport [https://github.com/qemu/qemu/commit/c8a76dbd90c2f48df89b75bef74917f90a59b623]
+
+Signed-off-by: Archana Polampalli <archana.polampalli@windriver.com>
+---
+ block/monitor/block-hmp-cmds.c | 3 ++-
+ blockdev-nbd.c | 8 ++++++++
+ include/block/nbd.h | 7 +++++++
+ qapi/block-export.json | 4 ++--
+ 4 files changed, 19 insertions(+), 3 deletions(-)
+
+diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c
+index c729cbf1e..78a697585 100644
+--- a/block/monitor/block-hmp-cmds.c
++++ b/block/monitor/block-hmp-cmds.c
+@@ -415,7 +415,8 @@ void hmp_nbd_server_start(Monitor *mon, const QDict *qdict)
+ goto exit;
+ }
+
+- nbd_server_start(addr, NULL, NULL, 0, &local_err);
++ nbd_server_start(addr, NULL, NULL, NBD_DEFAULT_MAX_CONNECTIONS,
++ &local_err);
+ qapi_free_SocketAddress(addr);
+ if (local_err != NULL) {
+ goto exit;
+diff --git a/blockdev-nbd.c b/blockdev-nbd.c
+index 267a1de90..24ba5382d 100644
+--- a/blockdev-nbd.c
++++ b/blockdev-nbd.c
+@@ -170,6 +170,10 @@ void nbd_server_start(SocketAddress *addr, const char *tls_creds,
+
+ void nbd_server_start_options(NbdServerOptions *arg, Error **errp)
+ {
++ if (!arg->has_max_connections) {
++ arg->max_connections = NBD_DEFAULT_MAX_CONNECTIONS;
++ }
++
+ nbd_server_start(arg->addr, arg->tls_creds, arg->tls_authz,
+ arg->max_connections, errp);
+ }
+@@ -182,6 +186,10 @@ void qmp_nbd_server_start(SocketAddressLegacy *addr,
+ {
+ SocketAddress *addr_flat = socket_address_flatten(addr);
+
++ if (!has_max_connections) {
++ max_connections = NBD_DEFAULT_MAX_CONNECTIONS;
++ }
++
+ nbd_server_start(addr_flat, tls_creds, tls_authz, max_connections, errp);
+ qapi_free_SocketAddress(addr_flat);
+ }
+diff --git a/include/block/nbd.h b/include/block/nbd.h
+index 1d4d65922..d4f8b21ae 100644
+--- a/include/block/nbd.h
++++ b/include/block/nbd.h
+@@ -39,6 +39,13 @@ extern const BlockExportDriver blk_exp_nbd;
+ */
+ #define NBD_DEFAULT_HANDSHAKE_MAX_SECS 10
+
++/*
++ * NBD_DEFAULT_MAX_CONNECTIONS: Number of client sockets to allow at
++ * once; must be large enough to allow a MULTI_CONN-aware client like
++ * nbdcopy to create its typical number of 8-16 sockets.
++ */
++#define NBD_DEFAULT_MAX_CONNECTIONS 100
++
+ /* Handshake phase structs - this struct is passed on the wire */
+
+ typedef struct NBDOption {
+diff --git a/qapi/block-export.json b/qapi/block-export.json
+index 7874a49ba..1d255d77e 100644
+--- a/qapi/block-export.json
++++ b/qapi/block-export.json
+@@ -28,7 +28,7 @@
+ # @max-connections: The maximum number of connections to allow at the
+ # same time, 0 for unlimited. Setting this to 1 also stops the
+ # server from advertising multiple client support (since 5.2;
+-# default: 0)
++# default: 100)
+ #
+ # Since: 4.2
+ ##
+@@ -63,7 +63,7 @@
+ # @max-connections: The maximum number of connections to allow at the
+ # same time, 0 for unlimited. Setting this to 1 also stops the
+ # server from advertising multiple client support (since 5.2;
+-# default: 0).
++# default: 100).
+ #
+ # Returns: error if the server is already running.
+ #
+--
+2.40.0
diff --git a/meta/recipes-devtools/qemu/qemu/CVE-2024-7409-0003.patch b/meta/recipes-devtools/qemu/qemu/CVE-2024-7409-0003.patch
new file mode 100644
index 0000000000..b2b9b15c54
--- /dev/null
+++ b/meta/recipes-devtools/qemu/qemu/CVE-2024-7409-0003.patch
@@ -0,0 +1,126 @@
+From b9b72cb3ce15b693148bd09cef7e50110566d8a0 Mon Sep 17 00:00:00 2001
+From: Eric Blake <eblake@redhat.com>
+Date: Thu, 8 Aug 2024 16:05:08 -0500
+Subject: [PATCH] nbd/server: CVE-2024-7409: Drop non-negotiating clients
+
+A client that opens a socket but does not negotiate is merely hogging
+qemu's resources (an open fd and a small amount of memory); and a
+malicious client that can access the port where NBD is listening can
+attempt a denial of service attack by intentionally opening and
+abandoning lots of unfinished connections. The previous patch put a
+default bound on the number of such ongoing connections, but once that
+limit is hit, no more clients can connect (including legitimate ones).
+The solution is to insist that clients complete handshake within a
+reasonable time limit, defaulting to 10 seconds. A client that has
+not successfully completed NBD_OPT_GO by then (including the case of
+where the client didn't know TLS credentials to even reach the point
+of NBD_OPT_GO) is wasting our time and does not deserve to stay
+connected. Later patches will allow fine-tuning the limit away from
+the default value (including disabling it for doing integration
+testing of the handshake process itself).
+
+Note that this patch in isolation actually makes it more likely to see
+qemu SEGV after nbd-server-stop, as any client socket still connected
+when the server shuts down will now be closed after 10 seconds rather
+than at the client's whims. That will be addressed in the next patch.
+
+For a demo of this patch in action:
+$ qemu-nbd -f raw -r -t -e 10 file &
+$ nbdsh --opt-mode -c '
+H = list()
+for i in range(20):
+ print(i)
+ H.insert(i, nbd.NBD())
+ H[i].set_opt_mode(True)
+ H[i].connect_uri("nbd://localhost")
+'
+$ kill $!
+
+where later connections get to start progressing once earlier ones are
+forcefully dropped for taking too long, rather than hanging.
+
+Suggested-by: Daniel P. Berrangé <berrange@redhat.com>
+Signed-off-by: Eric Blake <eblake@redhat.com>
+Message-ID: <20240807174943.771624-13-eblake@redhat.com>
+Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
+[eblake: rebase to changes earlier in series, reduce scope of timer]
+Signed-off-by: Eric Blake <eblake@redhat.com>
+
+CVE: CVE-2024-7409
+
+Upstream-Status: Backport [https://gitlab.com/qemu-project/qemu/-/commit/b9b72cb3ce15b693148bd09cef7e50110566d8a0]
+
+Signed-off-by: Archana Polampalli <archana.polampalli@windriver.com>
+---
+ nbd/server.c | 28 +++++++++++++++++++++++++++-
+ nbd/trace-events | 1 +
+ 2 files changed, 28 insertions(+), 1 deletion(-)
+
+diff --git a/nbd/server.c b/nbd/server.c
+index f8881936e..6155e329a 100644
+--- a/nbd/server.c
++++ b/nbd/server.c
+@@ -3155,22 +3155,48 @@ static void nbd_client_receive_next_request(NBDClient *client)
+ }
+ }
+
++static void nbd_handshake_timer_cb(void *opaque)
++{
++ QIOChannel *ioc = opaque;
++
++ trace_nbd_handshake_timer_cb();
++ qio_channel_shutdown(ioc, QIO_CHANNEL_SHUTDOWN_BOTH, NULL);
++}
++
+ static coroutine_fn void nbd_co_client_start(void *opaque)
+ {
+ NBDClient *client = opaque;
+ Error *local_err = NULL;
++ QEMUTimer *handshake_timer = NULL;
+
+ qemu_co_mutex_init(&client->send_lock);
+
+- /* TODO - utilize client->handshake_max_secs */
++ /*
++ * Create a timer to bound the time spent in negotiation. If the
++ * timer expires, it is likely nbd_negotiate will fail because the
++ * socket was shutdown.
++ */
++ if (client->handshake_max_secs > 0) {
++ handshake_timer = aio_timer_new(qemu_get_aio_context(),
++ QEMU_CLOCK_REALTIME,
++ SCALE_NS,
++ nbd_handshake_timer_cb,
++ client->sioc);
++ timer_mod(handshake_timer,
++ qemu_clock_get_ns(QEMU_CLOCK_REALTIME) +
++ client->handshake_max_secs * NANOSECONDS_PER_SECOND);
++ }
++
+ if (nbd_negotiate(client, &local_err)) {
+ if (local_err) {
+ error_report_err(local_err);
+ }
++ timer_free(handshake_timer);
+ client_close(client, false);
+ return;
+ }
+
++ timer_free(handshake_timer);
+ WITH_QEMU_LOCK_GUARD(&client->lock) {
+ nbd_client_receive_next_request(client);
+ }
+diff --git a/nbd/trace-events b/nbd/trace-events
+index 00ae3216a..cbd0a4ab7 100644
+--- a/nbd/trace-events
++++ b/nbd/trace-events
+@@ -76,6 +76,7 @@ nbd_co_receive_request_payload_received(uint64_t cookie, uint64_t len) "Payload
+ nbd_co_receive_ext_payload_compliance(uint64_t from, uint64_t len) "client sent non-compliant write without payload flag: from=0x%" PRIx64 ", len=0x%" PRIx64
+ nbd_co_receive_align_compliance(const char *op, uint64_t from, uint64_t len, uint32_t align) "client sent non-compliant unaligned %s request: from=0x%" PRIx64 ", len=0x%" PRIx64 ", align=0x%" PRIx32
+ nbd_trip(void) "Reading request"
++nbd_handshake_timer_cb(void) "client took too long to negotiate"
+
+ # client-connection.c
+ nbd_connect_thread_sleep(uint64_t timeout) "timeout %" PRIu64
+--
+2.40.0
diff --git a/meta/recipes-devtools/qemu/qemu/CVE-2024-7409-0004.patch b/meta/recipes-devtools/qemu/qemu/CVE-2024-7409-0004.patch
new file mode 100644
index 0000000000..9515c631ad
--- /dev/null
+++ b/meta/recipes-devtools/qemu/qemu/CVE-2024-7409-0004.patch
@@ -0,0 +1,164 @@
+From 3e7ef738c8462c45043a1d39f702a0990406a3b3 Mon Sep 17 00:00:00 2001
+From: Eric Blake <eblake@redhat.com>
+Date: Wed, 7 Aug 2024 12:23:13 -0500
+Subject: [PATCH] nbd/server: CVE-2024-7409: Close stray clients at server-stop
+
+A malicious client can attempt to connect to an NBD server, and then
+intentionally delay progress in the handshake, including if it does
+not know the TLS secrets. Although the previous two patches reduce
+this behavior by capping the default max-connections parameter and
+killing slow clients, they did not eliminate the possibility of a
+client waiting to close the socket until after the QMP nbd-server-stop
+command is executed, at which point qemu would SEGV when trying to
+dereference the NULL nbd_server global which is no longer present.
+This amounts to a denial of service attack. Worse, if another NBD
+server is started before the malicious client disconnects, I cannot
+rule out additional adverse effects when the old client interferes
+with the connection count of the new server (although the most likely
+is a crash due to an assertion failure when checking
+nbd_server->connections > 0).
+
+For environments without this patch, the CVE can be mitigated by
+ensuring (such as via a firewall) that only trusted clients can
+connect to an NBD server. Note that using frameworks like libvirt
+that ensure that TLS is used and that nbd-server-stop is not executed
+while any trusted clients are still connected will only help if there
+is also no possibility for an untrusted client to open a connection
+but then stall on the NBD handshake.
+
+Given the previous patches, it would be possible to guarantee that no
+clients remain connected by having nbd-server-stop sleep for longer
+than the default handshake deadline before finally freeing the global
+nbd_server object, but that could make QMP non-responsive for a long
+time. So intead, this patch fixes the problem by tracking all client
+sockets opened while the server is running, and forcefully closing any
+such sockets remaining without a completed handshake at the time of
+nbd-server-stop, then waiting until the coroutines servicing those
+sockets notice the state change. nbd-server-stop now has a second
+AIO_WAIT_WHILE_UNLOCKED (the first is indirectly through the
+blk_exp_close_all_type() that disconnects all clients that completed
+handshakes), but forced socket shutdown is enough to progress the
+coroutines and quickly tear down all clients before the server is
+freed, thus finally fixing the CVE.
+
+This patch relies heavily on the fact that nbd/server.c guarantees
+that it only calls nbd_blockdev_client_closed() from the main loop
+(see the assertion in nbd_client_put() and the hoops used in
+nbd_client_put_nonzero() to achieve that); if we did not have that
+guarantee, we would also need a mutex protecting our accesses of the
+list of connections to survive re-entrancy from independent iothreads.
+
+Although I did not actually try to test old builds, it looks like this
+problem has existed since at least commit 862172f45c (v2.12.0, 2017) -
+even back when that patch started using a QIONetListener to handle
+listening on multiple sockets, nbd_server_free() was already unaware
+that the nbd_blockdev_client_closed callback can be reached later by a
+client thread that has not completed handshakes (and therefore the
+client's socket never got added to the list closed in
+nbd_export_close_all), despite that patch intentionally tearing down
+the QIONetListener to prevent new clients.
+
+Reported-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
+Fixes: CVE-2024-7409
+CC: qemu-stable@nongnu.org
+Signed-off-by: Eric Blake <eblake@redhat.com>
+Message-ID: <20240807174943.771624-14-eblake@redhat.com>
+Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
+
+CVE: CVE-2024-7409
+
+Upstream-Status: Backport [https://gitlab.com/qemu-project/qemu/-/commit/3e7ef738c8462c45043a1d39f702a0990406a3b3]
+
+Signed-off-by: Archana Polampalli <archana.polampalli@windriver.com>
+---
+ blockdev-nbd.c | 35 ++++++++++++++++++++++++++++++++++-
+ 1 file changed, 34 insertions(+), 1 deletion(-)
+
+diff --git a/blockdev-nbd.c b/blockdev-nbd.c
+index 24ba5382d..f73409ae4 100644
+--- a/blockdev-nbd.c
++++ b/blockdev-nbd.c
+@@ -21,12 +21,18 @@
+ #include "io/channel-socket.h"
+ #include "io/net-listener.h"
+
++typedef struct NBDConn {
++ QIOChannelSocket *cioc;
++ QLIST_ENTRY(NBDConn) next;
++} NBDConn;
++
+ typedef struct NBDServerData {
+ QIONetListener *listener;
+ QCryptoTLSCreds *tlscreds;
+ char *tlsauthz;
+ uint32_t max_connections;
+ uint32_t connections;
++ QLIST_HEAD(, NBDConn) conns;
+ } NBDServerData;
+
+ static NBDServerData *nbd_server;
+@@ -51,6 +57,14 @@ int nbd_server_max_connections(void)
+
+ static void nbd_blockdev_client_closed(NBDClient *client, bool ignored)
+ {
++ NBDConn *conn = nbd_client_owner(client);
++
++ assert(qemu_in_main_thread() && nbd_server);
++
++ object_unref(OBJECT(conn->cioc));
++ QLIST_REMOVE(conn, next);
++ g_free(conn);
++
+ nbd_client_put(client);
+ assert(nbd_server->connections > 0);
+ nbd_server->connections--;
+@@ -60,14 +74,20 @@ static void nbd_blockdev_client_closed(NBDClient *client, bool ignored)
+ static void nbd_accept(QIONetListener *listener, QIOChannelSocket *cioc,
+ gpointer opaque)
+ {
++ NBDConn *conn = g_new0(NBDConn, 1);
++
++ assert(qemu_in_main_thread() && nbd_server);
+ nbd_server->connections++;
++ object_ref(OBJECT(cioc));
++ conn->cioc = cioc;
++ QLIST_INSERT_HEAD(&nbd_server->conns, conn, next);
+ nbd_update_server_watch(nbd_server);
+
+ qio_channel_set_name(QIO_CHANNEL(cioc), "nbd-server");
+ /* TODO - expose handshake timeout as QMP option */
+ nbd_client_new(cioc, NBD_DEFAULT_HANDSHAKE_MAX_SECS,
+ nbd_server->tlscreds, nbd_server->tlsauthz,
+- nbd_blockdev_client_closed, NULL);
++ nbd_blockdev_client_closed, conn);
+ }
+
+ static void nbd_update_server_watch(NBDServerData *s)
+@@ -81,12 +101,25 @@ static void nbd_update_server_watch(NBDServerData *s)
+
+ static void nbd_server_free(NBDServerData *server)
+ {
++ NBDConn *conn, *tmp;
++
+ if (!server) {
+ return;
+ }
+
++ /*
++ * Forcefully close the listener socket, and any clients that have
++ * not yet disconnected on their own.
++ */
+ qio_net_listener_disconnect(server->listener);
+ object_unref(OBJECT(server->listener));
++ QLIST_FOREACH_SAFE(conn, &server->conns, next, tmp) {
++ qio_channel_shutdown(QIO_CHANNEL(conn->cioc), QIO_CHANNEL_SHUTDOWN_BOTH,
++ NULL);
++ }
++
++ AIO_WAIT_WHILE_UNLOCKED(NULL, server->connections > 0);
++
+ if (server->tlscreds) {
+ object_unref(OBJECT(server->tlscreds));
+ }
+--
+2.40.0
--
2.34.1
^ permalink raw reply related [flat|nested] 19+ messages in thread