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].
Fix by retrying under a lock. This makes the failure rate go down to
roughly once in 50 seconds.
[1] https://github.com/bytecodealliance/wasmtime/issues/10000
[2] https://gist.github.com/alexcrichton/f14f23a892ffb9df2522754572d51b1c
Reported-by: Alex Crichton <[email protected]>
Reported-by: Ulrich Weigand <[email protected]>
Fixes: 67ff2186b0a4 ("accel/tcg: Use interval tree for user-only page tracking")
Cc: [email protected]
Signed-off-by: Ilya Leoshkevich <[email protected]>
---
accel/tcg/user-exec.c | 31 +++++++++++++++----------------
1 file changed, 15 insertions(+), 16 deletions(-)
diff --git a/accel/tcg/user-exec.c b/accel/tcg/user-exec.c
index 7704e4017dd..b70955697fe 100644
--- a/accel/tcg/user-exec.c
+++ b/accel/tcg/user-exec.c
@@ -510,24 +510,23 @@ bool page_check_range(vaddr start, vaddr len, int flags)
PageFlagsNode *p = pageflags_find(start, last);
int missing;
- if (!p) {
- if (!locked) {
- /*
- * Lockless lookups have false negatives.
- * Retry with the lock held.
- */
- mmap_lock();
- locked = -1;
- p = pageflags_find(start, last);
- }
- if (!p) {
- ret = false; /* entire region invalid */
+ /*
+ * Lockless lookups race with pageflags_set_clear() and may have false
+ * negatives:
+ * - The node pageflags_find() was looking for could have been
+ * temporarily removed and not added back yet.
+ * - pageflags_find() could have found a node which was subsequently
+ * removed and whose itree.start has been adjusted.
+ */
+ if (!p || start < p->itree.start) {
+ if (locked) {
+ ret = false;
break;
}
- }
- if (start < p->itree.start) {
- ret = false; /* initial bytes invalid */
- break;
+ /* Retry with the lock held. */
+ mmap_lock();
+ locked = -1;
+ continue;
}
missing = flags & ~p->flags;
--
2.54.0