From: 徐晗翔 <xuhx@hust.edu.cn>
To: tglx@kernel.org, mingo@redhat.com, anna-maria@linutronix.de,
frederic@kernel.org
Cc: linux-kernel@vger.kernel.org
Subject: futex/hrtimer: hrtimer_sleeper::task is read unsynchronized in 16 places on v7.3 -- the accessor series that fixes this is not in 7.3; please make sure it reaches the affected trees (backport nudge + stock-kernel KCSAN evidence)
Date: Tue, 3 Nov 2026 19:30:17 +0800 (GMT+08:00) [thread overview]
Message-ID: <2f91781e.1d651.1a1a12c9b65.Coremail.xuhx@hust.edu.cn> (raw)
In-Reply-To: <2026102727-data-sequel-ce41@gregkh>
[-- Attachment #1: Type: text/plain, Size: 3509 bytes --]
On v7.3, struct hrtimer_sleeper::task is written by the hrtimer
callback in hard interrupt context (hrtimer_wakeup(): t->task = NULL,
plus the start path's assignment) and read without any synchronization
in sixteen places across nine files -- the futex wait/requeue/PI
paths (kernel/futex/waitwake.c:386/542/593/728, requeue.c:747,
rtmutex.c:1647), the nanosleep core (kernel/time/hrtimer.c:2435/2441/
2445, sleep_timeout.c:215/223), wait.h:559, aio, io_uring/rw.c and
pktgen. The impact is bounded -- misclassification of a return code
(-ERESTARTSYS seen where -ETIMEDOUT is due, or an extra restart) with
no UAF and no lost wakeup -- which matches the analysis in the
upstream series that already fixes this (P. McKenney, 2026-07-31:
mark the field __private and route every access through
hrtimer_sleeper_task_get/set() accessors; nine patches). That series
is not in v7.3. I am writing to (a) confirm the analysis with
stock-kernel KCSAN evidence on 7.3 -- 48+ reports within ~20 s of
targeted microsecond-timeout load, exactly at the audited pairs
(__futex_wait vs hrtimer_wakeup, do_nanosleep vs hrtimer_wakeup),
and (b) ask that the series be applied/backported to the trees that
still carry the raw accesses, since every one of them keeps being a
KCSAN report factory (and a correctness smell) until then.
The detailed description:
* The unsynchronized pattern (v7.3)
Writers (2): hrtimer_sleeper_start_expires()'s task assignment and,
in hard IRQ context, hrtimer_wakeup() setting t->task = NULL
(kernel/time/hrtimer.c:2323-2333) when the timer expires.
Readers (16): each checks "did the timer already fire?" by reading
sleeper->task -- e.g. __futex_wait's return-code selection
(waitwake.c:728), futex_wait_requeue_pi paths (:542/:593/:386),
requeue.c:747, rt_mutex_waiter (rtmutex.c:1647), do_nanosleep's
restart decision (hrtimer.c:2435/2441/2445), etc. None holds a
lock or uses READ_ONCE against the IRQ writer.
* Evidence (KCSAN on a STOCK 7.3 kernel -- unlike slub, these files
are instrumented; no diagnostic build needed)
Targeted microsecond-timeout workloads (userspace static binary
plus a small module for the in-kernel waiters): futex with tiny
timeouts (146k/141k calls), FUTEX_WAIT_REQUEUE_PI (122k), nanosleep
(363k), epoll (2.89M), aio (531k), usleep_range /
wait_event_hrtimeout (581k each). Within ~20 seconds of load
start:
BUG: KCSAN: data-race in __futex_wait / hrtimer_wakeup
read-write to 0x... of 8 bytes by interrupt on cpu 1:
hrtimer_wakeup+0x17 <- t->task = NULL
__hrtimer_run_queues <- hrtimer_interrupt <- apic timer
read to 0x... of 8 bytes by task on cpu 3:
__futex_wait+0xc9 <- the waitwake.c:728
read, held in KCSAN's
watchpoint delay
futex_wait -> do_futex -> __x64_sys_futex
and the same shape for do_nanosleep vs hrtimer_wakeup
(do_nanosleep+0xbe = hrtimer.c:2435/2441). 48+ targeted reports
across the two representative read sites; the other audited read
sites share the identical mechanism (sampling/limiting is why they
were not individually collected).
Reproducers attached: fxfut.c (userspace, six thread classes) and
fxhrt.c (module driving usleep_range/wait_event_hrtimeout hammer).
Run on any KCSAN kernel with skip_watch/udelay tuned; first hits
arrive within tens of seconds.
Regards,
Hanxiang Xu
xuhx@hust.edu.cn
[-- Attachment #2: fxhrt.c --]
[-- Type: text/plain, Size: 2619 bytes --]
// SPDX-License-Identifier: GPL-2.0
/*
* fxhrt.c - KCSAN hammer for Case 2 of the kernel/futex manual audit,
* kernel-context side:
*
* hrtimer_sleeper::task plain reads racing the hardirq writer
* hrtimer_wakeup() (kernel/time/hrtimer.c:2323-2333 t->task = NULL).
*
* Sites exercised here:
* R10/R11 schedule_hrtimeout_range_clock() kernel/time/sleep_timeout.c:215/223
* R12 __wait_event_hrtimeout include/linux/wait.h:559
* (user-space side fxfut covers futex R1-R5, do_nanosleep R7-R9, epoll/aio)
*
* Method: kthreads looping microsecond sleeps so the timer expires within
* the KCSAN watch window of the immediately-following ->task read.
* usleep_range() reaches schedule_hrtimeout_range_clock() via
* do_usleep_range(); wait_event_hrtimeout() expands the wait.h macro.
*/
#define pr_fmt(fmt) "FXHRT: " fmt
#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/wait.h>
#include <linux/slab.h>
static int threads = 4;
static int secs = 90;
module_param(threads, int, 0);
module_param(secs, int, 0);
static DECLARE_WAIT_QUEUE_HEAD(fxwq);
static struct task_struct *htask[8];
static atomic_t stop_flag;
static atomic64_t n_usleep, n_wevt;
static int fx_hrt(void *arg)
{
unsigned long t;
while (!kthread_should_stop() && !atomic_read(&stop_flag)) {
/* R10/R11: schedule_hrtimeout_range_clock() t.task reads */
usleep_range(1, 2);
atomic64_inc(&n_usleep);
/* R12: __wait_event_hrtimeout __t.task read */
t = atomic64_inc_return(&n_wevt);
wait_event_hrtimeout(fxwq, (t & 1) == 2, 2);
}
return 0;
}
static int __init fxhrt_init(void)
{
long i;
int ret;
if (threads > 8)
threads = 8;
for (i = 0; i < threads; i++) {
struct task_struct *t = kthread_create(fx_hrt, (void *)i,
"fxhrt/%ld", i);
if (IS_ERR(t)) { ret = PTR_ERR(t); goto err; }
kthread_bind(t, (i + 4) % nr_cpu_ids);
wake_up_process(t);
htask[i] = t;
}
pr_info("%d hrt sleeper threads running (usleep_range 1-2us + wait_event_hrtimeout 2us)\n",
threads);
return 0;
err:
atomic_set(&stop_flag, 1);
for (i = 0; i < 8; i++)
if (htask[i])
kthread_stop(htask[i]);
return ret;
}
static void __exit fxhrt_exit(void)
{
int i;
atomic_set(&stop_flag, 1);
for (i = 0; i < 8; i++)
if (htask[i])
kthread_stop(htask[i]);
pr_info("totals: usleep_range=%lld wait_event_hrtimeout=%lld\n",
atomic64_read(&n_usleep), atomic64_read(&n_wevt));
}
module_init(fxhrt_init);
module_exit(fxhrt_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("KCSAN hammer: hrtimer_sleeper::task readers vs hrtimer_wakeup (audit Case 2)");
[-- Attachment #3: fxfut.c --]
[-- Type: text/plain, Size: 3505 bytes --]
// SPDX-License-Identifier: GPL-2.0
/* fxfut.c - KCSAN hammer for Case 2, user-space side.
*
* Threads hammering microsecond-timeout waits so the armed hrtimer
* expires inside the KCSAN watch window of the following ->task read:
*
* R1/R4 futex(FUTEX_WAIT_PRIVATE, .., 1-2us) -> futex_do_wait/__futex_wait
* R1 futex(FUTEX_WAIT_BITSET_PRIVATE, ...) -> futex_do_wait
* R5 futex(FUTEX_WAIT_REQUEUE_PI_PRIVATE..) -> handle_early_requeue_pi_wakeup
* R7-R9 nanosleep(1-2us) -> do_nanosleep
* R10 epoll_wait(1ms) -> schedule_hrtimeout_range_clock
* R13 io_getevents(1-2us timeout) -> aio_read_events_ring
*
* usage: fxfut <threads-per-kind> <seconds>
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/syscall.h>
#include <sys/epoll.h>
#include <time.h>
#ifndef SYS_futex
#define SYS_futex 202
#endif
#define FUTEX_WAIT 0
#define FUTEX_WAIT_BITSET 9
#define FUTEX_WAIT_REQUEUE_PI 11
#define FUTEX_PRIVATE_FLAG 128
#ifndef SYS_io_setup
#define SYS_io_setup 206
#define SYS_io_getevents 208
#endif
static volatile int running = 1;
static long cnt_futex, cnt_futexb, cnt_rqi, cnt_nano, cnt_epoll, cnt_aio;
static int n_threads = 3, secs = 90;
#define TS_US(nsec) ({ static struct timespec _ts; _ts.tv_sec = 0; _ts.tv_nsec = (nsec); &_ts; })
static void *tf_futex(void *a)
{
unsigned int word = 0;
while (running) {
syscall(SYS_futex, &word, FUTEX_WAIT | FUTEX_PRIVATE_FLAG,
word, TS_US(1000), NULL, 0);
cnt_futex++;
}
return NULL;
}
static void *tf_futexb(void *a)
{
unsigned int word = 0;
while (running) {
syscall(SYS_futex, &word, FUTEX_WAIT_BITSET | FUTEX_PRIVATE_FLAG,
word, TS_US(2000), NULL, 0xffffffff);
cnt_futexb++;
}
return NULL;
}
static void *tf_rqi(void *a)
{
unsigned int u1 = 0, u2 = 0;
while (running) {
syscall(SYS_futex, &u1, FUTEX_WAIT_REQUEUE_PI | FUTEX_PRIVATE_FLAG,
0, TS_US(2000), &u2, 0);
cnt_rqi++;
}
return NULL;
}
static void *tf_nano(void *a)
{
while (running) {
nanosleep(TS_US(1000), NULL);
cnt_nano++;
}
return NULL;
}
static void *tf_epoll(void *a)
{
int fd = epoll_create1(0);
struct epoll_event ev;
while (running && fd >= 0) {
epoll_wait(fd, &ev, 0, 1);
cnt_epoll++;
}
return NULL;
}
static void *tf_aio(void *a)
{
unsigned long ctx = 0;
if (syscall(SYS_io_setup, 4, &ctx) < 0)
return NULL;
while (running) {
syscall(SYS_io_getevents, ctx, 0, 0, NULL, TS_US(2000));
cnt_aio++;
}
return NULL;
}
int main(int argc, char **argv)
{
pthread_t th[64];
int n = 0, i;
if (argc > 1) n_threads = atoi(argv[1]);
if (argc > 2) secs = atoi(argv[2]);
if (n_threads < 1) n_threads = 1;
if (n_threads > 8) n_threads = 8;
for (i = 0; i < n_threads; i++) {
pthread_create(&th[n++], NULL, tf_futex, NULL);
pthread_create(&th[n++], NULL, tf_futexb, NULL);
pthread_create(&th[n++], NULL, tf_rqi, NULL);
pthread_create(&th[n++], NULL, tf_nano, NULL);
pthread_create(&th[n++], NULL, tf_epoll, NULL);
pthread_create(&th[n++], NULL, tf_aio, NULL);
}
printf("FXFUT: %d threads x 6 kinds, %ds\n", n_threads, secs);
fflush(stdout);
sleep(secs);
running = 0;
for (i = 0; i < n; i++)
pthread_join(th[i], NULL);
printf("FXFUT: totals futex=%ld futexb=%ld rqi=%ld nano=%ld epoll=%ld aio=%ld\n",
cnt_futex, cnt_futexb, cnt_rqi, cnt_nano, cnt_epoll, cnt_aio);
fflush(stdout);
return 0;
}
next prev parent reply other threads:[~2026-11-03 11:30 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-27 12:35 usb: devio: usbdev_release() has no barrier against an in-flight async_completed() callback (use-after-free window on struct usb_dev_state)gregkh@linuxfoundation.org 徐晗翔
2026-10-27 14:12 ` Greg KH
2026-11-03 11:21 ` mm/slub: has_pcs_used() reads remote per-CPU sheaf state from flush_all_cpus_locked() without synchronization (data race; KCSAN blind spot: slub.o is uninstrumented) 徐晗翔
2026-11-03 11:30 ` 徐晗翔 [this message]
2026-11-03 11:48 ` cgroup: fork vs cgroup.procs write race in favordynmods mode -- v7.3 is affected; please make sure bf6fbf46b5ff reaches the stable trees (repro incl. uninstrumented natural hit) 徐晗翔
2026-11-03 14:51 ` Sasha Levin
2026-11-03 11:57 ` net: rtnetlink: rtnl_link_unregister() vs in-flight RTM_GETLINK dump readers -- v7.3 lacks the synchronize_rcu() fix (Fixes: 6b57ff21a310 family); A/B reproduction on stock 7.3 徐晗翔
2026-11-03 15:49 ` Eric Dumazet
2026-11-03 12:07 ` wifi: wilc1000: SDIO/SPI remove destroys the srcu_struct before wiphy_unregister() -- late cfg80211 callbacks write through dangling srcu state (unfixed upstream; controlled + natural Oops) 徐晗翔
2026-11-03 12:10 ` gpio: gpiolib: final put of the gpio_device happens inside the SRCU read critical section on the setup-failure path -- deterministic use-after-free read at guard release (unfixed upstream) 徐晗翔
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=2f91781e.1d651.1a1a12c9b65.Coremail.xuhx@hust.edu.cn \
--to=xuhx@hust.edu.cn \
--cc=anna-maria@linutronix.de \
--cc=frederic@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mingo@redhat.com \
--cc=tglx@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®