@samitouri / QOSamiQemu / commits / e03b7dac65

accel/tcg: Make PageFlagsNodes' start and last immutable

page_check_range() may race with pageflags_set_clear() as follows: T1 T2 ------------------------------------- -------------------------------- p = pageflags_find(start, last); interval_tree_remove(&p->itree, ...); p->itree.start = last + 1; if (start < p->itree.start) { ret = false; interval_tree_insert(&p->itree, ...); leading to errors like fail indirect write 0x72f0a659aff0 (Bad address) in vma-pthread test. I am able to reliably reproduce this on a machine with 32 SMT threads as follows in about 25 seconds: jobs=32; \ seq "$jobs" | \ time -p parallel \ --jobs="$jobs" \ --halt=now,done=1 \ --ungroup \ ' _={}; while ./qemu-s390x tests/tcg/s390x-linux-user/vma-pthread; do printf .; done ' Also wasmtime project reported a similar failure pattern in their CI [1] with a similar reproducer [2]. There are other races like this. In general, region bounds mutating underneath the reader are very hard to reason about. So fix this by preventing mutations and creating copies instead. Use RCU guards in readers to avoid uses-after-frees. Now, when the reader finds a node, it may fearlessly access its fields and be certain that at some point in time the respective region had the respective bounds and permissions. The downside is slightly more expensive mprotect(), but complexity reduction is worth it. Lockless field accesses should probably be wrapped in qatomic_read(), but this is a pre-existing issue, so do not change it here. [1] https://github.com/bytecodealliance/wasmtime/issues/10000 [2] https://gist.github.com/alexcrichton/f14f23a892ffb9df2522754572d51b1c Cc: qemu-stable@nongnu.org Reported-by: Alex Crichton <alex@alexcrichton.com> Reported-by: Ulrich Weigand <ulrich.weigand@de.ibm.com> Fixes: 67ff2186b0a4 ("accel/tcg: Use interval tree for user-only page tracking") Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com> Signed-off-by: Richard Henderson <richard.henderson@linaro.org> Message-ID: <20260706165445.57418-2-iii@linux.ibm.com>

Ilya Leoshkevich committed Jul 6, 2026 at 18:51 UTC e03b7dac65d96d7d9b34bb88803029cf5ec7e4a9
1 file changed +21 -16
accel/tcg/user-exec.c
+21 -16
@@ -239,13 +239,16 @@ void page_dump(FILE *f)
239
240 int page_get_flags(vaddr address)
241 {
242 - PageFlagsNode *p = pageflags_find(address, address);
242 + PageFlagsNode *p;
243 +
244 + RCU_READ_LOCK_GUARD();
245
246 /*
247 * See util/interval-tree.c re lockless lookups: no false positives but
248 * there are false negatives. If we find nothing, retry with the mmap
249 * lock acquired.
250 */
251 + p = pageflags_find(address, address);
252 if (p) {
253 return p->flags;
254 }
@@ -301,15 +304,15 @@ static void pageflags_create_merge(vaddr start, vaddr last, int flags)
304
305 if (prev) {
306 if (next) {
304 - prev->itree.last = next->itree.last;
307 + pageflags_create(prev->itree.start, next->itree.last, flags);
308 g_free_rcu(next, rcu);
309 } else {
307 - prev->itree.last = last;
310 + pageflags_create(prev->itree.start, last, flags);
311 }
309 - interval_tree_insert(&prev->itree, &pageflags_root);
312 + g_free_rcu(prev, rcu);
313 } else if (next) {
311 - next->itree.start = start;
312 - interval_tree_insert(&next->itree, &pageflags_root);
314 + pageflags_create(start, next->itree.last, flags);
315 + g_free_rcu(next, rcu);
316 } else {
317 pageflags_create(start, last, flags);
318 }
@@ -371,8 +374,8 @@ static bool pageflags_set_clear(vaddr start, vaddr last,
374 if (set_flags != merge_flags) {
375 if (p_start < start) {
376 interval_tree_remove(&p->itree, &pageflags_root);
374 - p->itree.last = start - 1;
375 - interval_tree_insert(&p->itree, &pageflags_root);
377 + pageflags_create(p_start, start - 1, p_flags);
378 + g_free_rcu(p, rcu);
379
380 if (last < p_last) {
381 if (merge_flags & PAGE_VALID) {
@@ -394,11 +397,11 @@ static bool pageflags_set_clear(vaddr start, vaddr last,
397 }
398 if (last < p_last) {
399 interval_tree_remove(&p->itree, &pageflags_root);
397 - p->itree.start = last + 1;
398 - interval_tree_insert(&p->itree, &pageflags_root);
400 + pageflags_create(last + 1, p_last, p_flags);
401 if (merge_flags & PAGE_VALID) {
402 pageflags_create(start, last, merge_flags);
403 }
404 + g_free_rcu(p, rcu);
405 } else {
406 if (merge_flags & PAGE_VALID) {
407 p->flags = merge_flags;
@@ -419,8 +422,8 @@ static bool pageflags_set_clear(vaddr start, vaddr last,
422 if (set_flags == p_flags) {
423 if (start < p_start) {
424 interval_tree_remove(&p->itree, &pageflags_root);
422 - p->itree.start = start;
423 - interval_tree_insert(&p->itree, &pageflags_root);
425 + pageflags_create(start, p_last, p_flags);
426 + g_free_rcu(p, rcu);
427 }
428 if (p_last < last) {
429 start = p_last + 1;
@@ -432,8 +435,8 @@ static bool pageflags_set_clear(vaddr start, vaddr last,
435 /* Maybe split out head and/or tail ranges with the original flags. */
436 interval_tree_remove(&p->itree, &pageflags_root);
437 if (p_start < start) {
435 - p->itree.last = start - 1;
436 - interval_tree_insert(&p->itree, &pageflags_root);
438 + pageflags_create(p_start, start - 1, p_flags);
439 + g_free_rcu(p, rcu);
440
441 if (p_last < last) {
442 goto restart;
@@ -442,8 +445,8 @@ static bool pageflags_set_clear(vaddr start, vaddr last,
445 pageflags_create(last + 1, p_last, p_flags);
446 }
447 } else if (last < p_last) {
445 - p->itree.start = last + 1;
446 - interval_tree_insert(&p->itree, &pageflags_root);
448 + pageflags_create(last + 1, p_last, p_flags);
449 + g_free_rcu(p, rcu);
450 } else {
451 g_free_rcu(p, rcu);
452 goto restart;
@@ -505,6 +508,8 @@ bool page_check_range(vaddr start, vaddr len, int flags)
508 return false; /* wrap around */
509 }
510
511 + RCU_READ_LOCK_GUARD();
512 +
513 locked = have_mmap_lock();
514 while (true) {
515 PageFlagsNode *p = pageflags_find(start, last);