mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* ntsync: absolute MONOTONIC timeout ignores time namespace offset
@ 2026-05-18 10:21 Maoyi Xie
  2026-05-21  3:44 ` Elizabeth Figura
  2026-05-21 15:39 ` Maoyi Xie
  0 siblings, 2 replies; 3+ messages in thread
From: Maoyi Xie @ 2026-05-18 10:21 UTC (permalink / raw)
  To: zfigura, arnd, Greg Kroah-Hartman; +Cc: wine-devel, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1668 bytes --]

Hi all,

While reading drivers/misc/ntsync.c I noticed something that
might be a time-namespace bug. I would appreciate it if you
could take a look and let me know whether this is a real bug,
and whether it is worth fixing.

ntsync_schedule() takes the user-supplied absolute timeout
and hands it to schedule_hrtimeout_range_clock() with
HRTIMER_MODE_ABS. For the default CLOCK_MONOTONIC path, it
does not call timens_ktime_to_host() first.

A process inside a CLOCK_MONOTONIC time namespace computes
the absolute timeout in its own clock view. The kernel reads
the same value against the host clock. The two differ by the
namespace offset.

The other absolute-timeout consumers (timerfd, posix-timers,
alarmtimer, posix-stubs, futex) all run user-supplied
absolute MONOTONIC ktime through timens_ktime_to_host()
before hrtimer. ntsync was added post-5.17 and seems to have
missed that step.

/dev/ntsync is mode 0666, so any user in a time namespace
that can open it is affected. The user-visible impact is
wrong timeout behaviour for Wine inside a container that
sets a CLOCK_MONOTONIC offset.

Attached PoC: baseline run elapsed ~100 ms; the --in-timens
run inside a -10s namespace elapsed ~0 ms.

A candidate fix would call timens_ktime_to_host(clock, timeout)
in the else branch (i.e., when NTSYNC_WAIT_REALTIME is not set)
before passing the value to schedule_hrtimeout_range_clock().

If this is intentional or already known, please disregard.
Otherwise, I am happy to send a [PATCH] or to leave the fix to you.
Thank you for your time, and sorry for the noise if this is not actually
a bug or has already been spotted.

Thanks,
Maoyi Xie
https://maoyixie.com/

[-- Attachment #2: poc_ntsync_timens.log --]
[-- Type: application/octet-stream, Size: 1514 bytes --]

# bug24 PoC verification on linux-7.1-rc1 (mainline vanilla)
#
# Test platform:
#   qemu-system-x86_64 -enable-kvm -smp 4 -m 4G
#   bzImage: vm/build-7.1-rc1-vanilla/arch/x86/boot/bzImage
#   rootfs:  vm/bookworm.qcow2 (debian bookworm)
#   ntsync.ko: built CONFIG_NTSYNC=m from the same tree
#
# /dev/ntsync registers with mode 0666, so any user inside the VM
# (and any container exposing /dev/ntsync) can open it.

### Unpatched (linux-7.1-rc1 vanilla) ###

$ uname -a
Linux nsprobe 7.1.0-rc1-00001-g196d9a9bfa9b #2 SMP PREEMPT_DYNAMIC

$ insmod /root/ntsync.ko
$ ls -la /dev/ntsync
crw-rw-rw- 1 root root 10, 258 May 17 16:19 /dev/ntsync

$ ./poc_ntsync_timens                          # baseline (no timens)
[parent] baseline (no timens)
[wait] ret=-1 errno=110 (Connection timed out) elapsed=100 ms

$ ./poc_ntsync_timens --in-timens              # inside CLOCK_MONOTONIC -10s
[child] in CLOCK_MONOTONIC -10s time namespace
[wait] ret=-1 errno=110 (Connection timed out) elapsed=0 ms

### Patched (linux-7.1-rc1 + 0001-ntsync-timens-fix.patch) ###

$ uname -a
Linux nsprobe 7.1.0-rc1-00001-g196d9a9bfa9b-dirty #3 SMP PREEMPT_DYNAMIC

$ insmod /root/ntsync.ko
$ ./poc_ntsync_timens                          # baseline (no timens)
[parent] baseline (no timens)
[wait] ret=-1 errno=110 (Connection timed out) elapsed=100 ms

$ ./poc_ntsync_timens --in-timens              # inside CLOCK_MONOTONIC -10s
[child] in CLOCK_MONOTONIC -10s time namespace
[wait] ret=-1 errno=110 (Connection timed out) elapsed=100 ms

[-- Attachment #3: poc_ntsync_timens.c --]
[-- Type: application/octet-stream, Size: 4074 bytes --]

/*
 * PoC: ntsync NTSYNC_IOC_WAIT_ANY ignores the CLOCK_MONOTONIC
 *      time namespace offset.
 *
 * ntsync passes a user-supplied absolute timeout to hrtimer
 * with HRTIMER_MODE_ABS. It does not call timens_ktime_to_host()
 * first. Inside a CLOCK_MONOTONIC time namespace, the user view
 * and the host view of "now" differ by the namespace offset.
 *
 * Build: cc -O0 -g poc_ntsync_timens.c -o poc_ntsync_timens
 * Run:   ./poc_ntsync_timens               # baseline
 *        ./poc_ntsync_timens --in-timens   # inside -10s timens
 *
 * Baseline:  elapsed ~ 100 ms
 * In-timens: elapsed ~ 0   ms
 */

#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <linux/types.h>

#ifndef CLONE_NEWTIME
#define CLONE_NEWTIME 0x00000080
#endif

/* Mirror of include/uapi/linux/ntsync.h. */
struct ntsync_sem_args { __u32 count; __u32 max; };
struct ntsync_wait_args {
	__u64 timeout;
	__u64 objs;
	__u32 count;
	__u32 index;
	__u32 flags;
	__u32 owner;
	__u32 alert;
	__u32 pad;
};

#define NTSYNC_IOC_CREATE_SEM   _IOW ('N', 0x80, struct ntsync_sem_args)
#define NTSYNC_IOC_WAIT_ANY     _IOWR('N', 0x82, struct ntsync_wait_args)

static int run_wait(void)
{
	int fd = open("/dev/ntsync", O_RDWR | O_CLOEXEC);
	if (fd < 0) {
		fprintf(stderr, "open /dev/ntsync: %s\n", strerror(errno));
		return 1;
	}

	struct ntsync_sem_args sa = { .count = 0, .max = 1 };
	int sem_fd = ioctl(fd, NTSYNC_IOC_CREATE_SEM, &sa);
	if (sem_fd < 0) {
		fprintf(stderr, "ioctl CREATE_SEM: %s\n", strerror(errno));
		return 1;
	}

	struct timespec ts;
	clock_gettime(CLOCK_MONOTONIC, &ts);
	__u64 now_ns = (__u64)ts.tv_sec * 1000000000ULL + ts.tv_nsec;
	__u64 timeout_ns = now_ns + 100ULL * 1000000ULL; /* +100ms */

	struct ntsync_wait_args wa = {
		.timeout = timeout_ns,
		.objs    = (__u64)(uintptr_t)&sem_fd,
		.count   = 1,
		.index   = 0,
		.flags   = 0,     /* CLOCK_MONOTONIC */
		.owner   = 0,
		.alert   = 0,
	};

	struct timespec t0, t1;
	clock_gettime(CLOCK_MONOTONIC, &t0);
	int ret = ioctl(fd, NTSYNC_IOC_WAIT_ANY, &wa);
	int err = errno;
	clock_gettime(CLOCK_MONOTONIC, &t1);

	long ms = (t1.tv_sec - t0.tv_sec) * 1000 +
		  (t1.tv_nsec - t0.tv_nsec) / 1000000;
	printf("[wait] ret=%d errno=%d (%s) elapsed=%ld ms\n",
	       ret, err, strerror(err), ms);

	close(sem_fd);
	close(fd);
	return 0;
}

static int set_timens(long monotonic_offset_sec)
{
	if (unshare(CLONE_NEWUSER) < 0) {
		fprintf(stderr, "unshare(CLONE_NEWUSER): %s\n", strerror(errno));
		return 1;
	}
	int fd;
	if ((fd = open("/proc/self/setgroups", O_WRONLY)) >= 0) {
		write(fd, "deny", 4);
		close(fd);
	}
	if ((fd = open("/proc/self/uid_map", O_WRONLY)) >= 0) {
		dprintf(fd, "0 %d 1", getuid());
		close(fd);
	}
	if ((fd = open("/proc/self/gid_map", O_WRONLY)) >= 0) {
		dprintf(fd, "0 %d 1", getgid());
		close(fd);
	}

	if (unshare(CLONE_NEWTIME) < 0) {
		fprintf(stderr, "unshare(CLONE_NEWTIME): %s\n", strerror(errno));
		return 1;
	}

	fd = open("/proc/self/timens_offsets", O_WRONLY);
	if (fd < 0) {
		fprintf(stderr, "open timens_offsets: %s\n", strerror(errno));
		return 1;
	}
	char buf[64];
	int n = snprintf(buf, sizeof(buf), "monotonic %ld 0\n",
			 monotonic_offset_sec);
	if (write(fd, buf, n) < 0) {
		fprintf(stderr, "write timens_offsets: %s\n", strerror(errno));
		close(fd);
		return 1;
	}
	close(fd);
	return 0;
}

int main(int argc, char **argv)
{
	int in_timens = 0;
	for (int i = 1; i < argc; i++) {
		if (!strcmp(argv[i], "--in-timens"))
			in_timens = 1;
	}

	if (in_timens) {
		if (set_timens(-10) != 0)
			return 1;
		/* fork so child re-execs into the new time namespace */
		pid_t pid = fork();
		if (pid < 0) { perror("fork"); return 1; }
		if (pid == 0) {
			printf("[child] in CLOCK_MONOTONIC -10s time namespace\n");
			return run_wait();
		}
		int status;
		waitpid(pid, &status, 0);
		return WEXITSTATUS(status);
	}

	printf("[parent] baseline (no timens)\n");
	return run_wait();
}

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-05-21 15:39 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-05-18 10:21 ntsync: absolute MONOTONIC timeout ignores time namespace offset Maoyi Xie
2026-05-21  3:44 ` Elizabeth Figura
2026-05-21 15:39 ` Maoyi Xie

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®