mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
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;
}

  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®