On 2026/06/04 2:35, Peter Xu wrote:
On Wed, Jun 03, 2026 at 02:11:44PM +0900, Akihiko Odaki wrote:
qemu_get_ram_block() may set mru_block during the grace period. Once
mru_block is set, RCU readers can still access the ramblock, even if the
block is no longer visible in ram_list.
True..
When enriching the comment in reclaim_ramblock_prepare(), I think the phrase
“the last reader that can access this ramblock is gone” could be improved.
What matters here is that the last reader that could find the block in
ram_list is gone. The block is no longer visible in ram_list to any later
reader, and clearing the cache at that point ensures that it is no longer
visible through an mru_block cache hit either.
Yes that's ambiguous.
I updated comments in this patch, removed the 1st reset of mru_block, and
when at it also touched up the comment in qemu_get_ram_block(), let me know
if there's further comments before repost.
It looks good. You may repost it with my Reviewed-by added:
Reviewed-by: Akihiko Odaki <[email protected]>
Regards,
Akihiko Odaki
Thanks,
===8<===
From 18789a1136c753a6cdce1ea698cf4c1871121cc9 Mon Sep 17 00:00:00 2001
From: Peter Xu <[email protected]>
Date: Fri, 29 May 2026 12:05:15 -0400
Subject: [PATCH] memory/ramblock: Fix clear of mru_block on possible race
condition
The race condition was not reported in any real bug, but I found it while
reviewing some relevant chnages from Akihiko [1]. It's not clear if
there's any way to reproduce it, so far it's only theoretical. Hence
there's also no Fixes tag attached. There's also no need to copy stable
until we have a solid reproducer.
Currently, mru_block might still points to ramblocks that are removed if
below race condition happens:
Reader A Writer
-------- ------
rcu_read_lock()
walk list, find block X
QLIST_REMOVE_RCU(X)
qatomic_set(&mru_block, NULL)
call_rcu(X, reclaim_ramblock)
qatomic_set(&mru_block, X) <----------- overwrites NULL
rcu_read_unlock()
grace period ends, X freed
Reader C
--------
rcu_read_lock()
qatomic_rcu_read(&mru_block) -> X
Read X's block->offset ... <----------- UAF
To fix it, we can introduce a nested RCU free for reset of the mru_block
field, and only free the ramblock until the 2nd RCU call.
Since QEMU always: (1) dequeue the RCU node before invoking the
function, (2) reset all fields in rcu_head in the call_rcu1() call, nested
RCU will work all fine like what's used in this patch.
[1]
https://lore.kernel.org/r/[email protected]
Cc: Akihiko Odaki <[email protected]>
Signed-off-by: Peter Xu <[email protected]>
---
Based-on: <[email protected]>
---
system/physmem.c | 46 ++++++++++++++++++++++++++++++----------------
1 file changed, 30 insertions(+), 16 deletions(-)
diff --git a/system/physmem.c b/system/physmem.c
index 9e1ac13e82..c9e5696d6a 100644
--- a/system/physmem.c
+++ b/system/physmem.c
@@ -836,21 +836,14 @@ static RAMBlock *qemu_get_ram_block(ram_addr_t addr)
abort();
found:
- /* It is safe to write mru_block outside the BQL. This
- * is what happens:
- *
- * qatomic_set(&mru_block, xxx)
- * rcu_read_unlock()
- * xxx removed from list
- * rcu_read_lock()
- * read mru_block
- * qatomic_set(&mru_block, NULL);
- * call_rcu(reclaim_ramblock, xxx);
- * rcu_read_unlock()
+ /*
+ * It is safe to write mru_block outside the BQL, the writer (e.g. when
+ * QEMU frees a ramblock) is designed to be thread-safe with readers
+ * updating this field concurrently. See reclaim_ramblock_prepare().
*
- * qatomic_rcu_set is not needed here. The block was already published
- * when it was placed into the list. Here we're just making an extra
- * copy of the pointer.
+ * qatomic_rcu_set() is not needed here, because the block was already
+ * published when it was placed into the list. Here we're just making
+ * an extra copy of the pointer.
*/
qatomic_set(&ram_list.mru_block, block);
return block;
@@ -2590,6 +2583,23 @@ static void reclaim_ramblock(RAMBlock *block)
g_free(block);
}
+static void reclaim_ramblock_prepare(RAMBlock *block)
+{
+ /*
+ * After one round of grace period, no more reader can see this
+ * ramblock via ram_list. Reset this field making sure it will never
+ * point to the ramblock being freed.
+ */
+ qatomic_set(&ram_list.mru_block, NULL);
+ /*
+ * Wait for a second round of grace period to make sure whoever
+ * accessed the ramblock previously via mru_block has finished using
+ * it. Note: this is an intended nested use of rcu_head. If needed,
+ * we can provide two rcu_heads for ramblock.
+ */
+ call_rcu(block, reclaim_ramblock, rcu);
+}
+
void qemu_ram_free(RAMBlock *block)
{
g_autofree char *name = NULL;
@@ -2607,10 +2617,14 @@ void qemu_ram_free(RAMBlock *block)
name = cpr_name(block->mr);
cpr_delete_fd(name, 0);
QLIST_REMOVE_RCU(block, next);
- qatomic_set(&ram_list.mru_block, NULL);
/* Write list before version */
qatomic_store_release(&ram_list.version, ram_list.version + 1);
- call_rcu(block, reclaim_ramblock, rcu);
+ /*
+ * Wait for a grace period to make sure no reader can see this ramblock
+ * via ram_list anymore. Note that readers can still see and access
+ * the ramblock via mru_block, so we can't free it yet.
+ */
+ call_rcu(block, reclaim_ramblock_prepare, rcu);
qemu_mutex_unlock_ramlist();
}