โฌข DragonFlyBSD Kernel Audit
DF-3999 / emldmaht_harness_fixed.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
/*
 * DF-3998 / DF-3999 โ€” verbatim-code logic harness for the envy24ht(4)
 * DMA-emulator out-of-bounds indices (sys/dev/sound/pci/envy24ht.c).
 *
 * The five emldma functions below are copied VERBATIM (only #if(0) debug
 * blocks omitted) from:
 *
 *   envy24ht_p32sl  sys/dev/sound/pci/envy24ht.c:1311-1339
 *   envy24ht_p16sl  sys/dev/sound/pci/envy24ht.c:1341-1384
 *   envy24ht_p8u    sys/dev/sound/pci/envy24ht.c:1386-1414
 *   envy24ht_r32sl  sys/dev/sound/pci/envy24ht.c:1416-1444
 *   envy24ht_r16sl  sys/dev/sound/pci/envy24ht.c:1446-1474
 *   offset math     envy24htchan_trigger else-branch, envy24ht.c:1721-1723
 *   hwptr math      envy24ht_gethwptr, envy24ht.c:1124-1152
 *   channel map     envy24ht_chanmap[], envy24ht.c:221-233
 *   registration    envy24ht_pci_attach, envy24ht.c:2494-2505
 *                   (1 play channel + (2+adcn) rec channels; adcn =
 *                    ((scfg & 0x0c)>>2)+1; standard board scfg=0x0b ->
 *                    adcn=3 -> 5 rec channels, hw nums {1,2,3,4,10} ->
 *                    slot=(num-5)*2 = {-8,-6,-4,-2,+10})
 *
 * Environment model (matches envy24ht.c + the pcm core):
 *   - pbuf = ENVY24HT_PLAY_BUFUNIT(32) * ENVY24HT_SAMPLE_NUM(4096)
 *          = 131072 bytes = 32768 sample32_t entries      (envy24ht.c:2160)
 *   - rbuf = ENVY24HT_REC_BUFUNIT(8) * 4096 = 32768 bytes
 *          = 8192 sample32_t entries                      (envy24ht.c:2161)
 *   - ch->size = unit * ENVY24HT_SAMPLE_NUM               (envy24ht.c:1584)
 *   - readyptr/freeptr in [0, ch->size) โ€” controlled by how many bytes the
 *     unprivileged user wrote/read on the channel before triggering
 *   - ptr = envy24ht_gethwptr() frame index in [0,4095] of the RUNNING
 *     first same-direction channel โ€” user-observable via
 *     SNDCTL_DSP_GETOPTR/GETIPTR on that channel
 *   - ch->blk = as produced by envy24htchan_setblocksize
 *     (envy24ht.c:1648-1659) for a user-chosen blocksize:
 *       rec S16:  blk = prev/4*2 = prev/2   (prev = power of 2 <= size/2)
 *       play S16: blk = prev/4*8 = prev*2
 *
 * Cases (soft mode; canary arenas sized around the exact kernel buffers):
 *   [A] DF-3999: FIRST record channel (hw num 1 -> slot -8), offset 0:
 *       EMLDMARD reads sample32_t entries at indices -8..-1 (32 bytes
 *       BEFORE rbuf) into the user record stream.
 *   [B] DF-3999: 5th record channel (hw num 10 -> slot +10), offset 0:
 *       reads up to 10 entries PAST the rbuf end, unconditionally.
 *   [C] DF-3998: SECOND record channel START (S16_LE, attacker-chosen
 *       ptr/blk -> offset 4088): first loop access reads ~32 KB past rbuf
 *       into the user record stream (kernel heap disclosure).
 *   [D] DF-3998 play twin (p16sl): same unclamped dst=src/2+offset writes
 *       attacker PCM up to ~130 KB past pbuf โ€” write primitive exists but
 *       needs 2 running PLAY channels; the driver registers exactly 1
 *       (envy24ht.c:2494-2499) so on the driver as shipped this is LATENT
 *       (goes live if the commented-out 5-play-channel loop is restored).
 *
 * hard mode: rbuf against a PROT_NONE guard page sized exactly like the
 * kernel bus_dmamem allocation -> case [C] faults, proving the index
 * arithmetic the kernel would run against real kernel memory.
 *
 * Kernel-heap stand-in markers (chosen so the leaked value survives the
 * 16-bit truncation of r16sl):
 *   before rbuf: 0xABADBE00 | (i & 0xff)   -> 32-bit 0xABADBExx / 16-bit 0xBExx
 *   past rbuf:   0xDEADFE00 | (i & 0xff)   -> 32-bit 0xDEADFExx / 16-bit 0xFExx
 *   past pbuf:   0xDEAD0000 | i            (overwritten value 0xB9B90000)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <signal.h>
#include <setjmp.h>

#define ENVY24HT_PLAY_CHNUM 8
#define ENVY24HT_REC_CHNUM 2
#define ENVY24HT_SAMPLE_NUM 4096
#define ENVY24HT_PLAY_BUFUNIT (4 * 8)
#define ENVY24HT_REC_BUFUNIT  (4 * 2)
#define PCMDIR_PLAY 1
#define PCMDIR_REC  2

typedef struct { volatile uint32_t buffer; } sample32_t;

struct sc_info;
struct sc_chinfo {
	void *buffer;
	struct sc_info *parent;
	int dir;
	unsigned num;
	uint32_t format;
	uint32_t speed;
	uint32_t blk;
	uint8_t *data;
	unsigned int size;
	int unit;
	unsigned int offset;
	void (*emldma)(struct sc_chinfo *);
	int run;
};

struct sc_info {
	sample32_t *pbuf;
	sample32_t *rbuf;
	uint32_t psize, rsize;
};

static uint32_t g_readyptr, g_freeptr, g_ready, g_free;
#define sndbuf_getready(b)    (g_ready)
#define sndbuf_getfree(b)     (g_free)
#define sndbuf_getreadyptr(b) (g_readyptr)
#define sndbuf_getfreeptr(b)  (g_freeptr)

/* ================= VERBATIM FROM envy24ht.c ========================= */
static void
envy24ht_p32sl(struct sc_chinfo *ch)
{
	int length;
	sample32_t *dmabuf;
	uint32_t *data;
	int src, dst, ssize, dsize, slot;
	int i;

	length = sndbuf_getready(ch->buffer) / 8;
	dmabuf = ch->parent->pbuf;
	data = (uint32_t *)ch->data;
	src = sndbuf_getreadyptr(ch->buffer) / 4 % (ch->size / 4);
	src -= src % 2;
	dst = (src / 2 + ch->offset) % (ch->size / 8);
	ssize = ch->size / 4;
	dsize = ch->size / 8;
	slot = ch->num * 2;

	for (i = 0; i < length; i++) {
		dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot].buffer = data[src];
		dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot + 1].buffer = data[src + 1];
		dst++;
		dst %= dsize;
		src += 2;
		src %= ssize;
	}

	return;
}

static void
envy24ht_p16sl(struct sc_chinfo *ch)
{
	int length;
	sample32_t *dmabuf;
	uint16_t *data;
	int src, dst, ssize, dsize, slot;
	int i;

	length = sndbuf_getready(ch->buffer) / 4;
	dmabuf = ch->parent->pbuf;
	data = (uint16_t *)ch->data;
	src = sndbuf_getreadyptr(ch->buffer) / 2 % (ch->size / 2);
	src -= src % 2;
	dst = (src / 2 + ch->offset) % (ch->size / 4);
	ssize = ch->size / 2;
	dsize = ch->size / 4;
	slot = ch->num * 2;

	for (i = 0; i < length; i++) {
		dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot].buffer = (uint32_t)data[src] << 16;
		dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot + 1].buffer = (uint32_t)data[src + 1] << 16;
		dst++;
		dst %= dsize;
		src += 2;
		src %= ssize;
	}

	return;
}

static void
envy24ht_p8u(struct sc_chinfo *ch)
{
	int length;
	sample32_t *dmabuf;
	uint8_t *data;
	int src, dst, ssize, dsize, slot;
	int i;

	length = sndbuf_getready(ch->buffer) / 2;
	dmabuf = ch->parent->pbuf;
	data = (uint8_t *)ch->data;
	src = sndbuf_getreadyptr(ch->buffer) % ch->size;
	src -= src % 2;
	dst = (src / 2 + ch->offset) % (ch->size / 4);
	ssize = ch->size;
	dsize = ch->size / 4;
	slot = ch->num * 2;

	for (i = 0; i < length; i++) {
		dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot].buffer = ((uint32_t)data[src] ^ 0x80) << 24;
		dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot + 1].buffer = ((uint32_t)data[src + 1] ^ 0x80) << 24;
		dst++;
		dst %= dsize;
		src += 2;
		src %= ssize;
	}

	return;
}

static void
envy24ht_r32sl(struct sc_chinfo *ch)
{
	int length;
	sample32_t *dmabuf;
	uint32_t *data;
	int src, dst, ssize, dsize, slot;
	int i;

	length = sndbuf_getfree(ch->buffer) / 8;
	dmabuf = ch->parent->rbuf;
	data = (uint32_t *)ch->data;
	dst = sndbuf_getfreeptr(ch->buffer) / 4 % (ch->size / 4);
	dst -= dst % 2;
	src = (dst / 2 + ch->offset) % (ch->size / 8);
	dsize = ch->size / 4;
	ssize = ch->size / 8;
	slot = ((ch->num - 5 /* ENVY24HT_CHAN_REC_ADC1 */) * 2) % ENVY24HT_REC_CHNUM;

	for (i = 0; i < length; i++) {
		data[dst] = dmabuf[src * ENVY24HT_REC_CHNUM + slot].buffer;
		data[dst + 1] = dmabuf[src * ENVY24HT_REC_CHNUM + slot + 1].buffer;
		dst += 2;
		dst %= dsize;
		src++;
		src %= ssize;
	}

	return;
}

static void
envy24ht_r16sl(struct sc_chinfo *ch)
{
	int length;
	sample32_t *dmabuf;
	uint16_t *data;
	int src, dst, ssize, dsize, slot;
	int i;

	length = sndbuf_getfree(ch->buffer) / 4;
	dmabuf = ch->parent->rbuf;
	data = (uint16_t *)ch->data;
	dst = sndbuf_getfreeptr(ch->buffer) / 2 % (ch->size / 2);
	dst -= dst % 2;
	src = (dst / 2 + ch->offset) % (ch->size / 8);
	dsize = ch->size / 2;
	ssize = ch->size / 8;
	slot = ((ch->num - 5 /* ENVY24HT_CHAN_REC_ADC1 */) * 2) % ENVY24HT_REC_CHNUM;

	for (i = 0; i < length; i++) {
		data[dst] = dmabuf[src * ENVY24HT_REC_CHNUM + slot].buffer;
		data[dst + 1] = dmabuf[src * ENVY24HT_REC_CHNUM + slot + 1].buffer;
		dst += 2;
		dst %= dsize;
		src++;
		src %= ssize;
	}

	return;
}

/* envy24htchan_trigger PCMTRIG_START, second same-direction channel
 * (envy24ht.c:1714-1726, the sc->run[slot] != 1 branch) */
static unsigned int
trigger_offset_2nd_channel(uint32_t hw_ptr /* frames */, uint32_t blk,
                           uint32_t chsize_dwords /* ch->size/4 */, int unit)
{
	return ((hw_ptr / blk + 1) * blk % chsize_dwords) * 4 / unit;
}

/* ================== harness scaffolding ============================ */
static const size_t PBUF_BYTES = ENVY24HT_PLAY_BUFUNIT * ENVY24HT_SAMPLE_NUM;
static const size_t RBUF_BYTES = ENVY24HT_REC_BUFUNIT * ENVY24HT_SAMPLE_NUM;
static const size_t GUARD = 0x40000;

static char *arena_p, *arena_r;
static sample32_t *pbuf, *rbuf;
static sigjmp_buf jb;
static volatile int in_test;

static void segv(int sig) { if (in_test) siglongjmp(jb, 1); else _exit(9); }

static void
setup_soft(void)
{
	/* pbuf and rbuf live in SEPARATE arenas, each with canary margins on
	 * both sides โ€” in the kernel they are separate bus_dmamem_alloc's
	 * (envy24ht.c:2171-2187), so adjacent canaries must not alias. */
	arena_p = malloc(PBUF_BYTES + 2 * GUARD);
	arena_r = malloc(RBUF_BYTES + 2 * GUARD);
	memset(arena_p, 0, PBUF_BYTES + 2 * GUARD);
	memset(arena_r, 0, RBUF_BYTES + 2 * GUARD);
	pbuf = (sample32_t *)(arena_p + GUARD);
	rbuf = (sample32_t *)(arena_r + GUARD);
	/* kernel-heap stand-ins around each buffer */
	for (size_t i = 1; i <= GUARD / 4; i++) {
		pbuf[-(ssize_t)i].buffer = 0xABADBE00u | (i & 0xffu);
		rbuf[-(ssize_t)i].buffer = 0xABADBE00u | (i & 0xffu);
	}
	for (size_t i = 0; i < GUARD / 4; i++) {
		pbuf[PBUF_BYTES / 4 + i].buffer = 0xDEAD0000u + (uint32_t)i;
		rbuf[RBUF_BYTES / 4 + i].buffer = 0xDEADFE00u | (i & 0xffu);
	}
}

static struct sc_info S;

static void
report_rec32(const char *tag, uint32_t *data32, size_t nwords)
{
	size_t before = 0, past = 0;
	uint32_t firstv = 0; size_t firsti = 0; int have = 0;
	for (size_t i = 0; i < nwords; i++) {
		uint32_t v = data32[i];
		if ((v & 0xFF00FF00u) == 0xAB00BE00u ||
		    (v & 0xFF00FF00u) == 0xDE00FE00u) {
			if ((v & 0xFF00FF00u) == 0xAB00BE00u) before++; else past++;
			if (!have) { firstv = v; firsti = i; have = 1; }
		}
	}
	printf("  [%s] user stream received %zu samples from BEFORE rbuf, "
	       "%zu from PAST rbuf end%s", tag, before, past,
	       have ? "\n" : " (none)\n");
	if (have)
		printf("         first leaked: data[%zu] = 0x%08x (kernel-heap marker)\n",
		       firsti, firstv);
}

static void
report_rec16(const char *tag, uint16_t *data16, size_t nwords)
{
	size_t before = 0, past = 0;
	uint16_t firstv = 0; size_t firsti = 0; int have = 0;
	for (size_t i = 0; i < nwords; i++) {
		uint16_t v = data16[i];
		if ((v & 0xFF00u) == 0xBE00u || (v & 0xFF00u) == 0xFE00u) {
			if ((v & 0xFF00u) == 0xBE00u) before++; else past++;
			if (!have) { firstv = v; firsti = i; have = 1; }
		}
	}
	printf("  [%s] user stream received %zu samples from BEFORE rbuf, "
	       "%zu from PAST rbuf end%s", tag, before, past,
	       have ? "\n" : " (none)\n");
	if (have)
		printf("         first leaked: data[%zu] = 0x%04x (low half of "
		       "kernel-heap 0xDEAD/0xABAD marker word)\n", firsti, firstv);
}

int main(int argc, char **argv)
{
	int hard = (argc > 1 && strcmp(argv[1], "hard") == 0);

	printf("DF-3998/DF-3999 FIXED variant (mode=%s)\n",
	       hard ? "hard" : "soft");
	printf("pbuf=%zuB (%zu entries)  rbuf=%zuB (%zu entries)\n",
	       PBUF_BYTES, PBUF_BYTES / 4, RBUF_BYTES, RBUF_BYTES / 4);
	signal(SIGSEGV, segv);

	if (!hard) {
		setup_soft();
		S.pbuf = pbuf; S.rbuf = rbuf;
		S.psize = PBUF_BYTES; S.rsize = RBUF_BYTES;

		/* [A] DF-3999 โ€” first rec channel, hw num 1, slot -8, offset 0 */
		{
			struct sc_chinfo ch;
			static uint32_t data32[8192 + 4];
			memset(&ch, 0, sizeof(ch));
			ch.parent = &S; ch.dir = PCMDIR_REC; ch.num = 1;
			ch.unit = 8; ch.size = 8 * ENVY24HT_SAMPLE_NUM;
			ch.data = (uint8_t *)data32;
			memset(data32, 0, sizeof(data32));
			g_freeptr = 0; g_free = ch.size;
			envy24ht_r32sl(&ch);
			printf("[A] 1st rec ch (num=1, slot=-8, offset=0), r32sl:\n");
			report_rec32("A", data32, 32 /* first 16 frames */);
		}

		/* [B] DF-3999 โ€” 5th rec channel, hw num 10, slot +10, offset 0 */
		{
			struct sc_chinfo ch;
			static uint32_t data32[8192 + 4];
			memset(&ch, 0, sizeof(ch));
			ch.parent = &S; ch.dir = PCMDIR_REC; ch.num = 10;
			ch.unit = 8; ch.size = 8 * ENVY24HT_SAMPLE_NUM;
			ch.data = (uint8_t *)data32;
			memset(data32, 0, sizeof(data32));
			g_freeptr = 0; g_free = ch.size;
			envy24ht_r32sl(&ch);
			printf("[B] 5th rec ch (num=10, slot=+10, offset=0), r32sl:\n");
			report_rec32("B", data32, 8192);
		}

		/* [C] DF-3998 โ€” second rec channel START, S16_LE, worst case:
		 *     user blocksize 16 -> blk = 8 dwords; running 1st rec
		 *     channel at hw ptr 4087 frames (via GETIPTR);
		 *     offset = ((4087/8)+1)*8 % 4096 = 4088.
		 *     dst = freeptr/2 = 8191 -> the first access also writes
		 *     data16[8192], one element past the 8192-entry ch->data
		 *     (kernel M_ENVY24HT kmalloc) - same access-before-wrap
		 *     defect on the data side. */
		{
			struct sc_chinfo ch;
			static uint16_t data16[16384];
			uint32_t blk = 8, off;
			memset(&ch, 0, sizeof(ch));
			ch.parent = &S; ch.dir = PCMDIR_REC; ch.num = 2;
			ch.unit = 4; ch.size = 4 * ENVY24HT_SAMPLE_NUM;
			ch.data = (uint8_t *)data16;
			memset(data16, 0, sizeof(data16));
			off = trigger_offset_2nd_channel(4087, blk,
			                                 ch.size / 4, ch.unit);
			ch.offset = off;
			printf("[C] 2nd rec ch (num=2, slot=-6) START: ptr=4087 "
			       "blk=%u -> offset=%u (ssize=%d)\n",
			       blk, off, ch.size / 8);
			g_freeptr = ch.size - 2; g_free = ch.size;
			envy24ht_r16sl(&ch);
			report_rec16("C", data16, 8192);
			if (data16[8192] != 0)
				printf("         data-side OOB write: data16[8192] = "
				       "0x%04x (one element past ch->data)\n",
				       data16[8192]);
		}

		/* [D] DF-3998 play twin โ€” p16sl with offset (LATENT on this
		 *     driver as registered: needs 2 running play channels).
		 *     user blocksize 16 -> prev=16 -> blk = 16/4*8 = 32 dwords;
		 *     ptr=4063 -> offset = ((4063/32)+1)*32 % 4096 = 4064 */
		{
			struct sc_chinfo ch;
			static uint16_t data16[8192];
			uint32_t blk = 32, off;   /* prev=16 -> 16/4*8 = 32 */
			memset(&ch, 0, sizeof(ch));
			ch.parent = &S; ch.dir = PCMDIR_PLAY; ch.num = 1;
			ch.unit = 4; ch.size = 4 * ENVY24HT_SAMPLE_NUM;
			ch.data = (uint8_t *)data16;
			for (size_t i = 0; i < 8192; i++)
				data16[i] = 0xB9B9;
			off = trigger_offset_2nd_channel(4063, blk,
			                                 ch.size / 4, ch.unit);
			ch.offset = off;
			g_readyptr = ch.size - 4; g_ready = ch.size;
			envy24ht_p16sl(&ch);
			size_t oob = 0, firsti = 0; int have = 0;
			for (size_t i = 0; i < GUARD / 4; i++) {
				if (pbuf[PBUF_BYTES / 4 + i].buffer !=
				    0xDEAD0000u + (uint32_t)i) {
					oob++;
					if (!have) { firsti = i; have = 1; }
				}
			}
			printf("[D] play twin p16sl (LATENT, 2nd play ch): "
			       "offset=%u -> %zu sample32 entries written past "
			       "pbuf end", off, oob);
			if (have)
				printf(", first at +%zu bytes (value 0x%08x)\n",
				       firsti * 4,
				       (unsigned)pbuf[PBUF_BYTES / 4 + firsti].buffer);
			else
				printf(" none\n");
		}
		free(arena_p);
		free(arena_r);
		printf("SOFT-DONE\n");
		return 0;
	}

	/* hard mode: guard page exactly after rbuf */
	{
		size_t pagesz = 4096;
		char *m = mmap(NULL, RBUF_BYTES + pagesz, PROT_READ | PROT_WRITE,
		               MAP_PRIVATE | MAP_ANON, -1, 0);
		if (m == MAP_FAILED) { perror("mmap"); return 1; }
		if (mprotect(m + RBUF_BYTES, pagesz, PROT_NONE) != 0) {
			perror("mprotect"); return 1;
		}
		memset(m, 0, RBUF_BYTES);
		S.rbuf = (sample32_t *)m; S.pbuf = NULL;
		S.psize = PBUF_BYTES; S.rsize = RBUF_BYTES;

		struct sc_chinfo ch;
		static uint16_t data16[8192];
		memset(&ch, 0, sizeof(ch));
		ch.parent = &S; ch.dir = PCMDIR_REC; ch.num = 2;
		ch.unit = 4; ch.size = 4 * ENVY24HT_SAMPLE_NUM;
		ch.data = (uint8_t *)data16;
		ch.offset = trigger_offset_2nd_channel(4087, 8,
		                                       ch.size / 4, ch.unit);
		g_freeptr = ch.size - 2; g_free = ch.size;
		in_test = 1;
		if (sigsetjmp(jb, 1) == 0) {
			envy24ht_r16sl(&ch);
			printf("HARD: no fault (unexpected)\n");
		} else {
			printf("HARD: SIGSEGV at first OOB read past rbuf end "
			       "(offset=%u) - index arithmetic proven\n",
			       ch.offset);
		}
		in_test = 0;
		munmap(m, RBUF_BYTES + pagesz);
	}
	return 0;
}