From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-dy1-f177.google.com (mail-dy1-f177.google.com [74.125.82.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B9A21346791 for ; Wed, 10 Jun 2026 21:21:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.177 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781126476; cv=none; b=smwXxCkJpTfib5/ynoi/pcXh1v9qlbr02EqrNVeSgHS6Oq4GFol38jfCyMFoDAaCCILdzCnMgvDETQD4ST2zNpmrTdQiYjPyzo8wTGLKPmLYZBJFMBqxXDeXSkPqJFEW+UiMlBzkQR0HdAst18kfpgzqx9lRemNk97r5mbYASF8= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781126476; c=relaxed/simple; bh=lrGeoZqSPciy/we17U+Dpl1nncvr2/ySqGxYzAAeddQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=nYugfLFYN+Ym8//NhXUq686WZpYFj3+tK1HU+Lzo5cxZAoH/9q+Ny5BngYfTNv5/linpMNBlKAj1m5zv+6oLzcCZXHBODBLkjw3ner8mmrK/MnefR/y3fCgk4pUyKx2BeCx9cUSmQ2yFBkTedy9Pbm/pB3zw7WNpjgCdec/3NE4= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=G55PRZWM; arc=none smtp.client-ip=74.125.82.177 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="G55PRZWM" Received: by mail-dy1-f177.google.com with SMTP id 5a478bee46e88-3074adb8fcaso1654658eec.0 for ; Wed, 10 Jun 2026 14:21:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1781126474; x=1781731274; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=Lcxdh4WpQV75T4a1j56otEoiOSXpVoC9+LXovWlg34c=; b=G55PRZWMH7ey5Dc+CsUcHYpm4rukrDv7Yh3Uus/Csnl+hVpr5t1t7PteigpxBOppgc 9nfnFVYtg7EZDaltu036dXasi2UWt1GGDwOnDDCDX9brmDTtpfVkysY4JEYq5MiLchuU 5swgun4XXqB339YvIyIZebxXqKTcHNY0fsxwcik66kRwWeGBwtfxdZjAfkmkOixtzCRH jNqGI8eirJXWn46TDZSP3jNYJutWFpPhdid6AGL6Nov5rpTZ3gabUGg4Anuq4v3sjKZw 0m3EmqGlzGyxeGXm/2ZA0ghG74DTo1gHfYqOBlaSv31Z6EytxUftC5JcA1/ssy33RZzn gecg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1781126474; x=1781731274; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=Lcxdh4WpQV75T4a1j56otEoiOSXpVoC9+LXovWlg34c=; b=mqvi7YZ94aANOG4JVU4y3Tbe+oBU0kO6gLVwAiS8RNrfKGzHIvyk4S4G95oDXitQ1h V0CX+CqgivGw5QOwT3DfyIP87GoC/eJ0lddAKwgg+Rc8YB1yBstl04Da+OJD5f305uQi YeMU17YPqYZRXWDpUMKNSCDfA9sGkAf33RTn+T5Q+E19gmfOVZnkgGzN5sZz46yfuRnI 4kUVeTsyjUB3fZApTnrERlZ+YV3yBtuUe/Wxl26VKw8ovSE/0Jf8Ri5IleHfMjllfgwJ CzfXMoll3rfCm6O2qZqJO1DJPxSB83gikSch6yLEHJwTq1/Qb0avFEoT5KB6oTSYToz0 Lzxg== X-Forwarded-Encrypted: i=1; AFNElJ+R11ZG2JCeElbp4iDHmmpyfPSH8hDVYiynFFIFfaaEuY4v4bzXkxuPB86QTnr1O+i2Bfrjo6ZWaUjbRmk=@vger.kernel.org X-Gm-Message-State: AOJu0Yz96aTXtZ2JsaIs4fgOertuLrMF//35kICWx5zcIbOv1cv9GNla 6znufvhbwr+Ak/Tq2byfqKs48jh8ba0zlbuC2rL/8rYSh5NReii0ePxN X-Gm-Gg: Acq92OFL/Y3kdSqAdw8kroWlKbyLLpOYSKagBvbmcrK794TbRrMF3nWvVD4gSIXcKIk oFY1kfRQkLsw6FQqrEZs9aXHGNI18NSTOuBpyjR8eygodNzMjnbPgWRhtY7jsffEKGZ4S0CmNUc xJtHienFcDN+4DFkXsVsO3IK3k8R2CcSglp9EyAuc8qCCBAeOHRO50j6W2WJFhOUsCx84pgytoq 5kEcTBGM5mF7oKWWeWQyHqJ7sAcNugLl5Wp51Qf/bwlhJOOnReddmy0VhcA0nYvb5td4YPLR2em P6ocJfcYgra/LsRfEStkOOwFhwMHTu/bidMYFApXWnOZ8cdSFr9hs/CkZMazsHopYziLadmno9j ncVMWTsKm5Si2IDmwYs+h6+Up8DQhK47lJoGagGVhMNXVXUMD0m7K1/TgfsZI/iIZWUWn073dzl Y9lvHp4QTFcqZIaMV5QmstdUael/yYEMLacg== X-Received: by 2002:a05:7300:5729:b0:304:e72a:d4d9 with SMTP id 5a478bee46e88-307ff5f73f5mr630170eec.19.1781126473482; Wed, 10 Jun 2026 14:21:13 -0700 (PDT) Received: from jarvis.. ([2600:8800:1b01:9700:d47:c88c:e69d:2e42]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-3078c1ac378sm19472655eec.1.2026.06.10.14.21.12 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 10 Jun 2026 14:21:12 -0700 (PDT) From: Jay Vadayath To: akpm@linux-foundation.org Cc: Liam.Howlett@oracle.com, elver@google.com, jannh@google.com, linux-kernel@vger.kernel.org, linux-mm@kvack.org, ljs@kernel.org, pfalcato@suse.de, syzbot+38a879f4a73497f2dfef@syzkaller.appspotmail.com, syzkaller-bugs@googlegroups.com, vbabka@kernel.org Subject: Re: [syzbot] [mm?] KCSAN: data-race in mas_wr_slot_store / mtree_range_walk Date: Wed, 10 Jun 2026 14:20:41 -0700 Message-ID: <20260610212111.561726-1-jkrshnmenon@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260423093420.038ce8c6ffb45279d0a809a0@linux-foundation.org> References: <20260423093420.038ce8c6ffb45279d0a809a0@linux-foundation.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hello, I am a security researcher and was able to independently reproduce this data race on an x86-64 host using a KCSAN-enabled build of the affected kernel version (3cd8b194bf3428dfa53120fee47e827a7c495815). The crash can be reliably triggered using the provided PoC. However, I am not aware of a method that can take advantage of this bug to achieve a practical exploit. Reproduction environment: Kernel: linux kernel commit 3cd8b194bf3428dfa53120fee47e827a7c495815 (KCSAN-enabled) QEMU: qemu-system-x86_64, TCG/KVM, 2 vCPUs, 2 GiB RAM Host: x86-64, Linux Expected KCSAN report: [ 9.814310][ T2967] BUG: KCSAN: data-race in mas_wr_slot_store / mtree_range_walk [ 9.814320][ T2967] [ 9.814323][ T2967] write to 0xffff88800c4d0f30 of 8 bytes by task 2966 on cpu 0: [ 9.814328][ T2967] mas_wr_slot_store+0x33e/0x360 [ 9.814335][ T2967] mas_wr_store_entry+0x172/0x1d0 [ 9.814343][ T2967] mas_store_prealloc+0xc3/0x170 [ 9.814351][ T2967] commit_merge+0x247/0x450 [ 9.814360][ T2967] vma_expand+0x195/0x440 [ 9.814368][ T2967] vma_merge_new_range+0x153/0x3a0 [ 9.814376][ T2967] vma_merge_extend+0xe5/0x110 [ 9.814384][ T2967] do_mremap+0xbd8/0xc40 [ 9.814394][ T2967] __do_sys_mremap+0x99/0xd0 [ 9.814403][ T2967] do_syscall_64+0xf9/0x540 [ 9.814411][ T2967] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 9.814417][ T2967] [ 9.814419][ T2967] read to 0xffff88800c4d0f30 of 8 bytes by task 2967 on cpu 1: [ 9.814424][ T2967] mtree_range_walk+0x234/0x440 [ 9.814431][ T2967] mas_state_walk+0x5c/0x80 [ 9.814437][ T2967] mas_walk+0x75/0x190 [ 9.814444][ T2967] lock_vma_under_rcu+0x73/0x500 [ 9.814450][ T2967] madvise_do_behavior+0x29f/0x7b0 [ 9.814457][ T2967] do_madvise+0x12f/0x1b0 [ 9.814463][ T2967] __x64_sys_madvise+0x2c/0x40 [ 9.814469][ T2967] do_syscall_64+0xf9/0x540 [ 9.814476][ T2967] entry_SYSCALL_64_after_hwframe+0x77/0x7f The following patch was applied to scope KCSAN to the target subsystems: --- kcsan.patch --- diff --git a/certs/Makefile b/certs/Makefile index 3ee1960f9f4a..a14f4393963b 100644 --- a/certs/Makefile +++ b/certs/Makefile @@ -1,3 +1,4 @@ +KCSAN_SANITIZE := n # SPDX-License-Identifier: GPL-2.0 # # Makefile for the linux kernel signature checking certificates. diff --git a/io_uring/Makefile b/io_uring/Makefile index c54e328d1410..dea4f90bec8d 100644 --- a/io_uring/Makefile +++ b/io_uring/Makefile @@ -1,3 +1,4 @@ +KCSAN_SANITIZE := n # SPDX-License-Identifier: GPL-2.0 # # Makefile for io_uring diff --git a/kernel/kcsan/Makefile b/kernel/kcsan/Makefile index 824f30c93252..03a0e89b52f6 100644 --- a/kernel/kcsan/Makefile +++ b/kernel/kcsan/Makefile @@ -1,7 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 CONTEXT_ANALYSIS := y -KCSAN_SANITIZE := n KCOV_INSTRUMENT := n UBSAN_SANITIZE := n @@ -9,7 +8,7 @@ CFLAGS_REMOVE_core.o = $(CC_FLAGS_FTRACE) CFLAGS_REMOVE_debugfs.o = $(CC_FLAGS_FTRACE) CFLAGS_REMOVE_report.o = $(CC_FLAGS_FTRACE) -CFLAGS_core.o := $(call cc-option,-fno-conserve-stack) \ +CFLAGS_core.o := $(call cc-option,-fno-conserve-stack) -Wno-builtin-declaration-mismatch -Wno-missing-attributes -Wno-builtin-declaration-mismatch -Wno-missing-attributes \ $(call cc-option,-mno-outline-atomics) \ -fno-stack-protector -DDISABLE_BRANCH_PROFILING diff --git a/lib/Makefile b/lib/Makefile index 72c90fca6fef..6771e3d7ed58 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -337,3 +337,5 @@ CONTEXT_ANALYSIS_test_context-analysis.o := y obj-$(CONFIG_CONTEXT_ANALYSIS_TEST) += test_context-analysis.o subdir-$(CONFIG_FORTIFY_SOURCE) += test_fortify +KCSAN_SANITIZE_maple_tree.o := y +KCSAN_SANITIZE_maple_tree.o := y diff --git a/rust/Makefile b/rust/Makefile index b361bfedfdf0..e19ba2714d22 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -1,3 +1,4 @@ +KCSAN_SANITIZE := n # SPDX-License-Identifier: GPL-2.0 # Where to place rustdoc generated documentation diff --git a/samples/Makefile b/samples/Makefile index 07641e177bd8..9048409de6d0 100644 --- a/samples/Makefile +++ b/samples/Makefile @@ -1,3 +1,4 @@ +KCSAN_SANITIZE := n # SPDX-License-Identifier: GPL-2.0 # Makefile for Linux samples code diff --git a/scripts/Makefile b/scripts/Makefile index 3434a82a119f..95bddac1d1af 100644 --- a/scripts/Makefile +++ b/scripts/Makefile @@ -1,3 +1,4 @@ +KCSAN_SANITIZE := n # SPDX-License-Identifier: GPL-2.0 ### # scripts contains sources for various helper programs used throughout diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib index 0718e39cedda..cdcff805ae16 100644 --- a/scripts/Makefile.lib +++ b/scripts/Makefile.lib @@ -96,7 +96,7 @@ endif # ifeq ($(CONFIG_KCSAN),y) _c_flags += $(if $(patsubst n%,, \ - $(KCSAN_SANITIZE_$(target-stem).o)$(KCSAN_SANITIZE)$(is-kernel-object)), \ + $(KCSAN_SANITIZE_$(target-stem).o)$(KCSAN_SANITIZE)n), \ $(CFLAGS_KCSAN)) # Some uninstrumented files provide implied barriers required to avoid false # positives: set KCSAN_INSTRUMENT_BARRIERS for barrier instrumentation only. diff --git a/usr/Makefile b/usr/Makefile index e8f42478a0b7..4a519330041e 100644 --- a/usr/Makefile +++ b/usr/Makefile @@ -1,3 +1,4 @@ +KCSAN_SANITIZE := n # SPDX-License-Identifier: GPL-2.0 # # kbuild file for usr/ - including initramfs image --- end kcsan.patch --- Once the patch has been applied, the following scripts can be used to reproduce this issue locally. --- build.sh --- #!/usr/bin/env bash # build.sh — Build KCSAN kernel + initramfs + PoC for maple-tree data-race reproduction # # Usage: ./build.sh [kernel-config] set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO="${1:?Usage: $0 [kernel-config]}" REPO="$(cd "$REPO" && pwd)" CONFIG_SRC="${2:-$SCRIPT_DIR/38a879f4a73497f2dfef.config}" NPROC=$(nproc) JOBS=$((NPROC > 16 ? NPROC / 2 : NPROC)) BUILD_DIR="/tmp/kernel-builds/kcsan" LINK_DIR="$SCRIPT_DIR/builds/kcsan" NORMAL_USER="vscode" # ── Install dependencies ────────────────────────────────────────────────────── if ! command -v busybox &>/dev/null; then command -v apt-get &>/dev/null \ && sudo apt-get install -y --no-install-recommends busybox-static >/dev/null \ || { echo "busybox not found. Install busybox-static manually." >&2; exit 1; } fi # ── Build PoC binary ────────────────────────────────────────────────────────── gcc -static -O2 -Wall -Wextra -pthread "$SCRIPT_DIR/poc.c" -o "$SCRIPT_DIR/poc" # ── Patch lib/Makefile for KCSAN symbol visibility ──────────────────────────── if ! grep -q "CFLAGS_maple_tree.o.*fno-inline" "$REPO/lib/Makefile"; then echo 'CFLAGS_maple_tree.o += -fno-inline' >> "$REPO/lib/Makefile" fi # ── Build KCSAN kernel ──────────────────────────────────────────────────────── rm -rf "$BUILD_DIR" && mkdir -p "$BUILD_DIR" mkdir -p "$(dirname "$LINK_DIR")" && rm -rf "$LINK_DIR" && ln -sfn "$BUILD_DIR" "$LINK_DIR" cp "$CONFIG_SRC" "$BUILD_DIR/.config" cat >> "$BUILD_DIR/.config" <<'KCSAN_CONFIG' CONFIG_KCSAN_EARLY_ENABLE=y CONFIG_KCSAN_NUM_WATCHPOINTS=128 CONFIG_KCSAN_SKIP_WATCH=1000 CONFIG_KCSAN_REPORT_ONCE_IN_MS=1000 CONFIG_KCSAN_REPORT_VALUE_CHANGE_ONLY=y # CONFIG_KCSAN_PERMISSIVE is not set # CONFIG_KCSAN_ASSUME_PLAIN_WRITES_ATOMIC is not set CONFIG_KCSAN_IGNORE_ATOMICS=y CONFIG_DEBUG_INFO=y CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y KCSAN_CONFIG make -C "$REPO" O="$BUILD_DIR" olddefconfig 2>&1 | tail -5 make -C "$REPO" O="$BUILD_DIR" -j"$JOBS" bzImage 2>&1 | tail -10 # ── Build initramfs ─────────────────────────────────────────────────────────── BUSYBOX_BIN="$(command -v busybox)" WORKDIR="$(mktemp -d /tmp/initramfs_build.XXXXXX)" trap 'rm -rf "$WORKDIR"' EXIT ROOT="$WORKDIR/rootfs" mkdir -p "$ROOT"/{bin,sbin,usr/bin,usr/sbin,dev,proc,sys,tmp,run,etc} \ "$ROOT"/home/"$NORMAL_USER" cp "$BUSYBOX_BIN" "$ROOT/bin/busybox" && chmod +x "$ROOT/bin/busybox" for applet in $("$BUSYBOX_BIN" --list); do case "$applet" in ifconfig|ip|route|udhcpc|modprobe|insmod|rmmod|halt|poweroff|reboot|mdev) [[ -e "$ROOT/sbin/$applet" ]] || ln -s /bin/busybox "$ROOT/sbin/$applet" ;; *) [[ -e "$ROOT/bin/$applet" ]] || ln -s /bin/busybox "$ROOT/bin/$applet" ;; esac done cat > "$ROOT/etc/passwd" < "$ROOT/etc/shadow" chmod 640 "$ROOT/etc/shadow" printf 'root:x:0:\n%s:x:1000:\n' "$NORMAL_USER" > "$ROOT/etc/group" cp "$SCRIPT_DIR/poc" "$ROOT/poc" && chmod +x "$ROOT/poc" cat > "$ROOT/init" <<'INIT_SCRIPT' #!/bin/sh export PATH=/bin:/sbin:/usr/bin:/usr/sbin mount -t proc proc /proc mount -t sysfs sysfs /sys mount -t devtmpfs dev /dev 2>/dev/null || mdev -s mount -t tmpfs tmpfs /tmp mount -t tmpfs tmpfs /run mount -t debugfs debugfs /sys/kernel/debug 2>/dev/null || true echo 0 > /proc/sys/kernel/printk 2>/dev/null || true [ -f /sys/kernel/debug/kcsan ] && echo on > /sys/kernel/debug/kcsan if [ -x /poc ]; then cp /poc /run/poc && chmod 0755 /run/poc FOUND=0; i=1 while [ $i -le 10 ]; do echo "[init] === poc iteration $i/10 ===" su vscode -s /bin/sh -c /run/poc 2>&1 || /run/poc 2>&1 if dmesg | grep -q "mas_wr_slot_store.*mtree_range_walk\|mtree_range_walk.*mas_wr_slot_store"; then echo "[init] *** RACE REPRODUCED on iteration $i ***" FOUND=1; break fi i=$((i+1)) done echo "[init] poc loop finished, FOUND=$FOUND" echo "=== DMESG START ==="; dmesg; echo "=== DMESG END ===" poweroff -f fi exec /bin/sh INIT_SCRIPT chmod +x "$ROOT/init" ( cd "$ROOT"; find . | sort | cpio -o -H newc 2>/dev/null | gzip -9 > "$BUILD_DIR/initramfs.cpio.gz" ) echo "[build.sh] Kernel: $BUILD_DIR/arch/x86/boot/bzImage" echo "[build.sh] Initramfs: $BUILD_DIR/initramfs.cpio.gz" --- end build.sh --- --- poc.sh --- #!/usr/bin/env bash # poc.sh — Run the KCSAN maple-tree data-race PoC built by build.sh # # Usage: ./poc.sh # Prereq: ./build.sh must have been run first. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" KCSAN_BUILD="$SCRIPT_DIR/builds/kcsan" KERNEL="$KCSAN_BUILD/arch/x86/boot/bzImage" INITRAMFS="$KCSAN_BUILD/initramfs.cpio.gz" LOGS_DIR="$SCRIPT_DIR/logs" if [ ! -f "$KERNEL" ]; then echo "ERROR: kernel not found at $KERNEL — run ./build.sh first" >&2; exit 1 fi if [ ! -f "$INITRAMFS" ]; then echo "ERROR: initramfs not found at $INITRAMFS — run ./build.sh first" >&2; exit 1 fi rm -rf "$LOGS_DIR" && mkdir -p "$LOGS_DIR" if [ -e /dev/kvm ]; then ACCEL="-accel kvm"; TMO=180; else ACCEL="-accel tcg,thread=multi"; TMO=1000; fi KCSAN_ARGS="kcsan.skip_watch=15 kcsan.udelay_task=300 kcsan.udelay_interrupt=150" MAX_ATTEMPTS=3 FOUND=0 for attempt in $(seq 1 $MAX_ATTEMPTS); do SERIAL_LOG="$LOGS_DIR/serial_attempt_${attempt}.log" echo "[poc.sh] === Attempt $attempt/$MAX_ATTEMPTS ===" timeout "$TMO" qemu-system-x86_64 \ $ACCEL -m 2048 -smp 2 \ -kernel "$KERNEL" -initrd "$INITRAMFS" \ -append "console=ttyS0 root=/dev/ram rdinit=/init quiet loglevel=3 printk.devkmsg=off $KCSAN_ARGS" \ -serial file:"$SERIAL_LOG" -display none -no-reboot 2>/dev/null || true if grep -q "mas_wr_slot_store.*mtree_range_walk\|mtree_range_walk.*mas_wr_slot_store" "$SERIAL_LOG" 2>/dev/null; then echo "[poc.sh] *** RACE REPRODUCED on attempt $attempt ***" grep -m1 -A 24 "BUG: KCSAN.*mas_wr_slot_store" "$SERIAL_LOG" | sed 's/\r//' FOUND=1; break fi grep -q "poc loop finished" "$SERIAL_LOG" 2>/dev/null \ && echo "[poc.sh] PoC loop completed, race not triggered" \ || echo "[poc.sh] WARNING: boot incomplete" done echo "============================================================" [ $FOUND -eq 1 ] && echo "[poc.sh] SUCCESS" || echo "[poc.sh] NOT reproduced in $MAX_ATTEMPTS attempts" echo "============================================================" --- end poc.sh --- --- poc.c --- /* * poc.c - KCSAN data-race in mas_wr_slot_store / mtree_range_walk * (the *exact* syzbot 38a879f4a73497f2dfef race — the genuine double-fetch) * * Writer: mremap()-grow/shrink a VMA against a larger adjacent gap * -> vma_expand() -> commit_merge() -> mas_store_prealloc() * -> mas_wr_store_entry() -> mas_wr_slot_store() * writes wr_mas->pivots[offset] in place (boundary shift, entry count * unchanged) -- this is the reported writer. * Reader: page fault -> lock_vma_under_rcu() -> mas_walk() -> mtree_range_walk() * reads pivots[offset] locklessly (the double-fetched pivot). * * Why mremap (not mmap MAP_FIXED + mprotect like poc.c): the slot-store fast path * (mas_wr_store_type() -> wr_slot_store) is only taken when a store shifts a single * boundary WITHOUT changing the node's entry count, which in RCU mode additionally * requires wr_mas->offset_end - mas->offset == 1. Growing/shrinking a VMA into an * adjacent gap that *survives* (residual gap => entry count unchanged) hits exactly * that. MAP_FIXED/mprotect split the VMA (entry count changes) and take the * node-store/replace path instead, which is what poc.c trips. * * Build: gcc -static -O2 -Wall -pthread poc.c -o poc * Run: ./poc (needs a KCSAN-enabled kernel; ~30s) */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #define PAGE 4096UL #define LANE_PAGES 64UL /* address span reserved per lane */ #define LANE_SIZE (LANE_PAGES * PAGE) #define NLANES 32UL /* independent anchor/gap lanes */ #define BASE_PAGES 2UL /* anchor VMA baseline size (pages) */ static atomic_int g_stop; static atomic_int g_ready; /* SIGSEGV/SIGBUS recovery: touching a gap (or a region the writer just shrank * away) faults; we still want the kernel to have run lock_vma_under_rcu first. */ static __thread sigjmp_buf tl_jmp; static __thread volatile sig_atomic_t tl_jmp_active; static void segv_handler(int sig, siginfo_t *si, void *uctx) { (void)sig; (void)si; (void)uctx; if (tl_jmp_active) { tl_jmp_active = 0; siglongjmp(tl_jmp, 1); } _exit(111); } struct ctx { uint8_t *base; /* base of the reserved arena */ int seconds; int cpu; }; static void pin_cpu(int cpu) { if (cpu < 0) return; cpu_set_t set; CPU_ZERO(&set); CPU_SET(cpu, &set); (void)sched_setaffinity(0, sizeof(set), &set); } static uint64_t nsec_now(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec; } static inline uint8_t *lane_base(uint8_t *base, unsigned lane) { return base + (size_t)lane * LANE_SIZE; } /* * Writer: for each lane, repeatedly mremap()-grow the small anchor into the * adjacent gap and then shrink it back. Each grow/shrink shifts the single * boundary between the anchor VMA and the surviving gap -> mas_wr_slot_store() * updates pivots[offset] in place. */ static void *writer_fn(void *arg) { struct ctx *c = (struct ctx *)arg; pin_cpu(c->cpu); atomic_fetch_add(&g_ready, 1); while (atomic_load(&g_ready) < 2) sched_yield(); uint64_t end = nsec_now() + (uint64_t)c->seconds * 1000000000ull; unsigned iter = 0; while (!atomic_load(&g_stop) && nsec_now() < end) { unsigned lane = iter % NLANES; uint8_t *a = lane_base(c->base, lane); /* Oscillate the anchor between BASE_PAGES and a larger size, both > 1 page * and both leaving a residual gap (grow_pages <= LANE_PAGES-2). The entry * count (anchor VMA + surviving gap) never changes and the store always * shifts exactly one boundary => mas_wr_store_type() picks wr_slot_store * every time, instead of the node-store/rebalance paths that a shrink to * a single page can trigger. */ unsigned long grow_pages = 3 + (iter % (LANE_PAGES - 4)); /* 3..LANE_PAGES-2 */ size_t big = grow_pages * PAGE; size_t base = BASE_PAGES * PAGE; /* Grow base -> big (boundary shifts right), then shrink big -> base * (boundary shifts left). Both are in-place pivot updates. */ void *p = mremap(a, base, big, 0); if (p == MAP_FAILED) { iter++; continue; } p = mremap(a, big, base, 0); (void)p; iter++; } atomic_store(&g_stop, 1); return NULL; } /* * Reader: fault addresses across each lane (anchor + gap) so the lockless VMA * lookup (lock_vma_under_rcu -> mas_walk -> mtree_range_walk) walks the leaf * holding the boundary the writer is shifting, reading pivots[offset]. */ static void *reader_fn(void *arg) { struct ctx *c = (struct ctx *)arg; pin_cpu(c->cpu); atomic_fetch_add(&g_ready, 1); while (atomic_load(&g_ready) < 2) sched_yield(); uint64_t end = nsec_now() + (uint64_t)c->seconds * 1000000000ull; unsigned iter = 0; while (!atomic_load(&g_stop) && nsec_now() < end) { unsigned lane = iter % NLANES; uint8_t *a = lane_base(c->base, lane); /* Drop the anchor page so the next touch faults through the lockless path. */ madvise(a, PAGE, MADV_DONTNEED); /* Touch several offsets within the lane: the anchor page plus addresses * deeper in the (possibly grown / possibly gap) region. Each is a fault * that runs mtree_range_walk; gap touches segfault and are recovered. */ for (unsigned long off = 0; off < LANE_PAGES; off += 4) { volatile uint8_t *q = (volatile uint8_t *)(a + off * PAGE); if (sigsetjmp(tl_jmp, 1) == 0) { tl_jmp_active = 1; (void)*q; /* read fault -> lock_vma_under_rcu */ tl_jmp_active = 0; } if (atomic_load(&g_stop)) break; } iter++; } atomic_store(&g_stop, 1); return NULL; } int main(void) { int seconds = 30; struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = segv_handler; sa.sa_flags = SA_SIGINFO | SA_NODEFER; sigemptyset(&sa.sa_mask); sigaction(SIGSEGV, &sa, NULL); sigaction(SIGBUS, &sa, NULL); size_t arena = NLANES * LANE_SIZE; /* Reserve the whole arena, then punch each lane down to a BASE_PAGES anchor * followed by a (LANE_PAGES-BASE_PAGES)-page gap. */ uint8_t *base = mmap(NULL, arena, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (base == MAP_FAILED) { perror("mmap(arena)"); return 1; } for (unsigned lane = 0; lane < NLANES; lane++) { uint8_t *a = lane_base(base, lane); /* keep [a, a+BASE_PAGES*PAGE) mapped as the anchor; unmap the rest as a gap */ if (munmap(a + BASE_PAGES * PAGE, LANE_SIZE - BASE_PAGES * PAGE) != 0) perror("munmap(gap)"); } fprintf(stderr, "[poc] base=%p lanes=%lu lane_size=%luKB seconds=%d\n", (void *)base, NLANES, LANE_SIZE / 1024, seconds); struct ctx cw = { .base = base, .seconds = seconds, .cpu = 0 }; struct ctx cr = { .base = base, .seconds = seconds, .cpu = 1 }; atomic_store(&g_stop, 0); atomic_store(&g_ready, 0); pthread_t tw, tr; if (pthread_create(&tw, NULL, writer_fn, &cw) != 0) { perror("pthread_create(writer)"); return 1; } if (pthread_create(&tr, NULL, reader_fn, &cr) != 0) { perror("pthread_create(reader)"); return 1; } pthread_join(tw, NULL); pthread_join(tr, NULL); fprintf(stderr, "[poc] done\n"); return 0; } --- end poc.c --- Thanks, Jay Vadayath