โฌข DragonFlyBSD Kernel Audit
DF-4121 / vbt_oob_harness.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
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
/*
 * vbt_oob_harness.c โ€” source-faithful harness for three crafted-VBT OOB read
 * findings in sys/dev/drm/i915/intel_bios.c (DragonFlyBSD pass 2):
 *
 *   DF-4121 parse_sdvo_panel_data(): panel_type from BDB_SDVO_LVDS_OPTIONS
 *           (u8, 0..255) used as DTD index with no bounds check
 *           (intel_bios.c:363-381) -> read up to 255*18+18 bytes past the
 *           BDB_SDVO_PANEL_DTDS block data.
 *   DF-4122 parse_edp()/parse_psr()/parse_lfp_backlight()/etc.: find_section()
 *           only guarantees the block *fits inside the BDB*; a block may be
 *           smaller than the struct read from it -> reads past the bdb end
 *           (demonstrated instance: parse_edp edp->color_depth and
 *            &edp->power_seqs[panel_type] at intel_bios.c:569,582-583).
 *   DF-4123 parse_mipi_sequence(): goto_next_sequence() may return exactly
 *           seq_size, then `*(data + index)` reads 1 byte past the
 *           kmemdup'd seq_size heap buffer (intel_bios.c:1134-1158).
 *
 * Kernel-copied functions below are VERBATIM from intel_bios.c /
 * intel_vbt_defs.h / intel_bios.h (DRM_DEBUG* become no-op fprintf; the
 * drm_i915_private container becomes small stand-ins). Attacker model is the
 * pass-1 crafted-VBT model: VBT bytes come from platform firmware (opregion
 * RVDA/mailbox-4, PCI ROM, i915_vbt_firmware blob) and are validated ONLY by
 * intel_bios_is_valid_vbt() โ€” which bounds bdb_offset+bdb_size to the buffer,
 * and nothing more.
 *
 * Detection: buffers are placed in private mmap'd memory ending exactly at a
 * PROT_NONE guard page. Any read past the buffer end raises SIGSEGV; the
 * handler prints "OOB read confirmed" and exits 77. In the kernel the same
 * primitive is an OOB read of the kmemdup'd firmware-VBT heap buffer or of
 * the opregion/PCI-ROM mapping.
 *
 * Usage:  ./vbt_harness {sdvo|edp|mipi} [-p]     (-p = proposed fix applied)
 * Exit:   77 = OOB read confirmed (vulnerable path taken)
 *          0 = completed with no OOB (patched path / block rejected)
 *         70 = usage / internal error
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <unistd.h>
#include <signal.h>

typedef uint8_t  u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;

/* ---------------- VERBATIM structs (intel_vbt_defs.h / intel_bios.h) ----- */

struct vbt_header {           /* intel_vbt_defs.h:53 */
	u8 signature[20];
	u16 version;
	u16 header_size;
	u16 vbt_size;
	u8 vbt_checksum;
	u8 reserved0;
	u32 bdb_offset;
	u32 aim_offset[4];
} __packed;

struct bdb_header {           /* intel_vbt_defs.h:71 */
	u8 signature[16];
	u16 version;
	u16 header_size;
	u16 bdb_size;
} __packed;

struct lvds_dvo_timing {      /* intel_vbt_defs.h:523 */
	u16 clock;
	u8 hactive_lo;
	u8 hblank_lo;
	u8 hblank_hi:4;
	u8 hactive_hi:4;
	u8 vactive_lo;
	u8 vblank_lo;
	u8 vblank_hi:4;
	u8 vactive_hi:4;
	u8 hsync_off_lo;
	u8 hsync_pulse_width_lo;
	u8 vsync_pulse_width_lo:4;
	u8 vsync_off_lo:4;
	u8 vsync_pulse_width_hi:2;
	u8 vsync_off_hi:2;
	u8 hsync_pulse_width_hi:2;
	u8 hsync_off_hi:2;
	u8 himage_lo;
	u8 vimage_lo;
	u8 vimage_hi:4;
	u8 himage_hi:4;
	u8 h_border;
	u8 v_border;
	u8 rsvd1:3;
	u8 digital:2;
	u8 vsync_positive:1;
	u8 hsync_positive:1;
	u8 non_interlaced:1;
} __packed;

struct bdb_sdvo_lvds_options { /* intel_vbt_defs.h:626 */
	u8 panel_backlight;
	u8 h40_set_panel_type;
	u8 panel_type;
	u8 ssc_clk_freq;
	u16 als_low_trip;
	u16 als_high_trip;
	u8 sclalarcoeff_tab_row_num;
	u8 sclalarcoeff_tab_row_size;
	u8 coefficient[8];
	u8 panel_misc_bits_1;
	u8 panel_misc_bits_2;
	u8 panel_misc_bits_3;
	u8 panel_misc_bits_4;
} __packed;

struct edp_power_seq {        /* intel_bios.h:41 */
	u16 t1_t3;
	u16 t8;
	u16 t9;
	u16 t10;
	u16 t11_t12;
} __packed;

struct edp_fast_link_params { /* intel_vbt_defs.h:720 */
	u8 rate:4;
	u8 lanes:4;
	u8 preemphasis:4;
	u8 vswing:4;
} __packed;

struct bdb_edp {              /* intel_vbt_defs.h:737 */
	struct edp_power_seq power_seqs[16];
	u32 color_depth;
	struct edp_fast_link_params fast_link_params[16];
	u32 sdrrs_msa_timing_delay;
	u16 edp_s3d_feature;					/* 162 */
	u16 edp_t3_optimization;				/* 165 */
	u64 edp_vswing_preemph;					/* 173 */
	u16 fast_link_training;					/* 182 */
	u16 dpcd_600h_write_required;				/* 185 */
} __packed;

struct psr_table {            /* intel_vbt_defs.h:754 */
	u8 full_link:1;
	u8 require_aux_to_wakeup:1;
	u8 feature_bits_rsvd:6;
	u8 idle_frames:4;
	u8 lines_to_wait:3;
	u8 wait_times_rsvd:1;
	u16 tp1_wakeup_time;
	u16 tp2_tp3_wakeup_time;
} __packed;

struct bdb_psr {              /* intel_vbt_defs.h:770 */
	struct psr_table psr_table[16];
} __packed;

struct bdb_mipi_sequence {    /* intel_vbt_defs.h:910 */
	u8 version;
	u8 data[0];
} __packed;

#define BDB_SDVO_LVDS_OPTIONS	22
#define BDB_SDVO_PANEL_DTDS	23
#define BDB_EDP			27
#define BDB_MIPI_SEQUENCE	53

#define MIPI_SEQ_END		0
#define MIPI_SEQ_DEASSERT_RESET	1
#define MIPI_SEQ_MAX		13

#define MIPI_SEQ_ELEM_END	0
#define MIPI_SEQ_ELEM_SEND_PKT	1
#define MIPI_SEQ_ELEM_DELAY	2
#define MIPI_SEQ_ELEM_GPIO	3
#define MIPI_SEQ_ELEM_I2C	4

/* ---------------- guard-page "allocator" --------------------------------- */

static const unsigned char *watch_base = NULL;

static unsigned char *guard_alloc(size_t payload_size)
{
	size_t pg = (size_t)sysconf(_SC_PAGESIZE);
	size_t total = ((payload_size + pg - 1) / pg) * pg;
	unsigned char *m = mmap(NULL, total + pg, PROT_READ | PROT_WRITE,
				MAP_ANON | MAP_PRIVATE, -1, 0);
	if (m == MAP_FAILED) { perror("mmap"); exit(70); }
	if (mprotect(m + total, pg, PROT_NONE)) { perror("mprotect"); exit(70); }
	return m + total - payload_size;   /* payload ends at the guard page */
}

static int patched = 0;

static void oob_handler(int sig, siginfo_t *si, void *uc)
{
	(void)uc; (void)sig;
	printf("OOB read confirmed: SIGSEGV touching %p (offset %+ld from watched buffer base %p)\n",
	       si->si_addr,
	       watch_base ? (long)((const unsigned char *)si->si_addr - watch_base) : -1L,
	       watch_base);
	printf("RESULT: VULNERABLE โ€” out-of-bounds read taken by verbatim kernel parse code (exit 77)\n");
	_exit(77);
}

/* ---------------- VERBATIM kernel code (intel_bios.c) -------------------- */

/* intel_bios.c:62-69 */
static u32 _get_blocksize(const u8 *block_base)
{
	/* The MIPI Sequence Block v3+ has a separate size field. */
	if (*block_base == BDB_MIPI_SEQUENCE && *(block_base + 3) >= 3)
		return *((const u32 *)(block_base + 4));
	else
		return *((const u16 *)(block_base + 1));
}

/* intel_bios.c:72-75 */
static u32 get_blocksize(const void *block_data)
{
	return _get_blocksize(block_data - 3);
}

/* intel_bios.c:77-106 */
static const void *
find_section(const void *_bdb, int section_id)
{
	const struct bdb_header *bdb = _bdb;
	const u8 *base = _bdb;
	int index = 0;
	u32 total, current_size;
	u8 current_id;

	/* skip to first section */
	index += bdb->header_size;
	total = bdb->bdb_size;

	/* walk the sections looking for section_id */
	while (index + 3 < total) {
		current_id = *(base + index);
		current_size = _get_blocksize(base + index);
		index += 3;

		if (index + current_size > total)
			return NULL;

		if (current_id == section_id)
			return base + index;

		index += current_size;
	}

	return NULL;
}

/* intel_bios.c:1649-1682 (DRM_DEBUG_DRIVER -> fprintf) */
#define range_overflows(start, size, max) ({ \
	typeof(start) start__ = (start); \
	typeof(size) size__ = (size); \
	typeof(max) max__ = (max); \
	(void)(&start__ == &size__); \
	(void)(&start__ == &max__); \
	start__ >= max__ || size__ > max__ - start__; \
})
#define range_overflows_t(type, start, size, max) \
	(range_overflows(start, size, max))

static int intel_bios_is_valid_vbt(const void *buf, size_t size)
{
	const struct vbt_header *vbt = buf;
	const struct bdb_header *bdb;

	if (!vbt)
		return 0;

	if (sizeof(struct vbt_header) > size) {
		fprintf(stderr, "VBT header incomplete\n");
		return 0;
	}

	if (memcmp(vbt->signature, "$VBT", 4)) {
		fprintf(stderr, "VBT invalid signature\n");
		return 0;
	}

	if (range_overflows_t(size_t,
			      vbt->bdb_offset,
			      sizeof(struct bdb_header),
			      size)) {
		fprintf(stderr, "BDB header incomplete\n");
		return 0;
	}

	bdb = (const void *)((const u8 *)vbt + vbt->bdb_offset);
	if (range_overflows_t(size_t, vbt->bdb_offset, bdb->bdb_size, size)) {
		fprintf(stderr, "BDB incomplete\n");
		return 0;
	}

	return 1;
}

/* ---------------- crafted-VBT construction ------------------------------- */

static unsigned char blob[16384];   /* scratch, then copied to guard region */

static size_t emit_block(unsigned char *b, size_t off, u8 id,
			 const void *data, u16 datasz)
{
	b[off++] = id;
	b[off++] = (u8)(datasz & 0xff);
	b[off++] = (u8)(datasz >> 8);
	if (datasz)
		memcpy(b + off, data, datasz);
	return off + datasz;
}

/* Assemble VBT: header + bdb_header + blocks. Returns blob length; caller
 * copies it so the LAST byte lands immediately before the guard page. */
static size_t build_vbt(const u8 *blocks, size_t blocks_len, u16 bdb_size_override)
{
	struct vbt_header *vh = (struct vbt_header *)blob;
	struct bdb_header *bh;
	size_t bdb_off = sizeof(struct vbt_header);
	size_t bdb_len = sizeof(struct bdb_header) + blocks_len;

	memset(blob, 0, sizeof(blob));
	memcpy(vh->signature, "$VBT", 4);
	vh->version = 208;
	vh->header_size = sizeof(struct vbt_header);
	vh->vbt_size = bdb_off + bdb_len;
	vh->bdb_offset = bdb_off;

	bh = (struct bdb_header *)(blob + bdb_off);
	memcpy(bh->signature, "$BVBT", 5);
	bh->version = 208;
	bh->header_size = sizeof(struct bdb_header);
	bh->bdb_size = bdb_size_override ? bdb_size_override : (u16)bdb_len;

	memcpy(blob + bdb_off + sizeof(struct bdb_header), blocks, blocks_len);
	return bdb_off + bdb_len;
}

/* ---------------- DF-4121: parse_sdvo_panel_data (intel_bios.c:349-387) -- */

static int case_sdvo(void)
{
	u8 opts[3] = { 0x00, 0x00, 0xFF };   /* panel_type = 255 (u8, unchecked) */
	u8 dtds[sizeof(struct lvds_dvo_timing)];
	unsigned char blocks[64];
	size_t blen = 0, vlen;
	unsigned char *vbt;
	const struct bdb_header *bdb;
	const struct lvds_dvo_timing *dvo_timing;
	const struct bdb_sdvo_lvds_options *sdvo_lvds_options;
	int index = -1;   /* i915_modparams.vbt_sdvo_type default (i915_params.c:41) */

	memset(dtds, 0x41, sizeof(dtds));   /* one plausible DTD */

	blen = emit_block(blocks, blen, BDB_SDVO_LVDS_OPTIONS, opts, sizeof(opts));
	blen = emit_block(blocks, blen, BDB_SDVO_PANEL_DTDS, dtds, sizeof(dtds));

	vlen = build_vbt(blocks, blen, 0);
	vbt = guard_alloc(vlen);
	memcpy(vbt, blob, vlen);
	watch_base = vbt;

	if (!intel_bios_is_valid_vbt(vbt, vlen)) {
		fprintf(stderr, "harness bug: VBT rejected as invalid\n");
		return 70;
	}
	printf("[DF-4121] crafted VBT accepted by intel_bios_is_valid_vbt (%zu bytes)\n", vlen);
	printf("[DF-4121] BDB_SDVO_PANEL_DTDS is the last block, data size = %zu bytes (1 DTD)\n",
	       sizeof(dtds));

	/* --- verbatim logic of parse_sdvo_panel_data (intel_bios.c:363-381) --- */
	if (index == -1) {
		sdvo_lvds_options = find_section(vbt + ((struct vbt_header *)vbt)->bdb_offset,
						 BDB_SDVO_LVDS_OPTIONS);
		if (!sdvo_lvds_options)
			return 0;

		index = sdvo_lvds_options->panel_type;    /* intel_bios.c:370 */
	}
	printf("[DF-4121] panel_type from BDB_SDVO_LVDS_OPTIONS = %d (no bounds check in this tree)\n",
	       index);
	if (patched && index > 0xf) {                  /* proposed fix */
		printf("[DF-4121] PATCHED: rejected panel_type %d > 0xf โ€” no DTD read\n", index);
		return 0;
	}

	dvo_timing = find_section(vbt + ((struct vbt_header *)vbt)->bdb_offset,
				  BDB_SDVO_PANEL_DTDS);
	if (!dvo_timing)
		return 0;

	printf("[DF-4121] reading struct lvds_dvo_timing (%zu bytes) at dvo_timing + %d*%zu = +%d bytes past block data start\n",
	       sizeof(struct lvds_dvo_timing), index, sizeof(struct lvds_dvo_timing),
	       (int)(index * sizeof(struct lvds_dvo_timing)));

	/* intel_bios.c:381: fill_detail_timing_data(mode, dvo_timing + index);
	 * first field read is dvo_timing->hactive_hi at offset 3 of the entry */
	{
		const volatile struct lvds_dvo_timing *t =
			dvo_timing + index;
		u8 first = t->hactive_hi;       /* the OOB read */
		printf("[DF-4121] read value %#x (this line is unreachable if guard page hit first)\n",
		       first);
	}
	(void)bdb;
	printf("RESULT: NOT reproduced (no OOB) โ€” unexpected for unpatched build\n");
	return 1;
}

/* ---------------- DF-4122: parse_edp (intel_bios.c:557-666) --------------- */

static int case_edp(void)
{
	unsigned char blocks[16];
	size_t blen = 0, vlen;
	unsigned char *vbt;
	const struct bdb_header *bdb;
	const struct bdb_edp *edp;
	const struct edp_power_seq *edp_pps;
	int panel_type = 0;   /* vbt.panel_type default when BDB_LVDS_OPTIONS absent */

	/* BDB_EDP as the LAST block with declared size 1 (find_section accepts
	 * any size that fits in the bdb; nothing checks sizeof(struct bdb_edp)) */
	u8 one = 0x5a;
	blen = emit_block(blocks, blen, BDB_EDP, &one, 1);

	vlen = build_vbt(blocks, blen, 0);
	vbt = guard_alloc(vlen);
	memcpy(vbt, blob, vlen);
	watch_base = vbt;

	if (!intel_bios_is_valid_vbt(vbt, vlen)) {
		fprintf(stderr, "harness bug: VBT rejected as invalid\n");
		return 70;
	}
	printf("[DF-4122] crafted VBT accepted (%zu bytes); BDB_EDP last block declared size = 1 byte\n", vlen);

	bdb = (const struct bdb_header *)(vbt + ((struct vbt_header *)vbt)->bdb_offset);
	edp = find_section(bdb, BDB_EDP);
	if (!edp) { printf("no BDB_EDP\n"); return 1; }

	printf("[DF-4122] sizeof(struct bdb_edp) = %zu; parse_edp reads edp->color_depth at +%zu and power_seqs[%d] at +%zu with no block-size floor\n",
	       sizeof(struct bdb_edp), (size_t)(16 * sizeof(struct edp_power_seq)),
	       panel_type, (size_t)(panel_type * sizeof(struct edp_power_seq)));

	if (patched && get_blocksize(edp) < sizeof(struct edp_power_seq) * 16 + 4) {
		printf("[DF-4122] PATCHED: BDB_EDP block size %u < %zu โ€” rejected\n",
		       get_blocksize(edp), sizeof(struct edp_power_seq) * 16 + 4);
		return 0;
	}

	/* --- verbatim reads of parse_edp (intel_bios.c:569, 582-585) --- */
	printf("[DF-4122] edp->color_depth = %#x\n",
	       (edp->color_depth >> (panel_type * 2)) & 3);      /* :569 */

	edp_pps = &edp->power_seqs[panel_type];                  /* :582 */
	printf("[DF-4122] power_seqs[0].t1_t3 = %u\n", edp_pps->t1_t3);

	printf("RESULT: NOT reproduced (no OOB) โ€” unexpected for unpatched build\n");
	return 1;
}

/* ---------------- DF-4123: parse_mipi_sequence (intel_bios.c:1095-1172) -- */

/* VERBATIM intel_bios.c:908-944 */
static int goto_next_sequence(const u8 *data, int index, int total)
{
	u16 len;

	/* Skip Sequence Byte. */
	for (index = index + 1; index < total; index += len) {
		u8 operation_byte = *(data + index);
		index++;

		switch (operation_byte) {
		case MIPI_SEQ_ELEM_END:
			return index;
		case MIPI_SEQ_ELEM_SEND_PKT:
			if (index + 4 > total)
				return 0;
			len = *((const u16 *)(data + index + 2)) + 4;
			break;
		case MIPI_SEQ_ELEM_DELAY:
			len = 4;
			break;
		case MIPI_SEQ_ELEM_GPIO:
			len = 2;
			break;
		case MIPI_SEQ_ELEM_I2C:
			if (index + 7 > total)
				return 0;
			len = *(data + index + 6) + 7;
			break;
		default:
			fprintf(stderr, "Unknown operation byte\n");
			return 0;
		}
	}

	return 0;
}

/* VERBATIM intel_bios.c:860-906 */
static const u8 *
find_panel_sequence_block(const struct bdb_mipi_sequence *sequence,
			  u16 panel_id, u32 *seq_size)
{
	u32 total = get_blocksize(sequence);
	const u8 *data = &sequence->data[0];
	u8 current_id;
	u32 current_size;
	int header_size = sequence->version >= 3 ? 5 : 3;
	int index = 0;
	int i;

	if (sequence->version >= 3)
		data += 4;

	for (i = 0; i < 6 /* MAX_MIPI_CONFIGURATIONS */ && index < total; i++) {
		if (index + header_size > total) {
			fprintf(stderr, "Invalid sequence block (header)\n");
			return NULL;
		}

		current_id = *(data + index);
		if (sequence->version >= 3)
			current_size = *((const u32 *)(data + index + 1));
		else
			current_size = *((const u16 *)(data + index + 1));

		index += header_size;

		if (index + current_size > total) {
			fprintf(stderr, "Invalid sequence block\n");
			return NULL;
		}

		if (current_id == panel_id) {
			*seq_size = current_size;
			return data + index;
		}

		index += current_size;
	}

	fprintf(stderr, "Sequence block detected but no valid configuration\n");
	return NULL;
}

static int case_mipi(void)
{
	/* BDB_MIPI_SEQUENCE block data:
	 *   version = 1
	 *   entry: [panel_id=0][size=5][payload = 01 03 aa aa 00]
	 * payload: seq_id=MIPI_SEQ_DEASSERT_RESET(1), GPIO element (op=3, 2
	 * operand bytes), then MIPI_SEQ_ELEM_END as the FINAL byte -> the
	 * verbatim goto_next_sequence() returns exactly seq_size.
	 */
	static const u8 blk[] = {
		0x01,                       /* version 1 */
		0x00, 0x05, 0x00,           /* entry header: id 0, u16 size 5 */
		0x01, 0x03, 0xaa, 0xaa, 0x00, /* payload (5 bytes) */
	};
	unsigned char blocks[32];
	size_t blen = 0, vlen;
	unsigned char *vbt, *data;
	const struct bdb_header *bdb;
	const struct bdb_mipi_sequence *sequence;
	const u8 *seq_data;
	u32 seq_size = 0;
	int index = 0;
	u8 seq_id;
	const u8 *dsi_sequence[MIPI_SEQ_MAX];

	blen = emit_block(blocks, blen, BDB_MIPI_SEQUENCE, blk, sizeof(blk));
	vlen = build_vbt(blocks, blen, 0);
	vbt = guard_alloc(vlen);
	memcpy(vbt, blob, vlen);

	if (!intel_bios_is_valid_vbt(vbt, vlen)) {
		fprintf(stderr, "harness bug: VBT rejected\n");
		return 70;
	}
	bdb = (const struct bdb_header *)(vbt + ((struct vbt_header *)vbt)->bdb_offset);
	sequence = find_section(bdb, BDB_MIPI_SEQUENCE);
	if (!sequence || sequence->version >= 4) { printf("no seq\n"); return 1; }

	seq_data = find_panel_sequence_block(sequence, 0 /* panel_type */, &seq_size);
	if (!seq_data) { printf("no panel seq\n"); return 1; }
	printf("[DF-4123] panel sequence blob seq_size = %u\n", seq_size);

	/* kernel: data = kmemdup(seq_data, seq_size) โ€” exact-size allocation */
	data = guard_alloc(seq_size);
	memcpy(data, seq_data, seq_size);
	watch_base = data;
	printf("[DF-4123] kmemdup'd %u-byte heap buffer placed flush against guard page\n",
	       seq_size);

	/* --- verbatim parse loop of parse_mipi_sequence (intel_bios.c:1134-1158) --- */
	for (;;) {
		seq_id = *(data + index);                       /* :1135 */
		if (seq_id == MIPI_SEQ_END)
			break;
		if (seq_id >= MIPI_SEQ_MAX) {
			printf("[DF-4123] unknown sequence %u\n", seq_id);
			goto err;
		}
		dsi_sequence[seq_id] = data + index;

		index = goto_next_sequence(data, index, seq_size); /* :1153 */
		if (!index) {
			printf("[DF-4123] invalid sequence %u\n", seq_id);
			goto err;
		}
		if (patched && index >= (int)seq_size) {   /* proposed fix */
			printf("[DF-4123] PATCHED: loop stopped at index %d == seq_size before reading data[%d]\n",
			       index, index);
			return 0;
		}
	}
	printf("RESULT: loop ended normally (no OOB) โ€” unexpected for unpatched build\n");
	return 1;
err:
	printf("RESULT: err path (no OOB)\n");
	return 1;
}

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

int main(int argc, char **argv)
{
	struct sigaction sa;
	int rc;

	if (argc < 2) {
		fprintf(stderr, "usage: %s {sdvo|edp|mipi} [-p]\n", argv[0]);
		return 70;
	}
	if (argc > 2 && !strcmp(argv[2], "-p"))
		patched = 1;

	memset(&sa, 0, sizeof(sa));
	sa.sa_sigaction = oob_handler;
	sa.sa_flags = SA_SIGINFO;
	if (sigaction(SIGSEGV, &sa, NULL) || sigaction(SIGBUS, &sa, NULL)) {
		perror("sigaction"); return 70;
	}
	setvbuf(stdout, NULL, _IONBF, 0);   /* survive _exit() in the handler */

	printf("== i915 VBT parse harness (verbatim code from sys/dev/drm/i915/intel_bios.c) ==\n");
	printf("mode=%s patched=%d\n\n", argv[1], patched);

	if (!strcmp(argv[1], "sdvo"))    rc = case_sdvo();
	else if (!strcmp(argv[1], "edp")) rc = case_edp();
	else if (!strcmp(argv[1], "mipi")) rc = case_mipi();
	else { fprintf(stderr, "unknown mode\n"); return 70; }

	if (rc == 0)
		printf("RESULT: OK โ€” no out-of-bounds read on this path\n");
	return rc == 0 ? 0 : rc;
}