Repository navigation
Expand file tree
/
Copy pathcoreboot_20260909.html
More file actions
1089 lines (1026 loc) · 455 KB
/
Copy pathcoreboot_20260909.html
File metadata and controls
1089 lines (1026 loc) · 455 KB
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
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="color-scheme" content="dark">
<title>Code Audit — Interactive</title>
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{color-scheme:dark}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#0f172a;color:#e5e7eb;font-size:14px;line-height:1.5}
.app{max-width:1100px;margin:0 auto;padding:1.5rem 1rem}
.top{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem;margin-bottom:1rem;flex-wrap:wrap}
h1{font-size:1.5rem;font-weight:700;color:#f1f5f9}
.meta{font-size:.8rem;color:#6b7280;margin-top:.2rem}
.top-right{display:flex;align-items:center;gap:.75rem;flex-wrap:wrap}
.project-info{margin-bottom:1rem;padding:.75rem .9rem;background:#1e293b;border:1px solid #334155;border-radius:8px}
.project-info .pi-name{font-size:.95rem;font-weight:700;color:#f1f5f9}
.project-info .pi-desc{font-size:.82rem;color:#d1d5db;margin-top:.25rem}
.project-info .pi-row{display:flex;flex-wrap:wrap;gap:.4rem .9rem;margin-top:.45rem;font-size:.78rem;color:#9ca3af}
.project-info .pi-row .pi-k{color:#6b7280}
.project-info a{color:#60a5fa;text-decoration:none}
.project-info a:hover{text-decoration:underline}
.project-info code{font-family:"JetBrains Mono","Fira Code",Consolas,monospace;font-size:.74rem;color:#93c5fd}
.pi-repo-btn{display:inline-flex;align-items:center;gap:.3rem;padding:.18rem .5rem;background:#0f172a;border:1px solid #3b82f6;border-radius:6px;color:#60a5fa !important;font-weight:600}
.pi-repo-btn:hover{background:#1d283a;text-decoration:none !important}
#search{background:#1e293b;border:1px solid #334155;color:#e5e7eb;padding:.4rem .75rem;border-radius:6px;font-size:.875rem;width:220px;outline:none}
#search:focus{border-color:#3b82f6}
.group-label{font-size:.8rem;color:#9ca3af;display:flex;align-items:center;gap:.4rem}
select{background:#1e293b;border:1px solid #334155;color:#e5e7eb;padding:.35rem .6rem;border-radius:6px;font-size:.8rem;outline:none;cursor:pointer}
select:focus{border-color:#3b82f6}
.filterbar{display:flex;flex-direction:column;gap:.4rem;margin-bottom:.6rem;padding:.6rem .75rem;background:#1e293b;border:1px solid #334155;border-radius:8px}
.filter-row{display:flex;flex-wrap:wrap;gap:.3rem;align-items:center}
.filter-lbl{font-size:.65rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:#4b5563;white-space:nowrap;margin-right:.25rem}
.filter-toggle{font-size:.65rem;font-weight:600;padding:.18rem .5rem;border-radius:4px;border:1px solid #334155;background:#0f172a;color:#9ca3af;cursor:pointer;margin-left:auto;white-space:nowrap;letter-spacing:.04em}
.filter-toggle:hover{background:#1e293b;color:#e5e7eb;border-color:#4b5563}
.badge{font-size:.68rem;font-weight:700;padding:.18rem .5rem;border-radius:4px;border:1px solid transparent;cursor:pointer;letter-spacing:.03em;transition:opacity .12s,filter .12s;user-select:none}
.badge.off{opacity:.28;filter:grayscale(.5)}
.sev-critical{background:#450a0a;color:#fca5a5;border-color:#991b1b}
.sev-high {background:#431407;color:#fdba74;border-color:#9a3412}
.sev-medium {background:#422006;color:#fcd34d;border-color:#854d0e}
.sev-low {background:#0c1a3a;color:#93c5fd;border-color:#1d4ed8}
.sev-negligible{background:#1e293b;color:#6b7280;border-color:#334155}
.type-badge{background:#0c1e33;color:#7dd3fc;border-color:#0369a1}
.diff-very-easy{background:#052e16;color:#86efac;border-color:#16a34a}
.diff-easy{background:#064e3b;color:#6ee7b7;border-color:#059669}
.diff-medium{background:#1e293b;color:#9ca3af;border-color:#4b5563}
.diff-hard{background:#2e1065;color:#c4b5fd;border-color:#7c3aed}
.st-open{background:#1e293b;color:#4b5563;border-color:#334155}
.st-fixed{background:#052e16;color:#4ade80;border-color:#16a34a}
.st-wont{background:#431407;color:#fdba74;border-color:#9a3412}
.st-fp{background:#2e1065;color:#c4b5fd;border-color:#7c3aed}
#progress-wrap{display:flex;align-items:center;gap:.75rem;margin-bottom:.6rem;flex-wrap:wrap}
#progress-bar{flex:1;min-width:120px;height:7px;background:#1e293b;border-radius:4px;overflow:hidden;border:1px solid #334155}
#progress-fill{height:100%;background:#22c55e;border-radius:4px;transition:width .35s ease;width:0}
#progress-text{font-size:.75rem;color:#6b7280;white-space:nowrap}
.action-btn{font-size:.72rem;padding:.25rem .6rem;border-radius:5px;border:1px solid #334155;background:#1e293b;color:#9ca3af;cursor:pointer;white-space:nowrap}
.action-btn:hover{background:#334155;color:#e5e7eb}
.action-btn.active{background:#1d4ed8;color:#fff;border-color:#2563eb}
.action-btn.active:hover{background:#2563eb}
.search-wrap{display:flex;align-items:center;gap:.35rem}
#hidden-bar{margin-bottom:.6rem}
.hidden-inner{display:flex;flex-wrap:wrap;gap:.35rem;align-items:center}
.hidden-lbl{font-size:.72rem;color:#4b5563}
.show-btn{font-size:.7rem;padding:.15rem .45rem;border-radius:4px;background:#1e293b;color:#9ca3af;border:1px solid #334155;cursor:pointer}
.show-btn:hover{background:#334155;color:#e5e7eb}
.show-all-btn{font-size:.7rem;padding:.15rem .45rem;border-radius:4px;background:#2563eb;color:#fff;border:none;cursor:pointer}
.show-all-btn:hover{background:#1d4ed8}
.group-section{margin-bottom:1.5rem}
.group-header{display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem;padding-bottom:.35rem;border-bottom:1px solid #334155}
.group-title{font-size:.95rem;font-weight:700;color:#d1d5db}
.group-title.all-done{color:#4ade80}
.group-count{font-size:.72rem;color:#4b5563;background:#1e293b;padding:.1rem .4rem;border-radius:10px;white-space:nowrap}
.icon-btn{background:transparent;border:none;color:#334155;cursor:pointer;font-size:.85rem;padding:.05rem .2rem;line-height:1}
.icon-btn:hover{color:#6b7280}
.hide-btn{margin-left:auto;font-size:.68rem;padding:.12rem .45rem;border-radius:4px;background:transparent;color:#334155;border:1px solid #1e293b;cursor:pointer}
.hide-btn:hover{background:#1e293b;color:#6b7280}
.card{background:#1e293b;border:1px solid #334155;border-radius:8px;margin-bottom:.6rem;overflow:hidden;transition:opacity .15s}
.card.resolved{opacity:.55;border-left:3px solid #16a34a}
.card.resolved:hover{opacity:.85}
.card-header{display:flex;align-items:center;gap:.5rem;padding:.45rem .85rem;background:#162032;border-bottom:1px solid #334155;flex-wrap:wrap;cursor:pointer;user-select:none}
.card-header:hover{background:#1a2840}
.card-arrow{font-size:.65rem;color:#334155;flex-shrink:0}
.card-id{font-size:.75rem;font-weight:700;color:#6b7280;font-family:monospace;letter-spacing:.04em}
.card-loc{font-size:.72rem;color:#6b7280;font-family:"JetBrains Mono","Fira Code",Consolas,monospace;letter-spacing:.02em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
.status-btn{margin-left:auto;font-size:.68rem;font-weight:600;padding:.15rem .5rem;border-radius:4px;border:1px solid transparent;cursor:pointer;white-space:nowrap;transition:filter .1s}
.status-btn:hover{filter:brightness(1.25)}
.copy-btn{font-size:.68rem;font-weight:600;padding:.15rem .5rem;border-radius:4px;border:1px solid #334155;background:#0f172a;color:#9ca3af;cursor:pointer;white-space:nowrap;transition:background .12s,color .12s}
.copy-btn:hover{background:#334155;color:#e5e7eb}
.card-body{padding:.85rem .9rem}
.field-lbl{font-size:.85rem;font-weight:700;text-transform:uppercase;letter-spacing:.1em;color:#7dd3fc;margin:1rem 0 .35rem;padding-bottom:.18rem;border-bottom:1px solid #1e293b}
.field-lbl:first-child{margin-top:0}
.desc,.fix-txt{font-size:.875rem;color:#d1d5db;line-height:1.55}
.desc p,.fix-txt p{margin:.4rem 0}
.desc p:first-child,.fix-txt p:first-child{margin-top:0}
.desc p:last-child,.fix-txt p:last-child{margin-bottom:0}
.loc-block{margin:.3rem 0 .6rem}
.loc-ref{font-size:.8rem;color:#38bdf8;font-family:"JetBrains Mono","Fira Code",Consolas,monospace;font-weight:600;margin-bottom:.22rem}
a.loc-link{color:inherit;text-decoration:none}
a.loc-link:hover{text-decoration:underline}
.loc-lines{color:#4b5563;font-weight:400}
pre{background:#0f172a;color:#cdd6f4;border-radius:5px;padding:.6rem .8rem;overflow-x:auto;white-space:pre;font-size:.8rem;line-height:1.55;font-family:"JetBrains Mono","Fira Code",Consolas,monospace;border:1px solid #1e293b;max-height:320px}
.desc pre.md-code,.fix-txt pre.md-code{margin:.5rem 0;font-size:.82rem;color:#e5e7eb;background:#0b1224;border:1px solid #1e293b;max-height:none}
code.md-inline{font-family:"JetBrains Mono","Fira Code",Consolas,monospace;font-size:.86em;background:#0b1224;color:#fca5a5;border:1px solid #1e293b;border-radius:3px;padding:.04rem .32rem;white-space:pre-wrap;word-break:break-word}
.not-found{font-size:.8rem;color:#334155;font-style:italic}
mark{background:#854d0e;color:#fef9c3;border-radius:2px;padding:0 .1rem}
.no-results{text-align:center;color:#334155;padding:3rem 1rem;font-size:.9rem}
</style>
</head>
<body>
<script type="application/json" id="D">[
{"id":"PANIC_1","type":"panic","type_label":"Panic","severity":"medium","difficulty":"easy","description":"In `pirq_route_irqs()` the device pointer returned by `pcidev_path_on_bus(bus, devfn)` is passed straight into `pci_assign_irqs()` without a NULL check. `pcidev_path_on_bus()` returns `NULL` when the bus/devfn taken from the (board-supplied, static) PIRQ routing table does not correspond to an enabled device in the device tree, and `pci_assign_irqs()` immediately dereferences it via `dev->path.pci.devfn >> 3`, causing a NULL-pointer dereference during table generation. A PIRQ table that lists an optional/disabled device is enough to crash the boot.","fix":"Guard the call, e.g.:\n```c\nstruct device *dev = pcidev_path_on_bus(bus, devfn);\nif (dev)\n\tpci_assign_irqs(dev, irq_slot);\n```\ninstead of `pci_assign_irqs(pcidev_path_on_bus(bus, devfn), irq_slot);`","locations":[{"ref":"src/arch/x86/pirq_routing.c:160-161","code":" 160 | \t\t/* Bus, device, slots IRQs for {A,B,C,D}. */\n 161 | \t\tpci_assign_irqs(pcidev_path_on_bus(bus, devfn), irq_slot);","lines":2,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/arch/x86/pirq_routing.c#L160-L161"}]},
{"id":"PANIC_2","type":"panic","type_label":"Panic","severity":"medium","difficulty":"easy","description":"In `read_reg_prop()` the region count is computed as `prop->size / (4 * addr_cells + 4 * size_cells)` at the top of the function, but the sanity check on `addr_cells`/`size_cells` is only performed afterwards. If a (malformed or malicious) device tree yields `addr_cells == 0` and `size_cells == 0`, the divisor is `0` and this is an immediate division-by-zero crash. `addr_cells`/`size_cells` can be `0` when a node carries `#address-cells = <0>` / `#size-cells = <0>`, and the values flow unvalidated from `be32dec()` of the FDT into this function via `fdt_read_reg_prop()` and `dt_read_reg_prop()`. The `addr_cells > 2 || size_cells > 2` guard executes too late to prevent the divide, and also does not reject `0`. A large value such as `addr_cells == 0x40000000` also makes `4 * addr_cells` wrap to `0`, producing the same crash.","fix":"Move the validation before the division and also reject zero. Change:\n```c\nsize_t count = prop->size / (4 * addr_cells + 4 * size_cells);\nif (count > regions_count) { ... }\nif (addr_cells > 2 || size_cells > 2) {\n printk(...);\n return 0;\n}\n```\nto validate first:\n```c\nif (addr_cells == 0 || size_cells == 0 || addr_cells > 2 || size_cells > 2) {\n printk(BIOS_ERR, \"addr_cells (%d) or size_cells (%d) out of range\\n\", addr_cells, size_cells);\n return 0;\n}\nsize_t count = prop->size / (4 * addr_cells + 4 * size_cells);\nif (count > regions_count) { ... }\n```","locations":[{"ref":"src/commonlib/device_tree.c:67-96","code":" 67 | static size_t read_reg_prop(struct fdt_property *prop, u32 addr_cells, u32 size_cells,\n 68 | \t\t\t struct device_tree_region regions[], size_t regions_count)\n 69 | {\n 70 | \t// we found the reg property, no need to parse all regions in 'reg'\n 71 | \tsize_t count = prop->size / (4 * addr_cells + 4 * size_cells);\n 72 | \tif (count > regions_count) {\n 73 | \t\tprintk(BIOS_ERR, \"reg property has more entries (%zd) than regions array can hold (%zd)\\n\", count, regions_count);\n 74 | \t\tcount = regions_count;\n 75 | \t}\n 76 | \tif (addr_cells > 2 || size_cells > 2) {\n 77 | \t\tprintk(BIOS_ERR, \"addr_cells (%d) or size_cells (%d) bigger than 2\\n\",\n 78 | \t\t\t\t addr_cells, size_cells);\n 79 | \t\treturn 0;\n 80 | \t}\n 81 | \tuint32_t *ptr = prop->data;\n 82 | \tfor (int i = 0; i < count; i++) {\n 83 | \t\tif (addr_cells == 1)\n 84 | \t\t\tregions[i].addr = be32dec(ptr);\n 85 | \t\telse if (addr_cells == 2)\n 86 | \t\t\tregions[i].addr = be64dec(ptr);\n 87 | \t\tptr += addr_cells;\n 88 | \t\tif (size_cells == 1)\n 89 | \t\t\tregions[i].size = be32dec(ptr);\n 90 | \t\telse if (size_cells == 2)\n 91 | \t\t\tregions[i].size = be64dec(ptr);\n 92 | \t\tptr += size_cells;\n 93 | \t}\n 94 | \n 95 | \treturn count; // return the number of regions found in the reg property\n 96 | }","lines":30,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/commonlib/device_tree.c#L67-L96"}]},
{"id":"PANIC_3","type":"panic","type_label":"Panic","severity":"medium","difficulty":"very_easy","description":"In `storage_block_fill_write` the return value of `malloc(buffer_bytes)` is stored in `buffer` and immediately dereferenced via `ptr` in the fill loop (`*ptr++ = fill_pattern;`) with no NULL check. `buffer_bytes` can be up to 4 MiB, so on a constrained firmware heap the allocation can fail, causing a NULL-pointer write and crash. (Separately, `int ret` truncates the `uint64_t count` return value, though `count` is realistically within `int` range for fill operations.)","fix":"Add a NULL check right after the allocation.\nBefore:\n```c\nuint32_t *buffer = malloc(buffer_bytes);\nuint32_t *ptr = buffer;\n\nfor (; buffer_words ; buffer_words--)\n\t*ptr++ = fill_pattern;\n```\nAfter:\n```c\nuint32_t *buffer = malloc(buffer_bytes);\nif (buffer == NULL)\n\treturn 0;\nuint32_t *ptr = buffer;\n\nfor (; buffer_words ; buffer_words--)\n\t*ptr++ = fill_pattern;\n```","locations":[{"ref":"src/commonlib/storage/storage_write.c:116-120","code":" 116 | \tuint32_t *buffer = malloc(buffer_bytes);\n 117 | \tuint32_t *ptr = buffer;\n 118 | \n 119 | \tfor (; buffer_words ; buffer_words--)\n 120 | \t\t*ptr++ = fill_pattern;","lines":5,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/commonlib/storage/storage_write.c#L116-L120"}]},
{"id":"PANIC_4","type":"panic","type_label":"Panic","severity":"medium","difficulty":"easy","description":"In `spd_decode_ddr3()` the Medium Time Base is computed as `mtb = (((u32)spd[10]) << 8) / spd[11];` with no check that `spd[11]` (the MTB divisor byte) is non-zero. `spd[11]` comes directly from external SPD data. If it is `0`, this is a division by zero (hardware exception/hang during memory init). Note the CRC check earlier only sets `ret = SPD_STATUS_CRC_ERROR` and does NOT return (lines 129-132), so decoding always continues regardless of CRC, and a valid-checksum-but-malformed SPD can still hit this. The sibling XMP decode function explicitly guards against a zero divisor (`if (spd[181] == 0) ... ; if (spd[183] == 0) ...` at lines 434 and 451), showing the main path is missing the same guard. The later FTB divisor `spd[9] & 0x0f` is guarded by the `if` at line 286, so only `spd[11]` is unprotected.","fix":"Guard the divisor before dividing, mirroring the XMP path:\n```c\nif (spd[11] == 0) {\n\tprintram(\"Invalid MTB divisor\\n\");\n\tdimm->dram_type = SPD_MEMORY_TYPE_UNDEFINED;\n\treturn SPD_STATUS_INVALID;\n}\nmtb = (((u32)spd[10]) << 8) / spd[11];\n```","locations":[{"ref":"src/device/dram/ddr3.c:247-253","code":" 247 | \t/* Medium Timebase =\n 248 | \t * Medium Timebase (MTB) Dividend /\n 249 | \t * Medium Timebase (MTB) Divisor */\n 250 | \tmtb = (((u32)spd[10]) << 8) / spd[11];\n 251 | \n 252 | \t/* SDRAM Minimum Cycle Time (tCKmin) */\n 253 | \tdimm->tCK = spd[12] * mtb;","lines":7,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/device/dram/ddr3.c#L247-L253"}]},
{"id":"PANIC_5","type":"panic","type_label":"Panic","severity":"low","difficulty":"very_easy","description":"`acpi_device_hid()` dereferences `dev->ops->acpi_hid` after only checking `dev` for NULL, unlike the sibling `acpi_device_name()` which guards with `dev->ops && dev->ops->acpi_name`. If a caller passes a `struct device` whose `ops` is NULL, `acpi_device_hid()` performs a NULL-pointer dereference. It is a public API function, so robustness against `ops == NULL` should match `acpi_device_name()`.","fix":"Guard `dev->ops` before dereferencing.\n\nBefore:\n```c\n\t/* Check for device specific handler */\n\tif (dev->ops->acpi_hid)\n\t\treturn dev->ops->acpi_hid(dev);\n```\nAfter:\n```c\n\t/* Check for device specific handler */\n\tif (dev->ops && dev->ops->acpi_hid)\n\t\treturn dev->ops->acpi_hid(dev);\n```","locations":[{"ref":"src/acpi/device.c:119-134","code":" 119 | const char *acpi_device_hid(const struct device *dev)\n 120 | {\n 121 | \tif (!dev)\n 122 | \t\treturn NULL;\n 123 | \n 124 | \t/* Check for device specific handler */\n 125 | \tif (dev->ops->acpi_hid)\n 126 | \t\treturn dev->ops->acpi_hid(dev);\n 127 | \n 128 | \t/*\n 129 | \t * Don't walk up the tree to find any parent that can identify this device, as\n 130 | \t * PNP devices are hard to identify.\n 131 | \t */\n 132 | \n 133 | \treturn NULL;\n 134 | }","lines":16,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/acpi/device.c#L119-L134"}]},
{"id":"PANIC_6","type":"panic","type_label":"Panic","severity":"low","difficulty":"easy","description":"`generate_cpu_entries()` computes `cores_per_package = (cpuid_ebx(1) >> 16) & 0xff` and then divides `numcpus = totalcores / cores_per_package` without checking for zero. On processors that report 0 in the `EBX[23:16]` (logical processor count) field of `CPUID.1` - which is architecturally allowed for parts that do not implement that field - this is a division by zero. It is also used as the loop bound and passed to `acpigen_write_processor_package()` / `acpigen_write_processor_cnot()`.","fix":"Clamp `cores_per_package` to a minimum of 1 before dividing, e.g. `if (cores_per_package < 1) cores_per_package = 1;` after reading it from CPUID, so `numcpus = totalcores / cores_per_package` cannot divide by zero.","locations":[{"ref":"src/cpu/intel/speedstep/acpi.c:154-171","code":" 154 | \tint totalcores = dev_count_cpu();\n 155 | \tint cores_per_package = (cpuid_ebx(1) >> 16) & 0xff;\n 156 | \n 157 | \t/* This assumes that all CPUs share the same layout. */\n 158 | \tint numcpus = totalcores / cores_per_package;\n 159 | \n 160 | \tprintk(BIOS_DEBUG, \"Found %d CPU(s) with %d core(s) each.\\n\",\n 161 | \t numcpus, cores_per_package);\n 162 | \n 163 | \tfor (int cpu_id = 0; cpu_id < numcpus; ++cpu_id)\n 164 | \t\tfor (int core_id = 0; core_id < cores_per_package; core_id++)\n 165 | \t\t\tgenerate_cpu_entry(cpu_id, core_id, cores_per_package);\n 166 | \n 167 | \t/* PPKG is usually used for thermal management\n 168 | \t of the first and only package. */\n 169 | \tacpigen_write_processor_package(\"PPKG\", 0, cores_per_package);\n 170 | \n 171 | \tacpigen_write_processor_cnot(cores_per_package);","lines":18,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/cpu/intel/speedstep/acpi.c#L154-L171"}]},
{"id":"PANIC_7","type":"panic","type_label":"Panic","severity":"negligible","difficulty":"easy","description":"`timer_monotonic_get()` divides by `timer_fsb` (`usecs_elapsed = (mono_counter.last_value - current_tick) / timer_fsb`) using the value returned by `get_timer_fsb()` at line 74 without checking it for zero. The sibling function `udelay()` in the same file explicitly re-initializes the timer when `get_timer_fsb()` returns 0, but `timer_monotonic_get()` has no such guard, so a 0 FSB reading would be a division by zero. Only relevant when `LAPIC_MONOTONIC_TIMER` is selected and the FSB frequency cannot be determined.","fix":"After `timer_fsb = get_timer_fsb();` at line 74, skip the elapsed-time computation (or treat elapsed as 0) when `timer_fsb == 0`, mirroring the `if (!timer_fsb)` re-init handling already used in `udelay()`.","locations":[{"ref":"src/cpu/x86/lapic/apic_timer.c:74-77","code":" 74 | \ttimer_fsb = get_timer_fsb();\n 75 | \tcurrent_tick = lapic_read(LAPIC_TMCCT);\n 76 | \t/* Note that the APIC timer counts down. */\n 77 | \tusecs_elapsed = (mono_counter.last_value - current_tick) / timer_fsb;","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/cpu/x86/lapic/apic_timer.c#L74-L77"}]},
{"id":"UAF_1","type":"use_after_free","type_label":"Use After Free","severity":"medium","difficulty":"medium","description":"In sdl1/pdcclip.c PDC_freeclipboard(), after calling strlen(contents) to check\nthe length, the code writes into the buffer with strcpy(contents, \"PDCURSES\") and then\nimmediately calls free(contents) (lines 108-111). The strcpy write before free is intentional\n(as the comment explains), but the strlen call at line 108 operates on memory that may already\nhave been freed by a previous double-free call - there is no protection against the caller\npassing an invalid or already-freed pointer. Moreover the memset that would have made the\nuse-after-free detectable is commented out (line 106), while the strcpy still writes to\nfreed memory if called twice.","fix":"The pre-free strcpy pattern is fragile and not thread-safe. Simply call\nfree(contents) directly; the canary-write adds complexity without reliable benefit and\nrisks writing to already-freed memory on double-free.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/sdl1/pdcclip.c:92-115","code":" 92 | int PDC_freeclipboard(char *contents)\n 93 | {\n 94 | PDC_LOG((\"PDC_freeclipboard() - called\\n\"));\n 95 | \n 96 | /* should we also free empty the system clipboard? probably not */\n 97 | \n 98 | if (contents)\n 99 | {\n 100 | /* NOTE: We free the memory, but we can not set caller's pointer\n 101 | to NULL, so if caller calls again then will try to access\n 102 | free'd memory. We 1st overwrite memory with a string so if\n 103 | caller tries to use free memory they won't get what they\n 104 | expect & hopefully notice. */\n 105 | \n 106 | /* memset(contents, 0xFD, strlen(contents)); */\n 107 | \n 108 | if (strlen(contents) >= strlen(\"PDCURSES\"))\n 109 | strcpy(contents, \"PDCURSES\");\n 110 | \n 111 | free(contents);\n 112 | }\n 113 | \n 114 | return PDC_CLIP_SUCCESS;\n 115 | }","lines":24,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/sdl1/pdcclip.c#L92-L115"}]},
{"id":"MEM_1","type":"memory_leak","type_label":"Memory Leak","severity":"high","difficulty":"medium","description":"PDC_scr_open() in sdl1/pdcscrn.c allocates SP with calloc (line 73), then\ncalls SDL_Init, loads fonts, etc. On several early-exit paths the function returns ERR without\nfreeing SP. Specifically: if SDL_Init fails (line 84-86), if the font cannot be loaded (line\n101-104), and if SDL_SetVideoMode fails (line 163-166), the function returns ERR while SP\nremains allocated. This leaks sizeof(SCREEN) bytes every time initscr() is called and fails.","fix":"Add `free(SP); SP = NULL;` before each `return ERR;` inside PDC_scr_open(),\nmirroring what PDC_scr_free() does at line 59-63.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/sdl1/pdcscrn.c:67-202","code":" 67 | int PDC_scr_open(int argc, char **argv)\n 68 | {\n 69 | int i;\n 70 | \n 71 | PDC_LOG((\"PDC_scr_open() - called\\n\"));\n 72 | \n 73 | SP = calloc(1, sizeof(SCREEN));\n 74 | \n 75 | if (!SP)\n 76 | return ERR;\n 77 | \n 78 | pdc_own_screen = !pdc_screen;\n 79 | \n 80 | if (pdc_own_screen)\n 81 | {\n 82 | if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER) < 0)\n 83 | {\n 84 | fprintf(stderr, \"Could not start SDL: %s\\n\", SDL_GetError());\n 85 | return ERR;\n 86 | }\n 87 | \n 88 | atexit(SDL_Quit);\n 89 | }\n 90 | \n 91 | if (!pdc_font)\n 92 | {\n 93 | const char *fname = getenv(\"PDC_FONT\");\n 94 | pdc_font = SDL_LoadBMP(fname ? fname : \"pdcfont.bmp\");\n 95 | }\n 96 | \n 97 | if (!pdc_font)\n 98 | pdc_font = SDL_LoadBMP_RW(SDL_RWFromMem(deffont, sizeof(deffont)), 0);\n 99 | \n 100 | if (!pdc_font)\n 101 | {\n 102 | fprintf(stderr, \"Could not load font\\n\");\n 103 | return ERR;\n 104 | }\n 105 | \n 106 | SP->mono = !pdc_font->format->palette;\n 107 | \n 108 | if (!SP->mono && !pdc_back)\n 109 | {\n 110 | const char *bname = getenv(\"PDC_BACKGROUND\");\n 111 | pdc_back = SDL_LoadBMP(bname ? bname : \"pdcback.bmp\");\n 112 | }\n 113 | \n 114 | if (!SP->mono && (pdc_back || !pdc_own_screen))\n 115 | {\n 116 | SP->orig_attr = TRUE;\n 117 | SP->orig_fore = COLOR_WHITE;\n 118 | SP->orig_back = -1;\n 119 | }\n 120 | else\n 121 | SP->orig_attr = FALSE;\n 122 | \n 123 | pdc_fheight = pdc_font->h / 8;\n 124 | pdc_fwidth = pdc_font->w / 32;\n 125 | \n 126 | if (!SP->mono)\n 127 | pdc_flastc = pdc_font->format->palette->ncolors - 1;\n 128 | \n 129 | if (pdc_own_screen && !pdc_icon)\n 130 | {\n 131 | const char *iname = getenv(\"PDC_ICON\");\n 132 | pdc_icon = SDL_LoadBMP(iname ? iname : \"pdcicon.bmp\");\n 133 | \n 134 | if (!pdc_icon)\n 135 | pdc_icon = SDL_LoadBMP_RW(SDL_RWFromMem(deficon,\n 136 | sizeof(deficon)), 0);\n 137 | \n 138 | if (pdc_icon)\n 139 | SDL_WM_SetIcon(pdc_icon, NULL);\n 140 | }\n 141 | \n 142 | if (pdc_own_screen)\n 143 | {\n 144 | const char *env = getenv(\"PDC_LINES\");\n 145 | pdc_sheight = (env ? atoi(env) : 25) * pdc_fheight;\n 146 | \n 147 | env = getenv(\"PDC_COLS\");\n 148 | pdc_swidth = (env ? atoi(env) : 80) * pdc_fwidth;\n 149 | \n 150 | pdc_screen = SDL_SetVideoMode(pdc_swidth, pdc_sheight, 0,\n 151 | SDL_SWSURFACE|SDL_ANYFORMAT|SDL_RESIZABLE);\n 152 | }\n 153 | else\n 154 | {\n 155 | if (!pdc_sheight)\n 156 | pdc_sheight = pdc_screen->h - pdc_yoffset;\n 157 | \n 158 | if (!pdc_swidth)\n 159 | pdc_swidth = pdc_screen->w - pdc_xoffset;\n 160 | }\n 161 | \n 162 | if (!pdc_screen)\n 163 | {\n 164 | fprintf(stderr, \"Couldn't create a surface: %s\\n\", SDL_GetError());\n 165 | return ERR;\n 166 | }\n 167 | \n 168 | if (SP->orig_attr)\n 169 | PDC_retile();\n 170 | \n 171 | for (i = 0; i < 8; i++)\n 172 | {\n 173 | pdc_color[i].r = (i & COLOR_RED) ? 0xc0 : 0;\n 174 | pdc_color[i].g = (i & COLOR_GREEN) ? 0xc0 : 0;\n 175 | pdc_color[i].b = (i & COLOR_BLUE) ? 0xc0 : 0;\n 176 | \n 177 | pdc_color[i + 8].r = (i & COLOR_RED) ? 0xff : 0x40;\n 178 | pdc_color[i + 8].g = (i & COLOR_GREEN) ? 0xff : 0x40;\n 179 | pdc_color[i + 8].b = (i & COLOR_BLUE) ? 0xff : 0x40;\n 180 | }\n 181 | \n 182 | for (i = 0; i < 16; i++)\n 183 | pdc_mapped[i] = SDL_MapRGB(pdc_screen->format, pdc_color[i].r,\n 184 | pdc_color[i].g, pdc_color[i].b);\n 185 | \n 186 | SDL_EnableUNICODE(1);\n 187 | \n 188 | PDC_mouse_set();\n 189 | \n 190 | if (pdc_own_screen)\n 191 | PDC_set_title(argc ? argv[0] : \"PDCurses\");\n 192 | \n 193 | SP->lines = PDC_get_rows();\n 194 | SP->cols = PDC_get_columns();\n 195 | \n 196 | SP->mouse_wait = PDC_CLICK_PERIOD;\n 197 | SP->audible = FALSE;\n 198 | \n 199 | PDC_reset_prog_mode();\n 200 | \n 201 | return OK;\n 202 | }","lines":136,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/sdl1/pdcscrn.c#L67-L202"}]},
{"id":"MEM_2","type":"memory_leak","type_label":"Memory Leak","severity":"high","difficulty":"medium","description":"Connect_Fields iterates over the fields array setting field->form = form on each entry before checking for E_CONNECTED. If a later field is found to be already connected, the function returns E_CONNECTED but all previously visited fields already have their ->form pointer pointing at the new (not-yet-fully-connected) form object. Those fields are permanently orphaned from their original form and the partial state is never rolled back, leaking the page allocation that was already freed and leaving a corrupt form state.","fix":"Check all fields for pre-existing connections in a first pass before modifying any field->form pointer, or roll back all previously set field->form pointers on error before returning E_CONNECTED.","locations":[{"ref":"payloads/libpayload/curses/form/frm_def.c:176-186","code":" 176 | for (field_cnt = 0; fields[field_cnt]; field_cnt++)\n 177 | {\n 178 | if (fields[field_cnt]->form)\n 179 | \tRETURN(E_CONNECTED);\n 180 | if (field_cnt == 0 ||\n 181 | \t (fields[field_cnt]->status & _NEWPAGE))\n 182 | \tpage_nr++;\n 183 | fields[field_cnt]->form = form;\n 184 | }\n 185 | if (field_cnt == 0 || (short)field_cnt < 0)\n 186 | RETURN(E_BAD_ARGUMENT);","lines":11,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/frm_def.c#L176-L186"}]},
{"id":"MEM_3","type":"memory_leak","type_label":"Memory Leak","severity":"medium","difficulty":"medium","description":"In SubWinTest() (newdemo.c), if derwin/subwin returns NULL for swin2 or swin3, the function returns 1 immediately without freeing the already-allocated windows. If swin2 fails, swin1 is leaked. If swin3 fails, both swin1 and swin2 are leaked.","fix":"On each early-return path, call delwin() on every previously allocated subwindow before returning 1.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/demos/newdemo.c:82-87","code":" 82 | if ((swin1 = derwin(win, sh, sw, 3, 5)) == NULL)\n 83 | return 1;\n 84 | if ((swin2 = subwin(win, sh, sw, by + 4, bx + 8)) == NULL)\n 85 | return 1;\n 86 | if ((swin3 = subwin(win, sh, sw, by + 5, bx + 11)) == NULL)\n 87 | return 1;","lines":6,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/demos/newdemo.c#L82-L87"}]},
{"id":"MEM_4","type":"memory_leak","type_label":"Memory Leak","severity":"medium","difficulty":"medium","description":"In the scrolling message loop in main() (newdemo.c), malloc() is called to allocate scrollbuf but its return value is never checked. If malloc returns NULL, the subsequent memset(visbuf, ...) and strncpy(scrollbuf + i, ...) dereference a NULL pointer (offset by msg_len), causing a crash. This is reachable in any low-memory environment.","fix":"Check the return of malloc: if (!scrollbuf) { stop = 1; break; } before dereferencing visbuf or scrollbuf.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/demos/newdemo.c:349-358","code":" 349 | char *scrollbuf = malloc(scroll_len);\n 350 | char *visbuf = scrollbuf + msg_len;\n 351 | int stop = 0;\n 352 | int i;\n 353 | \n 354 | for (i = w + msg_len; i > 0; i--)\n 355 | {\n 356 | memset(visbuf, ' ', w);\n 357 | strncpy(scrollbuf + i, message, msg_len);\n 358 | mvwaddnstr(win, height / 2, 1, visbuf, w);","lines":10,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/demos/newdemo.c#L349-L358"}]},
{"id":"MEM_5","type":"memory_leak","type_label":"Memory Leak","severity":"medium","difficulty":"medium","description":"PDC_resize_screen() in sdl1/pdcscrn.c calls SDL_FreeSurface(pdc_screen) at line\n217 then immediately calls SDL_SetVideoMode. If SDL_SetVideoMode returns NULL (pdc_screen becomes\nNULL) the function still returns OK at line 228, silently leaving the application with no valid\nscreen surface. Any subsequent drawing calls will dereference the NULL pdc_screen pointer.\nAdditionally pdc_tileback is not freed before the retile on failure paths.","fix":"After SDL_SetVideoMode, check `if (!pdc_screen) return ERR;` before continuing\nand set SP->resized/cursrow/curscol only on success.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/sdl1/pdcscrn.c:206-229","code":" 206 | int PDC_resize_screen(int nlines, int ncols)\n 207 | {\n 208 | if (!pdc_own_screen)\n 209 | return ERR;\n 210 | \n 211 | if (nlines && ncols)\n 212 | {\n 213 | pdc_sheight = nlines * pdc_fheight;\n 214 | pdc_swidth = ncols * pdc_fwidth;\n 215 | }\n 216 | \n 217 | SDL_FreeSurface(pdc_screen);\n 218 | \n 219 | pdc_screen = SDL_SetVideoMode(pdc_swidth, pdc_sheight, 0,\n 220 | SDL_SWSURFACE|SDL_ANYFORMAT|SDL_RESIZABLE);\n 221 | \n 222 | if (pdc_tileback)\n 223 | PDC_retile();\n 224 | \n 225 | SP->resized = FALSE;\n 226 | SP->cursrow = SP->curscol = 0;\n 227 | \n 228 | return OK;\n 229 | }","lines":24,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/sdl1/pdcscrn.c#L206-L229"}]},
{"id":"MEM_6","type":"memory_leak","type_label":"Memory Leak","severity":"medium","difficulty":"medium","description":"In win32/pdcclip.c PDC_setclipboard(), OpenClipboard(NULL) is called at line\n99 but if GlobalAlloc fails at line 102-106, the function returns PDC_CLIP_MEMORY_ERROR without\ncalling CloseClipboard(). This leaks the open clipboard handle, preventing any other process\nfrom accessing the clipboard until the application terminates.","fix":"Add `CloseClipboard();` before `return PDC_CLIP_MEMORY_ERROR;` at line 106,\nmatching the pattern used in PDC_getclipboard().","locations":[{"ref":"payloads/libpayload/curses/PDCurses/win32/pdcclip.c:92-128","code":" 92 | int PDC_setclipboard(const char *contents, long length)\n 93 | {\n 94 | HGLOBAL ptr1;\n 95 | LPTSTR ptr2;\n 96 | \n 97 | PDC_LOG((\"PDC_setclipboard() - called\\n\"));\n 98 | \n 99 | if (!OpenClipboard(NULL))\n 100 | return PDC_CLIP_ACCESS_ERROR;\n 101 | \n 102 | ptr1 = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE,\n 103 | (length + 1) * sizeof(TCHAR));\n 104 | \n 105 | if (!ptr1)\n 106 | return PDC_CLIP_MEMORY_ERROR;\n 107 | \n 108 | ptr2 = GlobalLock(ptr1);\n 109 | \n 110 | #ifdef PDC_WIDE\n 111 | PDC_mbstowcs((wchar_t *)ptr2, contents, length);\n 112 | #else\n 113 | memcpy((char *)ptr2, contents, length + 1);\n 114 | #endif\n 115 | GlobalUnlock(ptr1);\n 116 | EmptyClipboard();\n 117 | \n 118 | if (!SetClipboardData(PDC_TEXT, ptr1))\n 119 | {\n 120 | GlobalFree(ptr1);\n 121 | return PDC_CLIP_ACCESS_ERROR;\n 122 | }\n 123 | \n 124 | CloseClipboard();\n 125 | GlobalFree(ptr1);\n 126 | \n 127 | return PDC_CLIP_SUCCESS;\n 128 | }","lines":37,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/win32/pdcclip.c#L92-L128"}]},
{"id":"MEM_7","type":"memory_leak","type_label":"Memory Leak","severity":"medium","difficulty":"medium","description":"In `_nc_Make_Argument()` for a `_LINKED_TYPE`, the node `p` is allocated and its\n`left` and `right` sub-arguments are constructed recursively (lines 100-101).\nIf the left recursive call succeeds but the right fails (incrementing `*err`),\nthe function returns `p` immediately (line 102) with a partially-constructed\nargument tree. The caller (`_nc_Copy_Type` / `set_field_type`) checks `*err != 0`\nand then calls `_nc_Free_Argument(dst->type, (TypeArgument*)(dst->arg))`, passing\n`p` as the arg to free. `_nc_Free_Argument` for a linked type will free both\n`p->left` (valid or NULL) and `p->right` (NULL since it failed), and then free\n`p` itself. The right sub-argument's partially-allocated internal resources (if\n`makearg` partially succeeded before failing) will be leaked since `p->right` is\nNULL and `_nc_Free_Argument` bails early. This is the same structural issue as\nthe copy path.","fix":"After the recursive calls, check whether `*err` increased since entry. If so,\ncall `_nc_Free_Argument(typ->left, p->left)` and `_nc_Free_Argument(typ->right,\np->right)` before freeing `p` and returning NULL, rather than returning a\npartially-valid node.","locations":[{"ref":"payloads/libpayload/curses/form/fld_def.c:94-118","code":" 94 | if ((typ->status & _LINKED_TYPE) != 0)\n 95 | \t{\n 96 | \t p = typeMalloc(TypeArgument, 1);\n 97 | \n 98 | \t if (p != 0)\n 99 | \t {\n 100 | \t p->left = _nc_Make_Argument(typ->left, ap, err);\n 101 | \t p->right = _nc_Make_Argument(typ->right, ap, err);\n 102 | \t return p;\n 103 | \t }\n 104 | \t else\n 105 | \t {\n 106 | \t *err += 1;\n 107 | \t }\n 108 | \t}\n 109 | else\n 110 | \t{\n 111 | \t assert(typ->makearg != (void *)0);\n 112 | \t if (!(res = (TypeArgument *)typ->makearg(ap)))\n 113 | \t {\n 114 | \t *err += 1;\n 115 | \t }\n 116 | \t}\n 117 | }\n 118 | return res;","lines":25,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/fld_def.c#L94-L118"}]},
{"id":"MEM_8","type":"memory_leak","type_label":"Memory Leak","severity":"medium","difficulty":"medium","description":"In `new_field()` (USE_WIDEC_SUPPORT build), `New_Field->working` (a newpad) and\n`New_Field->expanded` (a char* array) are allocated unconditionally on lines 307-308\nbefore any of the subsequent allocations that can fail (`_nc_Copy_Type` and the\n`buf` malloc). If either of those later allocations fails, execution falls through\nto `free_field(New_Field)`. `free_field()` correctly frees `expanded` and calls\n`delwin(working)` only when `field->expanded != NULL`. Since `expanded` was\nallocated with `typeCalloc` it is non-NULL but may be all-zeros, and `working`\ncould have failed (returned NULL from `newpad`). `delwin(NULL)` behaviour is\nimplementation-defined. More critically, if `typeCalloc` itself returns NULL\n(OOM), then `expanded` is NULL and the `working` pad leaks since `free_field()`\nonly cleans up widec resources when `expanded != NULL`.","fix":"Check both allocations for NULL immediately after they are made and jump to the\nerror path (calling `free_field`) if either fails. Also guard `delwin(working)`\nagainst a NULL `working` pointer in `free_field()`.","locations":[{"ref":"payloads/libpayload/curses/form/fld_def.c:306-334","code":" 306 | #if USE_WIDEC_SUPPORT\n 307 | New_Field->working = newpad(1, Buffer_Length(New_Field) + 1);\n 308 | New_Field->expanded = typeCalloc(char *, 1 + (unsigned)nbuf);\n 309 | #endif\n 310 | \n 311 | if (_nc_Copy_Type(New_Field, &default_field))\n 312 | \t{\n 313 | \t size_t len;\n 314 | \n 315 | \t len = Total_Buffer_Size(New_Field);\n 316 | \t if ((New_Field->buf = (FIELD_CELL *)malloc(len)))\n 317 | \t {\n 318 | \t /* Prefill buffers with blanks and insert terminating zeroes\n 319 | \t between buffers */\n 320 | \t int i, j;\n 321 | \t int cells = Buffer_Length(New_Field);\n 322 | \n 323 | \t for (i = 0; i <= New_Field->nbuf; i++)\n 324 | \t\t{\n 325 | \t\t FIELD_CELL *buffer = &(New_Field->buf[(cells + 1) * i]);\n 326 | \n 327 | \t\t for (j = 0; j < cells; ++j)\n 328 | \t\t {\n 329 | \t\t buffer[j] = blank;\n 330 | \t\t }\n 331 | \t\t buffer[j] = zeros;\n 332 | \t\t}\n 333 | \t returnField(New_Field);\n 334 | \t }","lines":29,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/fld_def.c#L306-L334"}]},
{"id":"MEM_9","type":"memory_leak","type_label":"Memory Leak","severity":"medium","difficulty":"medium","description":"In Generic_RegularExpression_Type (HAVE_REGEXP_H_FUNCS / HAVE_REGEXPR_H_FUNCS branch),\npArg->refCount is allocated at line 154 before the compilation retry loop.\nTwo paths leak it:\n1. If compile() returns a fatal error (reg_errno != 50): free(pArg) is called at\n line 177 but pArg->refCount is never freed first.\n2. If the loop exhausts blen > MAX_RX_LEN and pArg->compiled_expression stays NULL:\n the cleanup at lines 191-194 calls free(pArg) without freeing pArg->refCount.\nIn both cases the refCount allocation is permanently lost.","fix":"Free pArg->refCount before freeing pArg in both error paths:\n /* fatal error path: */\n free(pArg->refCount);\n free(pArg);\n pArg = NULL;\n /* and in the post-loop cleanup: */\n free(pArg->refCount);\n free(pArg);\n pArg = NULL;","locations":[{"ref":"payloads/libpayload/curses/form/fty_regex.c:146-195","code":" 146 | pArg = typeMalloc(RegExp_Arg, 1);\n 147 | \n 148 | if (pArg)\n 149 | \t{\n 150 | \t int blen = RX_INCREMENT;\n 151 | \n 152 | \t T((T_CREATE(\"RegExp_Arg %p\"), pArg));\n 153 | \t pArg->compiled_expression = NULL;\n 154 | \t pArg->refCount = typeMalloc(unsigned long, 1);\n 155 | \n 156 | \t *(pArg->refCount) = 1;\n 157 | \n 158 | \t do\n 159 | \t {\n 160 | \t char *buf = typeMalloc(char, blen);\n 161 | \n 162 | \t if (buf)\n 163 | \t\t{\n 164 | #if HAVE_REGEXP_H_FUNCS\n 165 | \t\t char *last_pos = compile(rx, buf, &buf[blen], '\\0');\n 166 | \n 167 | #else /* HAVE_REGEXPR_H_FUNCS */\n 168 | \t\t char *last_pos = compile(rx, buf, &buf[blen]);\n 169 | #endif\n 170 | \t\t if (reg_errno)\n 171 | \t\t {\n 172 | \t\t free(buf);\n 173 | \t\t if (reg_errno == 50)\n 174 | \t\t\tblen += RX_INCREMENT;\n 175 | \t\t else\n 176 | \t\t\t{\n 177 | \t\t\t free(pArg);\n 178 | \t\t\t pArg = NULL;\n 179 | \t\t\t break;\n 180 | \t\t\t}\n 181 | \t\t }\n 182 | \t\t else\n 183 | \t\t {\n 184 | \t\t pArg->compiled_expression = buf;\n 185 | \t\t break;\n 186 | \t\t }\n 187 | \t\t}\n 188 | \t }\n 189 | \t while (blen <= MAX_RX_LEN);\n 190 | \t}\n 191 | if (pArg && !pArg->compiled_expression)\n 192 | \t{\n 193 | \t free(pArg);\n 194 | \t pArg = NULL;\n 195 | \t}","lines":50,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/fty_regex.c#L146-L195"}]},
{"id":"MEM_10","type":"memory_leak","type_label":"Memory Leak","severity":"medium","difficulty":"medium","description":"In m_new.c new_menu() / m_post.c post_menu(), when typeCalloc(MENU,1) succeeds and _nc_Connect_Items() fails, the code correctly calls free(menu). However, free_menu() does NOT free menu->pattern even though pattern can be allocated in _nc_Connect_Items() (via _nc_Calculate_Item_Length_and_Width which calls set_menu_format which allocates pattern). If _nc_Connect_Items allocates the pattern buffer internally and then fails after that allocation, the partial cleanup via free(menu) leaks the pattern buffer because the menu struct is freed directly without calling _nc_Disconnect_Items or zeroing pattern first.","fix":"Before freeing menu on connect failure, call _nc_Disconnect_Items(menu) if it handles partial state, or explicitly free menu->pattern if set, matching the logic in free_menu().","locations":[{"ref":"payloads/libpayload/curses/menu/m_new.c:75-80","code":" 75 | \t if (!_nc_Connect_Items(menu, items))\n 76 | \t {\n 77 | \t err = E_NOT_CONNECTED;\n 78 | \t free(menu);\n 79 | \t menu = (MENU *) 0;\n 80 | \t }","lines":6,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/menu/m_new.c#L75-L80"}]},
{"id":"MEM_11","type":"memory_leak","type_label":"Memory Leak","severity":"low","difficulty":"medium","description":"In cbfs_module_init(), filenames[index] = strdup(f->filename) is called for each CBFS file but the return value of strdup is never checked for NULL. If strdup fails (allocation failure), a NULL pointer is stored in the filenames array. Later in cbfs_module_redraw(), findfile(filenames[i]) and strlen(filenames[i]) dereference this NULL, causing a crash. Additionally, on init failure after partial allocation, the previously strdup'd strings are never freed.","fix":"Check the return value of strdup and return an error on failure. On error, free all previously allocated filenames before returning.","locations":[{"ref":"payloads/coreinfo/cbfs_module.c:100-105","code":" 100 | \tfilenames = malloc(filecount * sizeof(char *));\n 101 | \tif (filenames == NULL)\n 102 | \t\treturn 0;\n 103 | \n 104 | \tfor (f = firstfile(); f; f = nextfile(f))\n 105 | \t\tfilenames[index++] = strdup((const char *)f->filename);","lines":6,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/coreinfo/cbfs_module.c#L100-L105"}]},
{"id":"MEM_12","type":"memory_leak","type_label":"Memory Leak","severity":"low","difficulty":"easy","description":"`acpi_dp_free()` recursively frees the sub-tables of `ACPI_DP_TYPE_CHILD` (via `dp->child`) and `ACPI_DP_TYPE_ARRAY` (via `dp->array`) nodes, but has no case for `ACPI_DP_TYPE_PACKAGE`. However `acpi_dp_add_package()` stores the package table in `new->child` (`new->child = package`). Because the `switch` in `acpi_dp_free()` falls through to `default` for `ACPI_DP_TYPE_PACKAGE`, the entire package sub-table (and everything appended to it) is leaked when `acpi_dp_write()` cleans up. This path is reached by common helpers such as `acpi_device_add_external_facing_port()`, `acpi_device_add_storage_d3_enable()`, `acpi_device_add_dma_property()`, etc., all of which go through `acpi_device_add_integer_property_with_uuid()` -> `acpi_dp_add_package()`. The leak is bounded and one-time-per-boot but is a genuine unbalanced `malloc`/`free`.","fix":"Add a case for `ACPI_DP_TYPE_PACKAGE` that frees its child table:\n```c\ncase ACPI_DP_TYPE_CHILD:\n\tacpi_dp_free(dp->child);\n\tbreak;\ncase ACPI_DP_TYPE_PACKAGE:\n\tacpi_dp_free(dp->child);\n\tbreak;\ncase ACPI_DP_TYPE_ARRAY:\n\tacpi_dp_free(dp->array);\n\tbreak;\n```","locations":[{"ref":"src/acpi/device.c:820-839","code":" 820 | static void acpi_dp_free(struct acpi_dp *dp)\n 821 | {\n 822 | \twhile (dp) {\n 823 | \t\tstruct acpi_dp *p = dp->next;\n 824 | \n 825 | \t\tswitch (dp->type) {\n 826 | \t\tcase ACPI_DP_TYPE_CHILD:\n 827 | \t\t\tacpi_dp_free(dp->child);\n 828 | \t\t\tbreak;\n 829 | \t\tcase ACPI_DP_TYPE_ARRAY:\n 830 | \t\t\tacpi_dp_free(dp->array);\n 831 | \t\t\tbreak;\n 832 | \t\tdefault:\n 833 | \t\t\tbreak;\n 834 | \t\t}\n 835 | \n 836 | \t\tfree(dp);\n 837 | \t\tdp = p;\n 838 | \t}\n 839 | }","lines":20,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/acpi/device.c#L820-L839"},{"ref":"src/acpi/device.c:1076-1090","code":" 1076 | struct acpi_dp *acpi_dp_add_package(struct acpi_dp *dp, struct acpi_dp *package)\n 1077 | {\n 1078 | \tstruct acpi_dp *new;\n 1079 | \n 1080 | \tif (!dp || !package || package->type != ACPI_DP_TYPE_TABLE)\n 1081 | \t\treturn NULL;\n 1082 | \n 1083 | \tnew = acpi_dp_new(dp, ACPI_DP_TYPE_PACKAGE, NULL);\n 1084 | \tif (new) {\n 1085 | \t\tnew->uuid = package->name;\n 1086 | \t\tnew->child = package;\n 1087 | \t}\n 1088 | \n 1089 | \treturn new;\n 1090 | }","lines":15,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/acpi/device.c#L1076-L1090"}]},
{"id":"OVF_1","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"critical","difficulty":"very_easy","description":"In bootlog_module_init(), g_buf is allocated with only chars_count bytes, but when the CBMEM console has overflowed (OVERFLOW bit set), the code writes overflow_chars_count bytes at g_buf[0] and then chars_count bytes at g_buf[overflow_chars_count]. The total bytes written is overflow_chars_count + chars_count, which exceeds the allocated chars_count bytes, causing a heap buffer overflow. This corrupts heap metadata and adjacent allocations.","fix":"Change the malloc call to allocate the full required size: g_buf = malloc(chars_count + overflow_chars_count). The variable g_lines_count already correctly uses the sum on line 125.","locations":[{"ref":"payloads/coreinfo/bootlog_module.c:128-143","code":" 128 | \tg_buf = malloc(chars_count);\n 129 | \tif (!g_buf) {\n 130 | \t\treturn -3;\n 131 | \t}\n 132 | \n 133 | \tif (console->cursor & OVERFLOW) {\n 134 | \t\tif (sanitize_buffer_for_display(buffer + cursor, size - cursor,\n 135 | \t\t\t\tg_buf, overflow_chars_count, SCREEN_X) < 0) {\n 136 | \t\t\tgoto err_free;\n 137 | \t\t}\n 138 | \t}\n 139 | \tif (sanitize_buffer_for_display(buffer, cursor,\n 140 | \t\t\t\t\tg_buf + overflow_chars_count,\n 141 | \t\t\t\t\tchars_count, SCREEN_X) < 0) {\n 142 | \t\tgoto err_free;\n 143 | \t}","lines":16,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/coreinfo/bootlog_module.c#L128-L143"}]},
{"id":"OVF_2","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"high","difficulty":"medium","description":"In timestamps_module_init(), the buffer is allocated as (max_entries + 4) * SCREEN_X bytes, but the per-entry formatting in timestamp_print_entry() can write significantly more than SCREEN_X bytes per entry across its multiple snprintf calls (up to ~95 bytes per entry: 51 for id+name, 20 for timestamp, 2 for ' (', 20 for step_time, 1 for ')', 1 for newline). Each individual snprintf is limited to SCREEN_X=80 bytes, but because buff_cur accumulates across all calls, approximately 95 bytes are written per entry. For more than ~15 entries (common in practice: coreboot usually records 50+ timestamps), the buffer overflows. Additionally, each snprintf call passes SCREEN_X rather than the remaining buffer space, so the size argument is semantically wrong.","fix":"Increase the buffer allocation to account for actual maximum bytes per entry. Calculate the real worst-case size per entry (at least 100 bytes) and allocate accordingly: buffer = malloc((max_entries + 4) * 100). Also pass the remaining buffer size (buffer_total_size - buff_cur) to snprintf instead of SCREEN_X.","locations":[{"ref":"payloads/coreinfo/timestamps_module.c:159-186","code":" 159 | \tbuffer = malloc((max_entries + 4) * SCREEN_X * sizeof(char));\n 160 | \n 161 | \tif (buffer == NULL)\n 162 | \t\treturn -3;\n 163 | \n 164 | \t/* Write the content */\n 165 | \tbuff_cur += snprintf(buffer, SCREEN_X, \"%d entries total:\\n\\n\",\n 166 | \t\t\tn_entries);\n 167 | \n 168 | \tprev_stamp = 0;\n 169 | \ttimestamp_print_entry(buffer, SCREEN_X, &buff_cur, 0, base_time,\n 170 | \t\t\tprev_stamp);\n 171 | \tprev_stamp = base_time;\n 172 | \n 173 | \ttotal_time = 0;\n 174 | \tfor (u32 i = 0; i < n_entries; i++) {\n 175 | \t\tuint64_t stamp;\n 176 | \t\tconst struct timestamp_entry *tse = ×tamps->entries[i];\n 177 | \n 178 | \t\tstamp = tse->entry_stamp + base_time;\n 179 | \t\ttotal_time += timestamp_print_entry(buffer, SCREEN_X,\n 180 | \t\t\t\t&buff_cur, tse->entry_id, stamp, prev_stamp);\n 181 | \t\tprev_stamp = stamp;\n 182 | \t}\n 183 | \n 184 | \tbuff_cur += snprintf(buffer + buff_cur, SCREEN_X, \"\\nTotal Time: \");\n 185 | \tbuff_cur += snprintf(buffer + buff_cur, SCREEN_X, \"%llu\", total_time);\n 186 | \tbuff_cur += snprintf(buffer + buff_cur, SCREEN_X, \"\\n\");","lines":28,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/coreinfo/timestamps_module.c#L159-L186"}]},
{"id":"OVF_3","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"high","difficulty":"medium","description":"In dos/pdcdisp.c and os2/pdcdisp.c, PDC_transform_line() declares a fixed-size local buffer\ntemp_line[256] but iterates up to `len` characters from `srcp`, writing into temp_line[j] for\nj in [0, len). The caller passes a `len` derived from the physical screen width (curscr->_maxx).\nScreen widths above 256 columns (e.g. 320-column modes or wide virtual terminals) cause a\nstack buffer overflow, corrupting the stack frame and potentially enabling code execution.","fix":"Increase temp_line to at least MAX_SCREEN_COLS (e.g. 512), or allocate it dynamically based on `len`, and add an assertion/clamp so `len` never exceeds the buffer size.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/dos/pdcdisp.c:94-109","code":" 94 | struct {unsigned char text, attr;} temp_line[256];\n 95 | \n 96 | /* replace the attribute part of the chtype with the actual\n 97 | color value for each chtype in the line */\n 98 | \n 99 | for (j = 0; j < len; j++)\n 100 | {\n 101 | chtype ch = srcp[j];\n 102 | \n 103 | temp_line[j].attr = pdc_atrtab[ch >> PDC_ATTR_SHIFT];\n 104 | #ifdef CHTYPE_LONG\n 105 | if (ch & A_ALTCHARSET && !(ch & 0xff80))\n 106 | ch = acs_map[ch & 0x7f];\n 107 | #endif\n 108 | temp_line[j].text = ch & 0xff;\n 109 | }","lines":16,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/dos/pdcdisp.c#L94-L109"},{"ref":"payloads/libpayload/curses/PDCurses/os2/pdcdisp.c:68-87","code":" 68 | struct {unsigned char text, attr;} temp_line[256];\n 69 | int j;\n 70 | \n 71 | PDC_LOG((\"PDC_transform_line() - called: line %d\\n\", lineno));\n 72 | \n 73 | /* replace the attribute part of the chtype with the\n 74 | actual color value for each chtype in the line */\n 75 | \n 76 | for (j = 0; j < len; j++)\n 77 | {\n 78 | chtype ch = srcp[j];\n 79 | \n 80 | temp_line[j].attr = pdc_atrtab[ch >> PDC_ATTR_SHIFT];\n 81 | \n 82 | #ifdef CHTYPE_LONG\n 83 | if (ch & A_ALTCHARSET && !(ch & 0xff80))\n 84 | ch = acs_map[ch & 0x7f];\n 85 | #endif\n 86 | temp_line[j].text = ch & 0xff;\n 87 | }","lines":20,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/os2/pdcdisp.c#L68-L87"}]},
{"id":"OVF_4","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"high","difficulty":"very_easy","description":"In wgetnstr() (non-wide path), when n <= 0 the function proceeds without error -- n is never validated to be positive before use. A caller passing n=0 skips the 'chars < n' guard (0 < 0 is always false), so no characters are stored, but the final '*p = NUL' still writes into an unbounded location since p==str is valid. More critically, when the caller passes a negative n (other than -1 which would come from the internal wrappers), no clamping to MAXLINE occurs in the non-wide path; but the check 'if (n < 0 || n > MAXLINE) n = MAXLINE' only exists in the PDC_WIDE branch (line 74). The non-wide path has no such check, so a caller to getnstr(str, INT_MIN) would result in 'chars < n' always being true, writing unboundedly into str.","fix":"Add the same n clamping used in the wide path ('if (n < 0 || n > MAXLINE) n = MAXLINE;') at the top of the non-wide branch of wgetnstr(), before the main loop.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/getstr.c:69-212","code":" 69 | int wgetnstr(WINDOW *win, char *str, int n)\n 70 | {\n 71 | #ifdef PDC_WIDE\n 72 | wchar_t wstr[MAXLINE + 1];\n 73 | \n 74 | if (n < 0 || n > MAXLINE)\n 75 | n = MAXLINE;\n 76 | \n 77 | if (wgetn_wstr(win, (wint_t *)wstr, n) == ERR)\n 78 | return ERR;\n 79 | \n 80 | return PDC_wcstombs(str, wstr, n);\n 81 | #else\n 82 | int ch, i, num, x, chars;\n 83 | char *p;\n 84 | bool stop, oldecho, oldcbreak, oldnodelay;\n 85 | \n 86 | PDC_LOG((\"wgetnstr() - called\\n\"));\n 87 | \n 88 | if (!win || !str)\n 89 | return ERR;\n 90 | \n 91 | chars = 0;\n 92 | p = str;\n 93 | stop = FALSE;\n 94 | \n 95 | x = win->_curx;\n 96 | \n 97 | oldcbreak = SP->cbreak; /* remember states */\n 98 | oldecho = SP->echo;\n 99 | oldnodelay = win->_nodelay;\n 100 | \n 101 | SP->echo = FALSE; /* we do echo ourselves */\n 102 | cbreak(); /* ensure each key is returned immediately */\n 103 | win->_nodelay = FALSE; /* don't return -1 */\n 104 | \n 105 | wrefresh(win);\n 106 | \n 107 | while (!stop)\n 108 | {\n 109 | ch = wgetch(win);\n 110 | \n 111 | switch (ch)\n 112 | {\n 113 | \n 114 | case '\\t':\n 115 | ch = ' ';\n 116 | num = TABSIZE - (win->_curx - x) % TABSIZE;\n 117 | for (i = 0; i < num; i++)\n 118 | {\n 119 | if (chars < n)\n 120 | {\n 121 | if (oldecho)\n 122 | waddch(win, ch);\n 123 | *p++ = ch;\n 124 | ++chars;\n 125 | }\n 126 | else\n 127 | beep();\n 128 | }\n 129 | break;\n 130 | \n 131 | case _ECHAR: /* CTRL-H -- Delete character */\n 132 | if (p > str)\n 133 | {\n 134 | if (oldecho)\n 135 | waddstr(win, \"\\b \\b\");\n 136 | ch = (unsigned char)(*--p);\n 137 | if ((ch < ' ') && (oldecho))\n 138 | waddstr(win, \"\\b \\b\");\n 139 | chars--;\n 140 | }\n 141 | break;\n 142 | \n 143 | case _DLCHAR: /* CTRL-U -- Delete line */\n 144 | while (p > str)\n 145 | {\n 146 | if (oldecho)\n 147 | waddstr(win, \"\\b \\b\");\n 148 | ch = (unsigned char)(*--p);\n 149 | if ((ch < ' ') && (oldecho))\n 150 | waddstr(win, \"\\b \\b\");\n 151 | }\n 152 | chars = 0;\n 153 | break;\n 154 | \n 155 | case _DWCHAR: /* CTRL-W -- Delete word */\n 156 | \n 157 | while ((p > str) && (*(p - 1) == ' '))\n 158 | {\n 159 | if (oldecho)\n 160 | waddstr(win, \"\\b \\b\");\n 161 | \n 162 | --p; /* remove space */\n 163 | chars--;\n 164 | }\n 165 | while ((p > str) && (*(p - 1) != ' '))\n 166 | {\n 167 | if (oldecho)\n 168 | waddstr(win, \"\\b \\b\");\n 169 | \n 170 | ch = (unsigned char)(*--p);\n 171 | if ((ch < ' ') && (oldecho))\n 172 | waddstr(win, \"\\b \\b\");\n 173 | chars--;\n 174 | }\n 175 | break;\n 176 | \n 177 | case '\\n':\n 178 | case '\\r':\n 179 | stop = TRUE;\n 180 | if (oldecho)\n 181 | waddch(win, '\\n');\n 182 | break;\n 183 | \n 184 | default:\n 185 | if (chars < n)\n 186 | {\n 187 | if (!SP->key_code && ch < 0x100)\n 188 | {\n 189 | *p++ = ch;\n 190 | if (oldecho)\n 191 | waddch(win, ch);\n 192 | chars++;\n 193 | }\n 194 | }\n 195 | else\n 196 | beep();\n 197 | \n 198 | break;\n 199 | \n 200 | }\n 201 | \n 202 | wrefresh(win);\n 203 | }\n 204 | \n 205 | *p = '\\0';\n 206 | \n 207 | SP->echo = oldecho; /* restore old settings */\n 208 | SP->cbreak = oldcbreak;\n 209 | win->_nodelay = oldnodelay;\n 210 | \n 211 | return OK;\n 212 | #endif","lines":144,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/getstr.c#L69-L212"}]},
{"id":"OVF_5","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"high","difficulty":"medium","description":"getstr(), wgetstr(), mvgetstr(), and mvwgetstr() all call wgetnstr() with n=MAXLINE (255). The caller-supplied buffer must therefore be at least 256 bytes, but the API gives no way to enforce this. A caller that allocates fewer than 256 bytes (e.g. 'char buf[64]') will get a stack/heap buffer overflow because wgetnstr() will write up to MAXLINE characters plus a NUL terminator into the buffer. The comment in the man-page header even warns: 'there's no way to know how long the buffer passed to wgetstr() is', yet the implementation picks an arbitrary 255-byte cap rather than bounding to what wgetnstr() was passed. The PDC_WIDE branch delegates to wgetn_wstr() with the same n, then calls PDC_wcstombs(str, wstr, n) -- the output of wcstombs can exceed n bytes for multi-byte encodings, so n is an insufficient guard there too.","fix":"Remove the unbounded wgetstr/getstr/mvgetstr/mvwgetstr wrappers, or at minimum document that the buffer must be at least MAXLINE+1 bytes. For PDC_WIDE, use the byte-count returned by PDC_wcstombs to ensure it did not exceed n.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/getstr.c:215-246","code":" 215 | int getstr(char *str)\n 216 | {\n 217 | PDC_LOG((\"getstr() - called\\n\"));\n 218 | \n 219 | return wgetnstr(stdscr, str, MAXLINE);\n 220 | }\n 221 | \n 222 | int wgetstr(WINDOW *win, char *str)\n 223 | {\n 224 | PDC_LOG((\"wgetstr() - called\\n\"));\n 225 | \n 226 | return wgetnstr(win, str, MAXLINE);\n 227 | }\n 228 | \n 229 | int mvgetstr(int y, int x, char *str)\n 230 | {\n 231 | PDC_LOG((\"mvgetstr() - called\\n\"));\n 232 | \n 233 | if (move(y, x) == ERR)\n 234 | return ERR;\n 235 | \n 236 | return wgetnstr(stdscr, str, MAXLINE);\n 237 | }\n 238 | \n 239 | int mvwgetstr(WINDOW *win, int y, int x, char *str)\n 240 | {\n 241 | PDC_LOG((\"mvwgetstr() - called\\n\"));\n 242 | \n 243 | if (wmove(win, y, x) == ERR)\n 244 | return ERR;\n 245 | \n 246 | return wgetnstr(win, str, MAXLINE);","lines":32,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/getstr.c#L215-L246"}]},
{"id":"OVF_6","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"high","difficulty":"medium","description":"PDC_wcstombs() (PDC_FORCE_UTF8 path) overflows the destination buffer when encoding multi-byte UTF-8 sequences. The loop condition is 'while (*src && i < n)', which allows i to be as large as n-1 when entering the body. For a 2-byte sequence (code < 0x800), the code writes dest[i] and dest[i+1] then does i += 2. If i started at n-1, dest[n] is written - one byte past the n-byte buffer. For a 3-byte sequence the problem is worse: from i=n-2 dest[n] is written; from i=n-1 both dest[n] and dest[n+1] are written. Additionally, the null terminator 'dest[i] = 0' after the loop can be written to dest[n] when the loop exhausted the buffer with multi-byte characters (i increments by 2 or 3, easily reaching or exceeding n).","fix":"Change the loop condition to check that there is enough room for the widest possible encoding before writing: for a 2-byte sequence require i + 1 < n, for a 3-byte sequence require i + 2 < n. A clean approach: 'while (*src) { code = *src++; bytes = (code < 0x80) ? 1 : (code < 0x800) ? 2 : 3; if (i + bytes >= n) break; /* write bytes */ i += bytes; } dest[i] = 0;'","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/util.c:279-307","code":" 279 | while (*src && i < n)\n 280 | {\n 281 | chtype code = *src++;\n 282 | \n 283 | if (code < 0x80)\n 284 | {\n 285 | dest[i] = code;\n 286 | i++;\n 287 | }\n 288 | else\n 289 | if (code < 0x800)\n 290 | {\n 291 | dest[i] = ((code & 0x07c0) >> 6) | 0xc0;\n 292 | dest[i + 1] = (code & 0x003f) | 0x80;\n 293 | i += 2;\n 294 | }\n 295 | else\n 296 | {\n 297 | dest[i] = ((code & 0xf000) >> 12) | 0xe0;\n 298 | dest[i + 1] = ((code & 0x0fc0) >> 6) | 0x80;\n 299 | dest[i + 2] = (code & 0x003f) | 0x80;\n 300 | i += 3;\n 301 | }\n 302 | }\n 303 | # else\n 304 | size_t i = wcstombs(dest, src, n);\n 305 | # endif\n 306 | dest[i] = '\\0';\n 307 | return i;","lines":29,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/util.c#L279-L307"}]},
{"id":"OVF_7","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"high","difficulty":"very_easy","description":"PDC_transform_line() in win32/pdcdisp.c uses a fixed-size stack buffer of 512 CHAR_INFO\nelements. The function accepts an arbitrary `len` parameter from its callers. If `len` exceeds 512 the\nloop at line 104 writes past the end of `ci[512]`, causing a stack buffer overflow. A terminal window\nwider than 512 columns (unlikely but possible) or a malicious/corrupted len value would trigger this.","fix":"Either cap len to 512 at the start of the function (add `if (len > 512) len = 512;`)\nor allocate `ci` dynamically with `malloc(len * sizeof(CHAR_INFO))` and free it before returning.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/win32/pdcdisp.c:87-116","code":" 87 | CHAR_INFO ci[512];\n 88 | int j;\n 89 | COORD bufSize, bufPos;\n 90 | SMALL_RECT sr;\n 91 | \n 92 | PDC_LOG((\"PDC_transform_line() - called: lineno=%d\\n\", lineno));\n 93 | \n 94 | bufPos.X = bufPos.Y = 0;\n 95 | \n 96 | bufSize.X = len;\n 97 | bufSize.Y = 1;\n 98 | \n 99 | sr.Top = lineno;\n 100 | sr.Bottom = lineno;\n 101 | sr.Left = x;\n 102 | sr.Right = x + len - 1;\n 103 | \n 104 | for (j = 0; j < len; j++)\n 105 | {\n 106 | chtype ch = srcp[j];\n 107 | \n 108 | ci[j].Attributes = pdc_atrtab[ch >> PDC_ATTR_SHIFT];\n 109 | #ifdef CHTYPE_LONG\n 110 | if (ch & A_ALTCHARSET && !(ch & 0xff80))\n 111 | ch = acs_map[ch & 0x7f];\n 112 | #endif\n 113 | ci[j].Char.UnicodeChar = ch & A_CHARTEXT;\n 114 | }\n 115 | \n 116 | WriteConsoleOutput(pdc_con_out, ci, bufSize, bufPos, &sr);","lines":30,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/win32/pdcdisp.c#L87-L116"}]},
{"id":"OVF_8","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"high","difficulty":"medium","description":"In Previous_Enum, when args->count == 0 the kwds pointer is computed as\n'&args->kwds[cnt - 1]' which evaluates to '&args->kwds[-1]' (args->kwds is NULL\nwhen count is 0, per Generic_Enum_Type). This is undefined behavior - out-of-bounds\npointer arithmetic on a NULL pointer. The subsequent 'if (kwds)' check is always\ntrue (non-null pointer due to arithmetic), so the while loop then dereferences\nthe invalid pointer, causing a crash or memory corruption.","fix":"Guard against count == 0 at the start of Previous_Enum:\n if (!args->kwds || args->count <= 0)\n return FALSE;\nPlace this check before the kwds pointer computation at line 385.","locations":[{"ref":"payloads/libpayload/curses/form/fty_enum.c:383-405","code":" 383 | const enumARG *args = (const enumARG *)argp;\n 384 | int cnt = args->count;\n 385 | char **kwds = &args->kwds[cnt - 1];\n 386 | bool ccase = args->checkcase;\n 387 | unsigned char *bp = (unsigned char *)field_buffer(field, 0);\n 388 | \n 389 | if (kwds)\n 390 | {\n 391 | while (cnt--)\n 392 | \t{\n 393 | \t if (Compare((unsigned char *)(*kwds--), bp, ccase) == EXACT)\n 394 | \t break;\n 395 | \t}\n 396 | \n 397 | if (cnt <= 0)\n 398 | \tkwds = &args->kwds[args->count - 1];\n 399 | \n 400 | if ((cnt >= 0) || (Compare((const unsigned char *)dummy, bp, ccase) == EXACT))\n 401 | \t{\n 402 | \t set_field_buffer(field, 0, *kwds);\n 403 | \t return TRUE;\n 404 | \t}\n 405 | }","lines":23,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/fty_enum.c#L383-L405"}]},
{"id":"OVF_9","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"high","difficulty":"medium","description":"In m_req_name.c menu_request_by_name(), strncpy(buf, str, sizeof(buf)) copies up to 16 bytes but does NOT guarantee NUL-termination when the input string is exactly 16 bytes or longer (strncpy pads with NUL only up to sizeof(buf) but if source length >= sizeof(buf) the destination has no NUL). The subsequent while loop checks `buf[i] != '\\0'` but `i` is bounded by `sizeof(buf)`, so it won't overflow buf itself. However, the final strncmp on line 118 compares `request_names[i]` against `buf` using sizeof(buf)=16 as the count. If str has exactly 16 chars with no NUL at position 15, the comparison reads into uninitialized or next stack memory because `buf` is a 16-byte array and strncmp is asked to compare 16 bytes including the missing NUL. This means a 16+ char input can match a shorter request name spuriously if stack bytes happen to match.","fix":"Declare buf[17] and use strncpy(buf, str, 16); buf[16] = '\\0'; to always NUL-terminate. Alternatively use strlcpy if available.","locations":[{"ref":"payloads/libpayload/curses/menu/m_req_name.c:103-122","code":" 103 | char buf[16];\n 104 | \n 105 | T((T_CALLED(\"menu_request_by_name(%s)\"), _nc_visbuf(str)));\n 106 | \n 107 | if (str)\n 108 | {\n 109 | strncpy(buf, str, sizeof(buf));\n 110 | while ((i < sizeof(buf)) && (buf[i] != '\\0'))\n 111 | \t{\n 112 | \t buf[i] = (char)toupper(UChar(buf[i]));\n 113 | \t i++;\n 114 | \t}\n 115 | \n 116 | for (i = 0; i < A_SIZE; i++)\n 117 | \t{\n 118 | \t if (strncmp(request_names[i], buf, sizeof(buf)) == 0)\n 119 | \t returnCode(MIN_MENU_COMMAND + (int)i);\n 120 | \t}\n 121 | }\n 122 | RETURN(E_NO_MATCH);","lines":20,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/menu/m_req_name.c#L103-L122"}]},
{"id":"OVF_10","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"high","difficulty":"medium","description":"In tinycurses.c newwin(), window_count, ldat_count, and linebuf_count are each checked or incremented independently but only window_count is bounds-checked (line 394). ldat_count and linebuf_count can individually overflow their respective static arrays if called with unusual window sizes (e.g., a window with more lines than SCREEN_Y). ldat_list is sized [MAX_WINDOWS][SCREEN_Y] but ldat_count indexes it as a flat counter and is incremented by 1 per window regardless of num_lines. linebuf_list is sized [SCREEN_Y * MAX_WINDOWS][SCREEN_X + 1] but linebuf_count is incremented by num_lines per window; with 3 windows of 25 lines each that is exactly 75 which fits, but the check is absent. Any window taller than SCREEN_Y would overflow linebuf_list even if window_count < MAX_WINDOWS.","fix":"Add bounds checks: verify `ldat_count < MAX_WINDOWS` and `linebuf_count + num_lines <= SCREEN_Y * MAX_WINDOWS` before proceeding, and return NULL on overflow.","locations":[{"ref":"payloads/libpayload/curses/tinycurses.c:385-421","code":" 385 | WINDOW *newwin(int num_lines, int num_columns, int begy, int begx)\n 386 | {\n 387 | \tWINDOW *win;\n 388 | \tint i;\n 389 | \n 390 | \t/* Use next statically allocated window. */\n 391 | \t// TODO: Error handling. Yes. Please.\n 392 | \t// TODO: WINDOWLIST?\n 393 | \n 394 | \tif (window_count >= MAX_WINDOWS)\n 395 | \t\treturn NULL;\n 396 | \n 397 | \twin = &window_list[window_count++];\n 398 | \n 399 | \t// bool is_pad = (flags & _ISPAD);\n 400 | \n 401 | \t// TODO: Checks.\n 402 | \n 403 | \twin->_cury = 0;\n 404 | \twin->_curx = 0;\n 405 | \twin->_maxy = num_lines - 1;\n 406 | \twin->_maxx = num_columns - 1;\n 407 | \twin->_begy = begy;\n 408 | \twin->_begx = begx;\n 409 | \t// win->_yoffset = SP->_topstolen;\n 410 | \n 411 | \twin->_line = ldat_list[ldat_count++];\n 412 | \n 413 | \t/* FIXME: Is this right? Should the window attributes be normal? */\n 414 | \twin->_color = PAIR_NUMBER(0);\n 415 | \twin->_attrs = A_NORMAL;\n 416 | \n 417 | \tfor (i = 0; i < num_lines; i++)\n 418 | \t\twin->_line[i].text =\n 419 | \t\t (NCURSES_CH_T *)&linebuf_list[linebuf_count++];\n 420 | \n 421 | \treturn win;","lines":37,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/tinycurses.c#L385-L421"}]},
{"id":"OVF_11","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"high","difficulty":"very_easy","description":"In get_cmos_value() and set_cmos_value(), the len parameter is u32 and the while loop does len -= 8. When len is not a multiple of 8 and greater than 8 (e.g., len=10), the first subtraction leaves len=2, then the second iteration subtracts 8 from an unsigned 2 to produce 0xFFFFFFFE. The loop condition (len > 0) is now true for ~536 million iterations, causing unbounded reads/writes to the value buffer and arbitrary NVRAM addresses.","fix":"Replace the while condition with a signed check, or only enter the byte-copy loop when len is known to be a multiple of 8. A safe fix: change the loop to iterate a fixed count: for (int bytes = len / 8; bytes > 0; bytes--) { value[offs++] = nvram->read(addr++); }. Also handle any remaining bits (len % 8) with a partial read/mask.","locations":[{"ref":"payloads/libpayload/drivers/options.c:120-123","code":" 120 | \twhile (len > 0) {\n 121 | \t\tlen -= 8;\n 122 | \t\tvalue[offs++]=nvram->read(addr++);\n 123 | \t}","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/options.c#L120-L123"},{"ref":"payloads/libpayload/drivers/options.c:149-152","code":" 149 | \twhile (len > 0) {\n 150 | \t\tlen -= 8;\n 151 | \t\tnvram->write(value[offs++], addr++);\n 152 | \t}","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/options.c#L149-L152"}]},
{"id":"OVF_12","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"high","difficulty":"medium","description":"In `spd_decode_ddr2()`, when the SPD advertises no CAS support (`spd[18]==0`, i.e. `dimm->cas_supported==0`), the code only warns and sets `ret = SPD_STATUS_INVALID_FIELD` (line 422) without returning. It then computes `cl = spd_get_msbs(dimm->cas_supported)` which calls `log2(0)`. Per `src/include/lib.h` (`log2(0) == -1`), `spd_get_msbs` returns `-1` truncated to `u8` = `255`. `cl` is then used to index `dimm->cycle_time[cl]` and `dimm->access_time[cl]` (both `u32[8]` per `ddr2.h`), and also `dimm->cycle_time[cl - 1]`, `dimm->cycle_time[cl - 2]`, giving indices `255`, `254`, `253`. These are massively out-of-bounds writes into (and far past) the `dimm_attr_ddr2_st` structure. The SPD checksum at `spd[63]` is attacker-controllable (a plain byte sum), so a crafted/corrupt SPD EEPROM with a matching checksum but `spd[18]==0` reaches this path and corrupts memory. Shifts such as `1 << (cl - 1)` (`1 << 254`) are additionally undefined behavior.","fix":"After detecting invalid CAS support, bail out instead of continuing. For example, change the check at line 422 to return early:\n```c\nif ((dimm->cas_supported & 0x3) || !dimm->cas_supported) {\n\tprintk(BIOS_WARNING, \"SPD decode: invalid CAS support advertised.\\n\");\n\tdimm->dram_type = SPD_MEMORY_TYPE_UNDEFINED;\n\treturn SPD_STATUS_INVALID_FIELD;\n}\n```\nAlternatively, guard the `cl` computation/uses so `cl` (and `cl-1`, `cl-2`) are validated to be within `[2, 7]` before indexing `cycle_time[]`/`access_time[]`.","locations":[{"ref":"src/device/dram/ddr2.c:421-473","code":" 421 | \tdimm->cas_supported = spd[18];\n 422 | \tif ((dimm->cas_supported & 0x3) || !dimm->cas_supported) {\n 423 | \t\tprintk(BIOS_WARNING, \"SPD decode: invalid CAS support advertised.\\n\");\n 424 | \t\tret = SPD_STATUS_INVALID_FIELD;\n 425 | \t}\n 426 | \tprintram(\" Supported CAS mask : 0x%x\\n\", dimm->cas_supported);\n 427 | \n 428 | \tif ((dimm->rev < 0x13) && (dimm->cas_supported & 0x80)) {\n 429 | \t\tprintk(BIOS_WARNING, \"SPD decode: invalid CAS support advertised.\\n\");\n 430 | \t\tret = SPD_STATUS_INVALID_FIELD;\n 431 | \t}\n 432 | \tif ((dimm->rev < 0x12) && (dimm->cas_supported & 0x40)) {\n 433 | \t\tprintk(BIOS_WARNING, \"SPD decode: invalid CAS support advertised.\\n\");\n 434 | \t\tret = SPD_STATUS_INVALID_FIELD;\n 435 | \t}\n 436 | \n 437 | \t/* CL=X */\n 438 | \tcl = spd_get_msbs(dimm->cas_supported);\n 439 | \n 440 | \t/* SDRAM Cycle time at Maximum Supported CAS Latency (CL), CL=X */\n 441 | \tif (spd_decode_tck_time(&dimm->cycle_time[cl], spd[9]) != CB_SUCCESS) {\n 442 | \t\tprintk(BIOS_WARNING, \"SPD decode: invalid min tCL for CAS%d\\n\", cl);\n 443 | \t\tret = SPD_STATUS_INVALID_FIELD;\n 444 | \t}\n 445 | \t/* SDRAM Access from Clock */\n 446 | \tif (spd_decode_bcd_time(&dimm->access_time[cl], spd[10]) != CB_SUCCESS) {\n 447 | \t\tprintk(BIOS_WARNING, \"SPD decode: invalid min tAC for CAS%d\\n\", cl);\n 448 | \t\tret = SPD_STATUS_INVALID_FIELD;\n 449 | \t}\n 450 | \n 451 | \tif (dimm->cas_supported & (1 << (cl - 1))) {\n 452 | \t\t/* Minimum Clock Cycle at CLX-1 */\n 453 | \t\tif (spd_decode_tck_time(&dimm->cycle_time[cl - 1], spd[23]) != CB_SUCCESS) {\n 454 | \t\t\tprintk(BIOS_WARNING, \"SPD decode: invalid min tCL for CAS%d\\n\", cl - 1);\n 455 | \t\t\tret = SPD_STATUS_INVALID_FIELD;\n 456 | \t\t}\n 457 | \t\t/* Maximum Data Access Time (tAC) from Clock at CLX-1 */\n 458 | \t\tif (spd_decode_bcd_time(&dimm->access_time[cl - 1], spd[24]) != CB_SUCCESS) {\n 459 | \t\t\tprintk(BIOS_WARNING, \"SPD decode: invalid min tAC for CAS%d\\n\", cl - 1);\n 460 | \t\t\tret = SPD_STATUS_INVALID_FIELD;\n 461 | \t\t}\n 462 | \t}\n 463 | \tif (dimm->cas_supported & (1 << (cl - 2))) {\n 464 | \t\t/* Minimum Clock Cycle at CLX-2 */\n 465 | \t\tif (spd_decode_tck_time(&dimm->cycle_time[cl - 2], spd[25]) != CB_SUCCESS) {\n 466 | \t\t\tprintk(BIOS_WARNING, \"SPD decode: invalid min tCL for CAS%d\\n\", cl - 2);\n 467 | \t\t\tret = SPD_STATUS_INVALID_FIELD;\n 468 | \t\t}\n 469 | \t\t/* Maximum Data Access Time (tAC) from Clock at CLX-2 */\n 470 | \t\tif (spd_decode_bcd_time(&dimm->access_time[cl - 2], spd[26]) != CB_SUCCESS) {\n 471 | \t\t\tprintk(BIOS_WARNING, \"SPD decode: invalid min tAC for CAS%d\\n\", cl - 2);\n 472 | \t\t\tret = SPD_STATUS_INVALID_FIELD;\n 473 | \t\t}","lines":53,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/device/dram/ddr2.c#L421-L473"}]},
{"id":"OVF_13","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"medium","difficulty":"very_easy","description":"In SHA1Update(), the expression (len << 3) is computed using the type of len, which is size_t. On 32-bit targets size_t is 32 bits, so the shift overflows to zero (or wraps) when len >= 0x20000000 (512 MiB). The overflowed value is then added to context->count (u64), silently producing a wrong bit-count and therefore a wrong digest for inputs larger than 512 MiB on 32-bit builds.","fix":"Cast len to u64 before shifting: context->count += ((u64)len << 3);","locations":[{"ref":"payloads/libpayload/crypto/sha1.c:143","code":" 143 | \tcontext->count += (len << 3);","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/crypto/sha1.c#L143"}]},
{"id":"OVF_14","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"medium","difficulty":"medium","description":"In tui.c, padstr() uses a static char buf[MAXSTRLEN] (256 bytes). The format string produced is '%-<length>s', where length is the caller-supplied field width. When length (which equals COLS or COLS-3) exceeds 255, the sprintf() call writes more than 256 bytes into buf, overflowing the static buffer. COLS > 255 is common on modern or wide terminals.","fix":"Either allocate buf dynamically (malloc(length + 1)) or increase MAXSTRLEN to a safe value and enforce the limit before calling sprintf.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/demos/tui.c:71-79","code":" 71 | {\n 72 | static char buf[MAXSTRLEN];\n 73 | char fmt[10];\n 74 | \n 75 | sprintf(fmt, (int)strlen(s) > length ? \"%%.%ds\" : \"%%-%ds\", length);\n 76 | sprintf(buf, fmt, s);\n 77 | \n 78 | return buf;\n 79 | }","lines":9,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/demos/tui.c#L71-L79"}]},
{"id":"OVF_15","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"medium","difficulty":"medium","description":"vwprintw() uses a 513-byte local buffer 'printbuf' and, when HAVE_VSNPRINTF is not defined, falls back to vsprintf() with no length limit. vsprintf() can write arbitrarily large output into printbuf, overflowing the buffer if the formatted string exceeds 512 characters. The vsnprintf() path is safe (limit of 512 bytes including null fits in 513 bytes), but the fallback path has no guard.","fix":"Replace the vsprintf fallback with a size-limited alternative. If vsnprintf is not available, either implement a bounded sprintf wrapper or increase printbuf and truncate. At minimum add a compile-time assertion that HAVE_VSNPRINTF is defined for safety.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/printw.c:43-53","code":" 43 | char printbuf[513];\n 44 | int len;\n 45 | \n 46 | PDC_LOG((\"vwprintw() - called\\n\"));\n 47 | \n 48 | #ifdef HAVE_VSNPRINTF\n 49 | len = vsnprintf(printbuf, 512, fmt, varglist);\n 50 | #else\n 51 | len = vsprintf(printbuf, fmt, varglist);\n 52 | #endif\n 53 | return (waddstr(win, printbuf) == ERR) ? ERR : len;","lines":11,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/printw.c#L43-L53"}]},
{"id":"OVF_16","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"medium","difficulty":"medium","description":"is_linetouched() accepts line == win->_maxy due to an off-by-one in the guard condition. The check is 'if (!win || line > win->_maxy || line < 0) return FALSE'. When line == win->_maxy, the check passes, and the code accesses win->_firstch[win->_maxy], which is one past the end of the win->_maxy-element array. The arrays _firstch and _lastch are allocated with exactly nlines (== _maxy) elements, so index _maxy is always out of bounds. The same off-by-one exists in touchline() and wtouchln() at their guard conditions (line 81, line 118), though for those functions, start == _maxy or y == _maxy simply causes the subsequent loop to run zero iterations, which is harmless.","fix":"Change the condition to 'line >= win->_maxy' (strict greater-than-or-equal): 'if (!win || line >= win->_maxy || line < 0) return FALSE;'","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/touch.c:138-146","code":" 138 | bool is_linetouched(WINDOW *win, int line)\n 139 | {\n 140 | PDC_LOG((\"is_linetouched() - called: win=%p line=%d\\n\", win, line));\n 141 | \n 142 | if (!win || line > win->_maxy || line < 0)\n 143 | return FALSE;\n 144 | \n 145 | return (win->_firstch[line] != _NO_CHANGE) ? TRUE : FALSE;\n 146 | }","lines":9,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/touch.c#L138-L146"}]},
{"id":"OVF_17","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"medium","difficulty":"medium","description":"PDC_mbstowcs() (PDC_FORCE_UTF8 path) writes a null terminator one element past the end of the destination buffer when the input exactly fills the buffer. The loop 'while (*src && i < n)' exits when i equals n (after the last increment). The line 'dest[i] = 0' then writes to dest[n], which is one past the n-element buffer. Callers such as slk_set() pass a fixed-size wchar_t array with n as its character capacity, so this off-by-one reliably writes one wchar_t beyond the array.","fix":"Cap i before writing the terminator: the loop should run while (*src && i < n - 1) to leave room for the null, or add 'if (i > n) i = n; dest[i] = 0;' after the loop (where dest has n+1 elements). Better still, allocate and document dest as having n+1 elements and keep the existing logic.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/util.c:253-268","code":" 253 | while (*src && i < n)\n 254 | {\n 255 | int retval = PDC_mbtowc(dest + i, src, len);\n 256 | \n 257 | if (retval < 1)\n 258 | return -1;\n 259 | \n 260 | src += retval;\n 261 | len -= retval;\n 262 | i++;\n 263 | }\n 264 | # else\n 265 | size_t i = mbstowcs(dest, src, n);\n 266 | # endif\n 267 | dest[i] = 0;\n 268 | return i;","lines":16,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/util.c#L253-L268"}]},
{"id":"OVF_18","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"medium","difficulty":"medium","description":"`Total_Buffer_Size()` in form.priv.h computes:\n (Buffer_Length(field) + 1) * (1 + field->nbuf) * sizeof(FIELD_CELL)\nwhere Buffer_Length = drows * dcols. Both `drows` and `dcols` are `int` (set\ndirectly from caller-supplied `rows+nrow` and `cols` in `new_field()`). For\nlarge values the intermediate multiplication `drows * dcols` overflows a signed\nint (UB), and the subsequent multiplication with `(1 + nbuf) * sizeof(FIELD_CELL)`\ncan overflow `size_t`, producing a truncated length passed to `malloc()`. The\nresulting undersized buffer is then written past its end during the buffer-fill\nloop in `new_field()`.","fix":"Perform the size calculation in `size_t` throughout and check for overflow before\ncalling malloc. Example:\n size_t buf_len = (size_t)field->drows * (size_t)field->dcols;\n size_t total = (buf_len + 1) * (size_t)(1 + field->nbuf) * sizeof(FIELD_CELL);\n if (buf_len == 0 || total / (buf_len + 1) != (size_t)(1 + field->nbuf) * sizeof(FIELD_CELL))\n /* overflow */;\nAlso add upper-bound validation of `rows`, `cols`, `nrow`, and `nbuf` in\n`new_field()`.","locations":[{"ref":"payloads/libpayload/curses/form/form.priv.h:120-124","code":" 120 | #define Buffer_Length(field) ((field)->drows * (field)->dcols)\n 121 | \n 122 | /* Calculate the total size of all buffers for this field */\n 123 | #define Total_Buffer_Size(field) \\\n 124 | ( (Buffer_Length(field) + 1) * (1+(field)->nbuf) * sizeof(FIELD_CELL) )","lines":5,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/form.priv.h#L120-L124"},{"ref":"payloads/libpayload/curses/form/fld_def.c:285-333","code":" 285 | if (rows > 0 &&\n 286 | cols > 0 &&\n 287 | frow >= 0 &&\n 288 | fcol >= 0 &&\n 289 | nrow >= 0 &&\n 290 | nbuf >= 0 &&\n 291 | ((err = E_SYSTEM_ERROR) != 0) &&\t/* trick: this resets the default error */\n 292 | (New_Field = typeMalloc(FIELD, 1)) != 0)\n 293 | {\n 294 | T((T_CREATE(\"field %p\"), (void *)New_Field));\n 295 | *New_Field = default_field;\n 296 | New_Field->rows = rows;\n 297 | New_Field->cols = cols;\n 298 | New_Field->drows = rows + nrow;\n 299 | New_Field->dcols = cols;\n 300 | New_Field->frow = frow;\n 301 | New_Field->fcol = fcol;\n 302 | New_Field->nrow = nrow;\n 303 | New_Field->nbuf = nbuf;\n 304 | New_Field->link = New_Field;\n 305 | \n 306 | #if USE_WIDEC_SUPPORT\n 307 | New_Field->working = newpad(1, Buffer_Length(New_Field) + 1);\n 308 | New_Field->expanded = typeCalloc(char *, 1 + (unsigned)nbuf);\n 309 | #endif\n 310 | \n 311 | if (_nc_Copy_Type(New_Field, &default_field))\n 312 | \t{\n 313 | \t size_t len;\n 314 | \n 315 | \t len = Total_Buffer_Size(New_Field);\n 316 | \t if ((New_Field->buf = (FIELD_CELL *)malloc(len)))\n 317 | \t {\n 318 | \t /* Prefill buffers with blanks and insert terminating zeroes\n 319 | \t between buffers */\n 320 | \t int i, j;\n 321 | \t int cells = Buffer_Length(New_Field);\n 322 | \n 323 | \t for (i = 0; i <= New_Field->nbuf; i++)\n 324 | \t\t{\n 325 | \t\t FIELD_CELL *buffer = &(New_Field->buf[(cells + 1) * i]);\n 326 | \n 327 | \t\t for (j = 0; j < cells; ++j)\n 328 | \t\t {\n 329 | \t\t buffer[j] = blank;\n 330 | \t\t }\n 331 | \t\t buffer[j] = zeros;\n 332 | \t\t}\n 333 | \t returnField(New_Field);","lines":49,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/fld_def.c#L285-L333"}]},
{"id":"OVF_19","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"medium","difficulty":"medium","description":"In Check_This_Field for TYPE_INTEGER (fty_int.c), the validated value is formatted\nwith sprintf(buf, \"%.*ld\", (prec > 0 ? prec : 0), val) into a 100-byte stack buffer.\nWhen 'prec' is large (e.g. caller passes 200), the formatted output can far exceed\n100 bytes, overflowing the stack buffer 'buf'. There is no upper bound check on\nprec before the sprintf call.","fix":"Clamp precision before formatting:\n int safe_prec = (prec > 0 && prec < 90) ? prec : 0;\n sprintf(buf, \"%.*ld\", safe_prec, val);\nOr use snprintf with a length limit and verify the result fits.","locations":[{"ref":"payloads/libpayload/curses/form/fty_int.c:172-237","code":" 172 | char buf[100];\n 173 | bool result = FALSE;\n 174 | \n 175 | while (*bp && *bp == ' ')\n 176 | bp++;\n 177 | if (*bp)\n 178 | {\n 179 | if (*bp == '-')\n 180 | \tbp++;\n 181 | #if USE_WIDEC_SUPPORT\n 182 | if (*bp)\n 183 | \t{\n 184 | \t bool blank = FALSE;\n 185 | \t int len;\n 186 | \t int n;\n 187 | \t wchar_t *list = _nc_Widen_String((char *)bp, &len);\n 188 | \n 189 | \t if (list != 0)\n 190 | \t {\n 191 | \t result = TRUE;\n 192 | \t for (n = 0; n < len; ++n)\n 193 | \t\t{\n 194 | \t\t if (blank)\n 195 | \t\t {\n 196 | \t\t if (list[n] != ' ')\n 197 | \t\t\t{\n 198 | \t\t\t result = FALSE;\n 199 | \t\t\t break;\n 200 | \t\t\t}\n 201 | \t\t }\n 202 | \t\t else if (list[n] == ' ')\n 203 | \t\t {\n 204 | \t\t blank = TRUE;\n 205 | \t\t }\n 206 | \t\t else if (!isDigit(list[n]))\n 207 | \t\t {\n 208 | \t\t result = FALSE;\n 209 | \t\t break;\n 210 | \t\t }\n 211 | \t\t}\n 212 | \t free(list);\n 213 | \t }\n 214 | \t}\n 215 | #else\n 216 | while (*bp)\n 217 | \t{\n 218 | \t if (!isdigit(UChar(*bp)))\n 219 | \t break;\n 220 | \t bp++;\n 221 | \t}\n 222 | while (*bp && *bp == ' ')\n 223 | \tbp++;\n 224 | result = (*bp == '\\0');\n 225 | #endif\n 226 | if (result)\n 227 | \t{\n 228 | \t val = atol(s);\n 229 | \t if (low < high)\n 230 | \t {\n 231 | \t if (val < low || val > high)\n 232 | \t\tresult = FALSE;\n 233 | \t }\n 234 | \t if (result)\n 235 | \t {\n 236 | \t sprintf(buf, \"%.*ld\", (prec > 0 ? prec : 0), val);\n 237 | \t set_field_buffer(field, 0, buf);","lines":66,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/fty_int.c#L172-L237"}]},
{"id":"OVF_20","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"medium","difficulty":"medium","description":"In Check_This_Field for TYPE_NUMERIC (fty_num.c), the validated value is formatted\nwith sprintf(buf, \"%.*f\", (prec > 0 ? prec : 0), val) into a 64-byte stack buffer.\nA double can have up to ~308 integer digits plus 'prec' decimal digits. When prec\nis large or val is large, the formatted output can overflow the 64-byte buffer.","fix":"Use snprintf with the buffer size and validate the return:\n int n = snprintf(buf, sizeof(buf), \"%.*f\", (prec > 0 ? prec : 0), val);\n if (n >= (int)sizeof(buf)) result = FALSE;\nAlternatively increase the buffer to at least 400 bytes or clamp precision.","locations":[{"ref":"payloads/libpayload/curses/form/fty_num.c:181-275","code":" 181 | Check_This_Field(FIELD *field, const void *argp)\n 182 | {\n 183 | const thisARG *argn = (const thisARG *)argp;\n 184 | double low = argn->low;\n 185 | double high = argn->high;\n 186 | int prec = argn->precision;\n 187 | unsigned char *bp = (unsigned char *)field_buffer(field, 0);\n 188 | char *s = (char *)bp;\n 189 | double val = 0.0;\n 190 | struct lconv *L = argn->L;\n 191 | char buf[64];\n 192 | bool result = FALSE;\n 193 | \n 194 | while (*bp && *bp == ' ')\n 195 | bp++;\n 196 | if (*bp)\n 197 | {\n 198 | if (*bp == '-' || *bp == '+')\n 199 | \tbp++;\n 200 | #if USE_WIDEC_SUPPORT\n 201 | if (*bp)\n 202 | \t{\n 203 | \t bool blank = FALSE;\n 204 | \t int state = 0;\n 205 | \t int len;\n 206 | \t int n;\n 207 | \t wchar_t *list = _nc_Widen_String((char *)bp, &len);\n 208 | \n 209 | \t if (list != 0)\n 210 | \t {\n 211 | \t result = TRUE;\n 212 | \t for (n = 0; n < len; ++n)\n 213 | \t\t{\n 214 | \t\t if (blank)\n 215 | \t\t {\n 216 | \t\t if (list[n] != ' ')\n 217 | \t\t\t{\n 218 | \t\t\t result = FALSE;\n 219 | \t\t\t break;\n 220 | \t\t\t}\n 221 | \t\t }\n 222 | \t\t else if (list[n] == ' ')\n 223 | \t\t {\n 224 | \t\t blank = TRUE;\n 225 | \t\t }\n 226 | \t\t else if (isDecimalPoint(list[n]))\n 227 | \t\t {\n 228 | \t\t if (++state > 1)\n 229 | \t\t\t{\n 230 | \t\t\t result = FALSE;\n 231 | \t\t\t break;\n 232 | \t\t\t}\n 233 | \t\t }\n 234 | \t\t else if (!isDigit(list[n]))\n 235 | \t\t {\n 236 | \t\t result = FALSE;\n 237 | \t\t break;\n 238 | \t\t }\n 239 | \t\t}\n 240 | \t free(list);\n 241 | \t }\n 242 | \t}\n 243 | #else\n 244 | while (*bp)\n 245 | \t{\n 246 | \t if (!isdigit(UChar(*bp)))\n 247 | \t break;\n 248 | \t bp++;\n 249 | \t}\n 250 | if (isDecimalPoint(*bp))\n 251 | \t{\n 252 | \t bp++;\n 253 | \t while (*bp)\n 254 | \t {\n 255 | \t if (!isdigit(UChar(*bp)))\n 256 | \t\tbreak;\n 257 | \t bp++;\n 258 | \t }\n 259 | \t}\n 260 | while (*bp && *bp == ' ')\n 261 | \tbp++;\n 262 | result = (*bp == '\\0');\n 263 | #endif\n 264 | if (result)\n 265 | \t{\n 266 | \t val = atof(s);\n 267 | \t if (low < high)\n 268 | \t {\n 269 | \t if (val < low || val > high)\n 270 | \t\tresult = FALSE;\n 271 | \t }\n 272 | \t if (result)\n 273 | \t {\n 274 | \t sprintf(buf, \"%.*f\", (prec > 0 ? prec : 0), val);\n 275 | \t set_field_buffer(field, 0, buf);","lines":95,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/fty_num.c#L181-L275"}]},
{"id":"OVF_21","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"medium","difficulty":"medium","description":"In tinycurses.c waddch() and waddnstr(), win->_curx is incremented without checking against win->_maxx. Writing past _maxx writes into the next row's text buffer (or beyond) in the statically allocated linebuf_list, causing silent memory corruption. The comment `// FIXME` at lines 524 and 554 acknowledges this.","fix":"After incrementing win->_curx, check `if (win->_curx > win->_maxx)` and either stop writing or wrap to the next row (with corresponding _cury increment and _maxy check).","locations":[{"ref":"payloads/libpayload/curses/tinycurses.c:503-532","code":" 503 | int waddch(WINDOW *win, const chtype ch)\n 504 | {\n 505 | \tint code = ERR;\n 506 | \t// NCURSES_CH_T wch;\n 507 | \t// SetChar2(wch, ch);\n 508 | \n 509 | \tif (win->_line[win->_cury].firstchar == _NOCHANGE ||\n 510 | \t\t\twin->_line[win->_cury].firstchar > win->_curx)\n 511 | \t\twin->_line[win->_cury].firstchar = win->_curx;\n 512 | \n 513 | \twin->_line[win->_cury].text[win->_curx].chars[0] =\n 514 | \t\t((ch) & (chtype)A_CHARTEXT);\n 515 | \n 516 | \twin->_line[win->_cury].text[win->_curx].attr = WINDOW_ATTRS(win);\n 517 | \twin->_line[win->_cury].text[win->_curx].attr |=\n 518 | \t\t((ch) & (chtype)A_ATTRIBUTES);\n 519 | \n 520 | \tif (win->_line[win->_cury].lastchar == _NOCHANGE ||\n 521 | \t\t\twin->_line[win->_cury].lastchar < win->_curx)\n 522 | \t\twin->_line[win->_cury].lastchar = win->_curx;\n 523 | \n 524 | \twin->_curx++;\t// FIXME\n 525 | \n 526 | \t// if (win && (waddch_nosync(win, wch) != ERR)) {\n 527 | \t// \t_nc_synchook(win);\n 528 | \t// \tcode = OK;\n 529 | \t// }\n 530 | \n 531 | \treturn code;\n 532 | }","lines":30,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/tinycurses.c#L503-L532"},{"ref":"payloads/libpayload/curses/tinycurses.c:534-568","code":" 534 | int waddnstr(WINDOW *win, const char *astr, int n)\n 535 | {\n 536 | \tint code = OK;\n 537 | \tconst char *str = astr;\n 538 | \n 539 | \tif (!str)\n 540 | \t\treturn ERR;\n 541 | \n 542 | \tif (n < 0)\n 543 | \t\tn = strlen(astr);\n 544 | \n 545 | \tif (win->_line[win->_cury].firstchar == _NOCHANGE ||\n 546 | \t\t\twin->_line[win->_cury].firstchar > win->_curx)\n 547 | \t\twin->_line[win->_cury].firstchar = win->_curx;\n 548 | \n 549 | \twhile ((n-- > 0) && (*str != '\\0')) {\n 550 | \t// while (*str != '\\0') {\n 551 | \t\twin->_line[win->_cury].text[win->_curx].chars[0] = *str++;\n 552 | \t\twin->_line[win->_cury].text[win->_curx].attr = WINDOW_ATTRS(win)\n 553 | ;\n 554 | \t\twin->_curx++;\t// FIXME\n 555 | \n 556 | \t\t// NCURSES_CH_T ch;\n 557 | \t\t// SetChar(ch, UChar(*str++), A_NORMAL);\n 558 | \t\t// if (_nc_waddch_nosync(win, ch) == ERR) {\n 559 | \t\t// \tcode = ERR;\n 560 | \t\t// \tbreak;\n 561 | \t\t// }\n 562 | \t}\n 563 | \n 564 | \tif (win->_line[win->_cury].lastchar == _NOCHANGE ||\n 565 | \t\t\twin->_line[win->_cury].lastchar < win->_curx)\n 566 | \t\twin->_line[win->_cury].lastchar = win->_curx;\n 567 | \n 568 | \treturn code;","lines":35,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/tinycurses.c#L534-L568"}]},
{"id":"OVF_22","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"medium","difficulty":"medium","description":"In mouse_cursor.c mouse_cursor_poll(), mouse_rel_x, mouse_rel_y, and mouse_rel_z are int accumulators that are multiplied by mouse_speed (default 0x299 = 665) or 256. With multiple drivers or large raw deltas, these can overflow a 32-bit int. For example, mouse_speed * INT_MAX / 256 overflows long before reaching INT_MAX in the accumulator. No overflow protection is applied.","fix":"Clamp the accumulated values or use int64_t for the intermediates. Alternatively, call mouse_cursor_get_rel() frequently enough that values never accumulate large sums.","locations":[{"ref":"payloads/libpayload/drivers/mouse_cursor.c:89-116","code":" 89 | void mouse_cursor_poll(void)\n 90 | {\n 91 | \tstruct mouse_cursor_input_driver *in;\n 92 | \tint rel_x, rel_y, rel_z;\n 93 | \tu32 buttons;\n 94 | \n 95 | \tmouse_buttons = 0;\n 96 | \t/* Iterate over all connected drivers */\n 97 | \tfor (in = cursor_in; in != NULL; in = in->next)\n 98 | \t\tif (in->get_state) {\n 99 | \t\t\tin->get_state(&rel_x, &rel_y, &rel_z, &buttons);\n 100 | \n 101 | \t\t\t/* Accumulate relative movement */\n 102 | \t\t\tif (mouse_cusor_is_fast(rel_x, rel_y)) {\n 103 | \t\t\t\t/* Apply simple cursor acceleration. Default: x2.6 */\n 104 | \t\t\t\tmouse_rel_x += rel_x * mouse_speed;\n 105 | \t\t\t\tmouse_rel_y += rel_y * mouse_speed;\n 106 | \t\t\t\tmouse_rel_z += rel_z * mouse_speed;\n 107 | \t\t\t} else {\n 108 | \t\t\t\tmouse_rel_x += rel_x * 256;\n 109 | \t\t\t\tmouse_rel_y += rel_y * 256;\n 110 | \t\t\t\tmouse_rel_z += rel_z * 256;\n 111 | \t\t\t}\n 112 | \n 113 | \t\t\t/* Logic or all buttons */\n 114 | \t\t\tmouse_buttons |= buttons;\n 115 | \t\t}\n 116 | }","lines":28,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/mouse_cursor.c#L89-L116"}]},
{"id":"OVF_23","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"medium","difficulty":"medium","description":"`acpigen_write_field_length()` encodes a length into a fixed `uint8_t emit[4]` buffer, but the loop `while (len) { emit[i] = len & 0xFF; i++; len >>= 8; }` can write past index 3. After `emit[0] = len & 0xF; len >>= 4;`, `len` can hold up to 28 significant bits, which requires 4 follow-on bytes (`emit[1]`..`emit[4]`), so `emit[4]` is written one element out of bounds on the stack, and then `for (j = 0; j < i; j++) acpigen_emit_byte(emit[j])` emits 5 bytes (also reading the OOB byte). This triggers when the encoded length is `>= 0x10000000` (2^28). The ACPI PkgLength encoding only supports 28-bit lengths, and `acpigen_write_field_offset()` already guards with `if (diff_bits > 0xFFFFFFF) return;`, but `acpigen_write_field_name()`/`acpigen_write_field_reserved()` call `acpigen_write_field_length()` with an unguarded `size`. It is reachable via `acpigen_write_field()`/`acpigen_write_rom()` (e.g. `FIELDLIST_NAMESTR(\"RBF0\", 8 * length)` for a ROM `>= 32 MiB`) or any caller passing a large `bits` field.","fix":"Add the same 28-bit guard used by `acpigen_write_field_offset()` at the top of `acpigen_write_field_length()`, e.g. `if (len > 0xFFFFFFF) { printk(BIOS_WARNING, \"%s: length too large to encode\\n\", __func__); return; }`, so `emit[]` never needs more than 4 bytes. Alternatively size the buffer `uint8_t emit[4]` correctly and bound the loop to `i < ARRAY_SIZE(emit)`.","locations":[{"ref":"src/acpi/acpigen.c:538-560","code":" 538 | static void acpigen_write_field_length(uint32_t len)\n 539 | {\n 540 | \tuint8_t i, j;\n 541 | \tuint8_t emit[4];\n 542 | \n 543 | \ti = 1;\n 544 | \tif (len < 0x40) {\n 545 | \t\temit[0] = len & 0x3F;\n 546 | \t} else {\n 547 | \t\temit[0] = len & 0xF;\n 548 | \t\tlen >>= 4;\n 549 | \t\twhile (len) {\n 550 | \t\t\temit[i] = len & 0xFF;\n 551 | \t\t\ti++;\n 552 | \t\t\tlen >>= 8;\n 553 | \t\t}\n 554 | \t}\n 555 | \t/* Update bit 7:6 : Number of bytes followed by emit[0] */\n 556 | \temit[0] |= (i - 1) << 6;\n 557 | \n 558 | \tfor (j = 0; j < i; j++)\n 559 | \t\tacpigen_emit_byte(emit[j]);\n 560 | }","lines":23,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/acpi/acpigen.c#L538-L560"},{"ref":"src/acpi/acpigen.c:582-592","code":" 582 | void acpigen_write_field_name(const char *name, uint32_t size)\n 583 | {\n 584 | \tacpigen_emit_simple_namestring(name);\n 585 | \tacpigen_write_field_length(size);\n 586 | }\n 587 | \n 588 | static void acpigen_write_field_reserved(uint32_t size)\n 589 | {\n 590 | \tacpigen_emit_byte(0);\n 591 | \tacpigen_write_field_length(size);\n 592 | }","lines":11,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/acpi/acpigen.c#L582-L592"}]},
{"id":"OVF_24","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"medium","difficulty":"easy","description":"`dt_adjust_phandle()` guards the access with `if (offset + 4 > prop->prop.size)` where both `offset` and `prop->prop.size` are `uint32_t`. When `offset` is close to `UINT32_MAX` (e.g. `0xFFFFFFFD`), `offset + 4` wraps to a small value and passes the check, after which `be32dec(prop->prop.data + offset)` performs a wild out-of-bounds read and `be32enc(prop->prop.data + offset, phandle)` performs an out-of-bounds write. The `offset` is fully controlled by overlay-supplied data: `dt_fixup_locals()` calls `dt_adjust_phandle(base_prop, base, be32dec(fixup_prop->prop.data + i))`, i.e. the offset is read verbatim from the `/__local_fixups__` property contents. The same wrapping pattern exists in `dt_fixup_external()` at the `offset + 4 > ovl_prop->prop.size` check, where `offset` comes from `skip_atoi()` on untrusted overlay text and is then used in `be32enc(ovl_prop->prop.data + offset, phandle)`.","fix":"Compare without adding to the offset so no overflow can occur. In `dt_adjust_phandle()` change `if (offset + 4 > prop->prop.size)` to `if (offset > prop->prop.size - 4 || prop->prop.size < 4)`, and in `dt_fixup_external()` change `offset + 4 > ovl_prop->prop.size` to `ovl_prop->prop.size < 4 || offset > ovl_prop->prop.size - 4`.","locations":[{"ref":"src/commonlib/device_tree.c:1721-1739","code":" 1721 | static uint32_t dt_adjust_phandle(struct device_tree_property *prop,\n 1722 | \t\t\t\t uint32_t adjustment, uint32_t offset)\n 1723 | {\n 1724 | \tif (offset + 4 > prop->prop.size)\n 1725 | \t\treturn 0;\n 1726 | \n 1727 | \tuint32_t phandle = be32dec(prop->prop.data + offset);\n 1728 | \tif (phandle == 0 ||\n 1729 | \t phandle == FDT_PHANDLE_ILLEGAL ||\n 1730 | \t phandle == 0xffffffff)\n 1731 | \t\treturn 0;\n 1732 | \n 1733 | \tphandle += adjustment;\n 1734 | \tif (phandle >= FDT_PHANDLE_ILLEGAL)\n 1735 | \t\treturn 0;\n 1736 | \n 1737 | \tbe32enc(prop->prop.data + offset, phandle);\n 1738 | \treturn phandle;\n 1739 | }","lines":19,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/commonlib/device_tree.c#L1721-L1739"},{"ref":"src/commonlib/device_tree.c:1916-1922","code":" 1916 | \t\t/* Move entry to first char after number, must be a '\\0'. */\n 1917 | \t\tuint32_t offset = skip_atoi(&entry);\n 1918 | \t\tif (!ovl_prop || offset + 4 > ovl_prop->prop.size || entry[0])\n 1919 | \t\t\treturn -1;\n 1920 | \t\tentry++; /* jump over '\\0' to potential next fixup */\n 1921 | \n 1922 | \t\tbe32enc(ovl_prop->prop.data + offset, phandle);","lines":7,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/commonlib/device_tree.c#L1916-L1922"}]},
{"id":"OVF_25","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"medium","difficulty":"very_easy","description":"In `sp_tx_edid_read()`, `edid_pos` is a `uint8_t` but is computed as `(i + count * 8) * MAX_DPCD_BUFFER_SIZE` where `MAX_DPCD_BUFFER_SIZE == 16`. For the `case 2`/`case 3` path (reached whenever the panel reports two or more EDID extension blocks, i.e. `blocks_num >= 2`), the value reaches `(0 + 2*8)*16 = 256` up to `(7 + 3*8)*16 = 496`, all of which overflow the 8-bit `edid_pos` and wrap to `0..240`. The subsequent guard `if (edid_pos <= size - MAX_DPCD_BUFFER_SIZE)` (where `size == FOUR_BLOCK_SIZE == 512`) is therefore always true, and `memcpy(&pedid_blocks_buf[edid_pos], pblock_buf, MAX_DPCD_BUFFER_SIZE)` writes the third/fourth 128-byte EDID blocks over the already-populated first/second blocks instead of at offsets 256..511. The result is a corrupted EDID handed to `decode_edid()`, producing wrong display timings. The sibling `case 0`/`case 1` uses `offset` whose max value 240 fits in `uint8_t`, so only `edid_pos` is affected.","fix":"Widen `edid_pos` so the block offset cannot truncate. Change the declaration:\n```c\n// before\n\tuint8_t offset, edid_pos;\n// after\n\tuint8_t offset;\n\tunsigned int edid_pos;\n```","locations":[{"ref":"src/drivers/analogix/anx7625/anx7625.c:605","code":" 605 | \tuint8_t offset, edid_pos;","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/analogix/anx7625/anx7625.c#L605"},{"ref":"src/drivers/analogix/anx7625/anx7625.c:649-661","code":" 649 | \t\t\tfor (i = 0; i < 8; i++) {\n 650 | \t\t\t\tedid_pos = (i + count * 8) *\n 651 | \t\t\t\t\tMAX_DPCD_BUFFER_SIZE;\n 652 | \n 653 | \t\t\t\tif (g_edid_break)\n 654 | \t\t\t\t\tbreak;\n 655 | \n 656 | \t\t\t\tsegments_edid_read(bus, count / 2,\n 657 | \t\t\t\t\t\t pblock_buf, offset);\n 658 | \t\t\t\tif (edid_pos <= size - MAX_DPCD_BUFFER_SIZE)\n 659 | \t\t\t\t\tmemcpy(&pedid_blocks_buf[edid_pos],\n 660 | \t\t\t\t\t pblock_buf,\n 661 | \t\t\t\t\t MAX_DPCD_BUFFER_SIZE);","lines":13,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/analogix/anx7625/anx7625.c#L649-L661"}]},
{"id":"OVF_26","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"medium","difficulty":"easy","description":"In `crb_tpm_process_command()` the response length is read from the TPM-controlled response header (`length = be32_to_cpu(read32(control_area.response_bfr + 2))`) and only checked for the lower bound (`if (length < 6) return 1`). It is never bounded by `max_response` (the caller's `recvbuf` size) before `memcpy(tpm2_response, control_area.response_bfr, length)`. The earlier check `if (control_area.response_size < max_response)` only guarantees the hardware buffer is at least `max_response`; it does not constrain `length`. A TPM that reports `length > max_response` (up to `response_size`) overflows the caller's response buffer with hardware-controlled data.","fix":"Reject/clamp the reported length against `max_response` before copying, e.g. `if (length > max_response) { printk(BIOS_ERR, \"TPM: response too large\\n\"); return -1; }` prior to the `memcpy`.","locations":[{"ref":"src/drivers/crb/tpm.c:289-297","code":" 289 | \t// Get Response Length\n 290 | \tuint32_t length = be32_to_cpu(read32(control_area.response_bfr + 2));\n 291 | \n 292 | \t/* Response has to have at least 6 bytes */\n 293 | \tif (length < 6)\n 294 | \t\treturn 1;\n 295 | \n 296 | \t// Copy Response\n 297 | \tmemcpy(tpm2_response, control_area.response_bfr, length);","lines":9,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/crb/tpm.c#L289-L297"}]},
{"id":"OVF_27","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"medium","difficulty":"very_easy","description":"In `tpm_tis_i2c_recv()` the response length `expected` is read from the TPM response header and only checked against an upper bound (`(size_t)expected > count`). It is never checked against the lower bound `TPM_HEADER_SIZE`. If a malformed/faulty TPM returns a size field `< 10`, the expression `expected - TPM_HEADER_SIZE` (both operands promote to `uint32_t`/`size_t`) underflows to a huge value that is passed as the `count` argument to `recv_data()`, which then loops reading TPM data into `&buf[TPM_HEADER_SIZE]` far past the caller's buffer, causing an out-of-bounds write in security-sensitive code.","fix":"Add a lower-bound check before the subtraction. Before:\n```\nif ((size_t)expected > count) {\n\tsize = -1;\n\tgoto out;\n}\n```\nAfter:\n```\nif ((size_t)expected > count || expected < TPM_HEADER_SIZE) {\n\tsize = -1;\n\tgoto out;\n}\n```","locations":[{"ref":"src/drivers/i2c/tpm/tpm.c:371-378","code":" 371 | \tmemcpy(&expected, buf + TPM_RSP_SIZE_BYTE, sizeof(expected));\n 372 | \texpected = be32_to_cpu(expected);\n 373 | \tif ((size_t)expected > count) {\n 374 | \t\tsize = -1;\n 375 | \t\tgoto out;\n 376 | \t}\n 377 | \n 378 | \tsize += recv_data(&buf[TPM_HEADER_SIZE], expected - TPM_HEADER_SIZE);","lines":8,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/i2c/tpm/tpm.c#L371-L378"}]},
{"id":"OVF_28","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"low","difficulty":"medium","description":"In dos/pdcclip.c PDC_setclipboard() and dos/pdcclip.c PDC_getclipboard(), the `length`\nparameter is typed as `long`. On platforms where `long` is 32-bit, a caller providing\na length larger than SIZE_MAX/2 would cause `malloc(length + 1)` to integer-overflow to a\nsmall value, then strcpy() would overflow the undersized buffer. Although in practice clipboard\ncontents are unlikely to be gigantic, the parameter type and the lack of a length sanity check\ncreate a latent vulnerability.","fix":"Add a sanity check such as `if (length < 0 || (size_t)length > SIZE_MAX - 1) return PDC_CLIP_MEMORY_ERROR;` before calling malloc. Consider using memcpy with the known length instead of strcpy.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/dos/pdcclip.c:71-90","code":" 71 | int PDC_setclipboard(const char *contents, long length)\n 72 | {\n 73 | PDC_LOG((\"PDC_setclipboard() - called\\n\"));\n 74 | \n 75 | if (pdc_DOS_clipboard)\n 76 | {\n 77 | free(pdc_DOS_clipboard);\n 78 | pdc_DOS_clipboard = NULL;\n 79 | }\n 80 | \n 81 | if (contents)\n 82 | {\n 83 | if ((pdc_DOS_clipboard = malloc(length + 1)) == NULL)\n 84 | return PDC_CLIP_MEMORY_ERROR;\n 85 | \n 86 | strcpy(pdc_DOS_clipboard, contents);\n 87 | }\n 88 | \n 89 | return PDC_CLIP_SUCCESS;\n 90 | }","lines":20,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/dos/pdcclip.c#L71-L90"},{"ref":"payloads/libpayload/curses/PDCurses/os2/pdcclip.c:101-149","code":" 101 | int PDC_setclipboard(const char *contents, long length)\n 102 | {\n 103 | #ifndef EMXVIDEO\n 104 | HAB hab;\n 105 | PTIB ptib;\n 106 | PPIB ppib;\n 107 | ULONG ulRC;\n 108 | PSZ szTextOut = NULL;\n 109 | int rc;\n 110 | #endif\n 111 | PDC_LOG((\"PDC_setclipboard() - called\\n\"));\n 112 | \n 113 | #ifndef EMXVIDEO\n 114 | DosGetInfoBlocks(&ptib, &ppib);\n 115 | ppib->pib_ultype = 3;\n 116 | hab = WinInitialize(0);\n 117 | \n 118 | if (!WinOpenClipbrd(hab))\n 119 | {\n 120 | WinTerminate(hab);\n 121 | return PDC_CLIP_ACCESS_ERROR;\n 122 | }\n 123 | \n 124 | rc = PDC_CLIP_MEMORY_ERROR;\n 125 | \n 126 | ulRC = DosAllocSharedMem((PVOID)&szTextOut, NULL, length + 1,\n 127 | PAG_WRITE | PAG_COMMIT | OBJ_GIVEABLE);\n 128 | \n 129 | if (ulRC == 0)\n 130 | {\n 131 | strcpy(szTextOut, contents);\n 132 | WinEmptyClipbrd(hab);\n 133 | \n 134 | if (WinSetClipbrdData(hab, (ULONG)szTextOut, CF_TEXT, CFI_POINTER))\n 135 | rc = PDC_CLIP_SUCCESS;\n 136 | else\n 137 | {\n 138 | DosFreeMem(szTextOut);\n 139 | rc = PDC_CLIP_ACCESS_ERROR;\n 140 | }\n 141 | }\n 142 | \n 143 | WinCloseClipbrd(hab);\n 144 | WinTerminate(hab);\n 145 | \n 146 | return rc;\n 147 | #else\n 148 | return PDC_CLIP_ACCESS_ERROR;\n 149 | #endif","lines":49,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/os2/pdcclip.c#L101-L149"}]},
{"id":"OVF_29","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"low","difficulty":"medium","description":"In options.c options_checksum_valid() and fix_options_checksum_with(), checksum_location is computed as (u32 cmos_checksum_location) / 8 and stored as int. It is then passed to nvram->read(checksum_location) and nvram->read(checksum_location+1). The nvram_accessor read function takes a u8 parameter, so any byte address above 255 is silently truncated. If coreboot reports a checksum location above byte 255, the wrong bytes are read/written, corrupting the checksum calculation silently.","fix":"Assert or check that checksum_location <= 254 (to allow checksum_location+1 to fit in u8), and return an error if not. Alternatively, extend the nvram_accessor interface to accept u16 addresses.","locations":[{"ref":"payloads/libpayload/drivers/options.c:62-77","code":" 62 | int options_checksum_valid(const struct nvram_accessor *nvram)\n 63 | {\n 64 | \tint i;\n 65 | \tint range_start = lib_sysinfo.cmos_range_start / 8;\n 66 | \tint range_end = lib_sysinfo.cmos_range_end / 8;\n 67 | \tint checksum_location = lib_sysinfo.cmos_checksum_location / 8;\n 68 | \tu16 checksum = 0, checksum_old;\n 69 | \n 70 | \tfor(i = range_start; i <= range_end; i++) {\n 71 | \t\tchecksum += nvram->read(i);\n 72 | \t}\n 73 | \n 74 | \tchecksum_old = ((nvram->read(checksum_location)<<8) | nvram->read(checksum_location+1));\n 75 | \n 76 | \treturn (checksum_old == checksum);\n 77 | }","lines":16,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/options.c#L62-L77"},{"ref":"payloads/libpayload/drivers/options.c:79-93","code":" 79 | void fix_options_checksum_with(const struct nvram_accessor *nvram)\n 80 | {\n 81 | \tint i;\n 82 | \tint range_start = lib_sysinfo.cmos_range_start / 8;\n 83 | \tint range_end = lib_sysinfo.cmos_range_end / 8;\n 84 | \tint checksum_location = lib_sysinfo.cmos_checksum_location / 8;\n 85 | \tu16 checksum = 0;\n 86 | \n 87 | \tfor(i = range_start; i <= range_end; i++) {\n 88 | \t\tchecksum += nvram->read(i);\n 89 | \t}\n 90 | \n 91 | \tnvram->write((checksum >> 8), checksum_location);\n 92 | \tnvram->write((checksum & 0xff), checksum_location + 1);\n 93 | }","lines":15,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/options.c#L79-L93"}]},
{"id":"OVF_30","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"low","difficulty":"medium","description":"In options.c get_cmos_value(), when the single-byte path is taken (len <= 8), the mask (1 << len) - 1 is computed with int arithmetic. If len == 8, 1 << 8 = 256, which fits in int but (1 << 8) - 1 = 0xFF. However if len == 32, 1 << 32 is undefined behavior in C (shift count >= width of type). The function only guards len <= 8 for this path, so len=32 does not reach here, but a future change loosening the guard could introduce UB.","fix":"Use (1u << len) - 1 and add a static_assert or runtime check that len <= 8 before this expression.","locations":[{"ref":"payloads/libpayload/drivers/options.c:112-116","code":" 112 | \tif(len <= 8) {\n 113 | \t\treg8 = nvram->read(addr);\n 114 | \t\treg8 >>= bit;\n 115 | \t\tvalue[0] = reg8 & ((1 << len) -1);\n 116 | \t\treturn 0;","lines":5,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/options.c#L112-L116"}]},
{"id":"OVF_31","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"low","difficulty":"easy","description":"In `soundwire_gen_multilane()` the lane-mapping loop is correctly bounded by `i < prop->lane_mapping_count && i < SOUNDWIRE_MAX_LANE`, but the sibling bus-holder loop iterates `for (i = 0; i < prop->lane_bus_holder_count; i++)` with no `SOUNDWIRE_MAX_LANE` clamp. Both `multilane_bus_holder_names[i]` (8 entries) and `prop->lane_bus_holder[i]` (declared `bool lane_bus_holder[SOUNDWIRE_MAX_LANE]`, i.e. 8) are indexed by `i`, so a devicetree config with `lane_bus_holder_count > SOUNDWIRE_MAX_LANE` causes an out-of-bounds read of both the names table and the config array. Additionally, `map->lane`, `map->connection.master_lane` and `map->connection.slave_link` used at lines 177-181 are not range-checked before indexing the 8-entry name tables.","fix":"Bound the bus-holder loop like the lane-mapping loop: `for (i = 0; i < prop->lane_bus_holder_count && i < SOUNDWIRE_MAX_LANE; i++)`, and defensively clamp/validate `map->lane`, `map->connection.master_lane` and `map->connection.slave_link` against `SOUNDWIRE_MAX_LANE` before using them as array indices.","locations":[{"ref":"src/acpi/soundwire.c:184-188","code":" 184 | \t/* Add bus holder properties. */\n 185 | \tfor (i = 0; i < prop->lane_bus_holder_count; i++)\n 186 | \t\tacpi_dp_add_integer(dsd, multilane_bus_holder_names[i],\n 187 | \t\t\t\t prop->lane_bus_holder[i]);\n 188 | }","lines":5,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/acpi/soundwire.c#L184-L188"},{"ref":"src/acpi/soundwire.c:171-182","code":" 171 | \tfor (i = 0; i < prop->lane_mapping_count && i < SOUNDWIRE_MAX_LANE; i++) {\n 172 | \t\tconst struct soundwire_multilane_map *map = &prop->lane_mapping[i];\n 173 | \t\tconst char *name;\n 174 | \n 175 | \t\t/* Get the name of this connection */\n 176 | \t\tif (map->direction == MASTER_LANE)\n 177 | \t\t\tname = multilane_master_lane_names[map->connection.master_lane];\n 178 | \t\telse\n 179 | \t\t\tname = multilane_slave_link_names[map->connection.slave_link];\n 180 | \n 181 | \t\tacpi_dp_add_string(dsd, multilane_names[map->lane], name);\n 182 | \t}","lines":12,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/acpi/soundwire.c#L171-L182"}]},
{"id":"OVF_32","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"low","difficulty":"easy","description":"In `mmc_update_capacity` the EXT_CSD SEC_CNT bytes (external data from the card) are combined with `ext_csd[EXT_CSD_SEC_CNT + 3] << 24`. `ext_csd[...]` is an `unsigned char` promoted to `int`; when the top byte is >= 128 the shift produces a value greater than `INT_MAX`, which is signed-integer-overflow undefined behavior. The intent is an unsigned 32-bit assembly.","fix":"Cast each byte to `uint32_t` before shifting, e.g. `capacity = ((uint32_t)ext_csd[EXT_CSD_SEC_CNT + 0] << 0) | ((uint32_t)ext_csd[EXT_CSD_SEC_CNT + 1] << 8) | ((uint32_t)ext_csd[EXT_CSD_SEC_CNT + 2] << 16) | ((uint32_t)ext_csd[EXT_CSD_SEC_CNT + 3] << 24);` so the arithmetic is performed in unsigned 32-bit and the outer `(uint32_t)` cast is unnecessary.","locations":[{"ref":"src/commonlib/storage/mmc.c:420-423","code":" 420 | \tcapacity = (uint32_t)(ext_csd[EXT_CSD_SEC_CNT + 0] << 0 |\n 421 | \t\t ext_csd[EXT_CSD_SEC_CNT + 1] << 8 |\n 422 | \t\t ext_csd[EXT_CSD_SEC_CNT + 2] << 16 |\n 423 | \t\t ext_csd[EXT_CSD_SEC_CNT + 3] << 24);","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/commonlib/storage/mmc.c#L420-L423"}]},
{"id":"OVF_33","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"low","difficulty":"easy","description":"`max_time` is read as `(msr.hi >> 16) & 0x7f`, giving a range of `0..127`, and is then used directly as an index into `power_limit_time_msr_to_sec[]`. That array is built with designated initializers whose highest index is `[0x70]` (112), so its size is only 113 entries (`0..112`). Any `max_time` value in `0x71..0x7f` (113..127) reads out of bounds past the static rodata array. The returned garbage byte is then used to (a) possibly overwrite `power_limit_1_time` and (b) index `power_limit_time_sec_to_msr[power_limit_1_time]`, potentially chaining into a second OOB read. `max_time` comes from the `MSR_PKG_POWER_SKU` hardware register, so a SKU reporting an out-of-table encoding triggers this. Identical bug exists in both the Haswell and SandyBridge/IvyBridge power-limit code.","fix":"Bound-check `max_time` against `ARRAY_SIZE(power_limit_time_msr_to_sec)` before indexing, e.g. `if (max_time < ARRAY_SIZE(power_limit_time_msr_to_sec) && power_limit_time_msr_to_sec[max_time] > power_limit_1_time) power_limit_1_time = power_limit_time_msr_to_sec[max_time];`, or size the array to 128 entries so the full `& 0x7f` range is always in bounds.","locations":[{"ref":"src/cpu/intel/haswell/haswell_init.c:283-288","code":" 283 | \tmax_time = (msr.hi >> 16) & 0x7f;\n 284 | \n 285 | \tprintk(BIOS_DEBUG, \"CPU TDP: %u Watts\\n\", tdp / power_unit);\n 286 | \n 287 | \tif (power_limit_time_msr_to_sec[max_time] > power_limit_1_time)\n 288 | \t\tpower_limit_1_time = power_limit_time_msr_to_sec[max_time];","lines":6,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/cpu/intel/haswell/haswell_init.c#L283-L288"},{"ref":"src/cpu/intel/model_206ax/model_206ax_init.c:123-128","code":" 123 | \tmax_time = (msr.hi >> 16) & 0x7f;\n 124 | \n 125 | \tprintk(BIOS_DEBUG, \"CPU TDP: %u Watts\\n\", tdp / power_unit);\n 126 | \n 127 | \tif (power_limit_time_msr_to_sec[max_time] > power_limit_1_time)\n 128 | \t\tpower_limit_1_time = power_limit_time_msr_to_sec[max_time];","lines":6,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/cpu/intel/model_206ax/model_206ax_init.c#L123-L128"}]},
{"id":"OVF_34","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"low","difficulty":"medium","description":"In `ucode_get_ext_sig_table()` the bounds check `ext_tbl_len < (sizeof(struct ext_sig_table) + ext_tbl->ext_sig_cnt * sizeof(struct ext_sig_entry))` uses `ext_tbl->ext_sig_cnt`, a `u32` taken straight from the microcode blob. The multiplication `ext_sig_cnt * sizeof(struct ext_sig_entry)` (12 bytes) is done in `size_t`; on a 32-bit `size_t` build a large `ext_sig_cnt` (> ~357M) overflows and wraps to a small value, so the check passes even though the table does not actually fit. The caller `find_cbfs_microcode()` then iterates `for (i = 0; i < ext_tbl->ext_sig_cnt; i++, entry++)` reading `ext_sig_cnt` entries and dereferencing `entry->sig`/`entry->pf` far past the mapped CBFS region. Additionally `ext_tbl_len` (signed `ssize_t`) is implicitly converted to unsigned for the comparison. Reachability is limited because the microcode blob ships inside the firmware image, so severity is low, but it is a genuine integer-overflow-driven OOB read of externally-formatted data.","fix":"Validate `ext_sig_cnt` against an upper bound derived from `ext_tbl_len` without multiplying first, e.g. compute `max_entries = (ext_tbl_len - sizeof(struct ext_sig_table)) / sizeof(struct ext_sig_entry)` and reject when `ext_tbl->ext_sig_cnt > max_entries`. Keep the length in a signed/`size_t` type consistently to avoid the signed/unsigned comparison.","locations":[{"ref":"src/cpu/intel/microcode/microcode.c:149-167","code":" 149 | static struct ext_sig_table *ucode_get_ext_sig_table(const struct microcode *ucode)\n 150 | {\n 151 | \tstruct ext_sig_table *ext_tbl;\n 152 | \t/* header + ucode data blob size */\n 153 | \tu32 size = ucode->data_size + sizeof(struct microcode);\n 154 | \n 155 | \tssize_t ext_tbl_len = ucode->total_size - size;\n 156 | \n 157 | \tif (ext_tbl_len < (ssize_t)sizeof(struct ext_sig_table))\n 158 | \t\treturn NULL;\n 159 | \n 160 | \text_tbl = (struct ext_sig_table *)((uintptr_t)ucode + size);\n 161 | \n 162 | \tif (ext_tbl_len < (sizeof(struct ext_sig_table) +\n 163 | \t\t\t\text_tbl->ext_sig_cnt * sizeof(struct ext_sig_entry)))\n 164 | \t\treturn NULL;\n 165 | \n 166 | \treturn ext_tbl;\n 167 | }","lines":19,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/cpu/intel/microcode/microcode.c#L149-L167"},{"ref":"src/cpu/intel/microcode/microcode.c:230-234","code":" 230 | \t\t\tfor (i = 0; i < ext_tbl->ext_sig_cnt; i++, entry++) {\n 231 | \t\t\t\tif ((sig == entry->sig) && (pf & entry->pf)) {\n 232 | \t\t\t\t\treturn ucode_updates;\n 233 | \t\t\t\t}\n 234 | \t\t\t}","lines":5,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/cpu/intel/microcode/microcode.c#L230-L234"}]},
{"id":"OVF_35","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"low","difficulty":"medium","description":"`azalia_find_verb()` reads the per-codec jack count `verb_table[idx + 2]` (and `verb_table[idx]`) inside the loop guarded only by `idx < (verb_table_bytes / sizeof(u32))`. Because `idx + 2` is dereferenced before validating it is in range, a truncated or malformed verb table (where a header's declared jack count does not land the running `idx` exactly on the table end) causes an out-of-bounds read past `verb_table`, and the subsequent `idx += verb_size + 3` can then walk further past the buffer. For well-formed compile-time tables this does not trigger, so impact is limited to malformed/hand-edited tables.","fix":"Bound-check each access against the table length, e.g. verify `idx + 2 < (verb_table_bytes / sizeof(u32))` before reading `verb_table[idx + 2]`, and verify `idx + 3 + verb_size <= (verb_table_bytes / sizeof(u32))` before returning `*verb = &verb_table[idx + 3]` / advancing `idx`.","locations":[{"ref":"src/device/azalia_device.c:119-136","code":" 119 | u32 azalia_find_verb(const u32 *verb_table, u32 verb_table_bytes, u32 viddid, const u32 **verb)\n 120 | {\n 121 | \tint idx = 0;\n 122 | \n 123 | \twhile (idx < (verb_table_bytes / sizeof(u32))) {\n 124 | \t\t/* Header contains the number of jacks, aka groups of 4 dwords */\n 125 | \t\tu32 verb_size = 4 * verb_table[idx + 2];\n 126 | \t\tif (verb_table[idx] != viddid) {\n 127 | \t\t\tidx += verb_size + 3;\t// skip verb + header\n 128 | \t\t\tcontinue;\n 129 | \t\t}\n 130 | \t\t*verb = &verb_table[idx + 3];\n 131 | \t\treturn verb_size;\n 132 | \t}\n 133 | \n 134 | \t/* Not all codecs need to load another verb */\n 135 | \treturn 0;\n 136 | }","lines":18,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/device/azalia_device.c#L119-L136"}]},
{"id":"OVF_36","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"low","difficulty":"very_easy","description":"In `i2c_gpiomux_mux_fill_ssdt()` the loop iterates `for (i = 0; i < config->mux_gpio_count; i++)` writing into the fixed-size local array `struct acpi_gpio_res_params param[MAX_NUM_MUX_GPIOS]` (size 4) and reading `config->mux_gpio[i]` (also size 4). `mux_gpio_count` is a `uint32_t` taken from devicetree with no bounds check, so a devicetree that sets it above `MAX_NUM_MUX_GPIOS` overflows both the stack array `param` and reads past `mux_gpio[]`.","fix":"Clamp the loop to the array size. Before:\n```\nfor (i = 0; i < config->mux_gpio_count; i++) {\n```\nAfter:\n```\nfor (i = 0; i < config->mux_gpio_count && i < MAX_NUM_MUX_GPIOS; i++) {\n```\n(and pass the clamped count to `acpi_dp_add_gpio_array()`).","locations":[{"ref":"src/drivers/i2c/gpiomux/mux/mux.c:26-47","code":" 26 | \tstruct acpi_gpio_res_params param[MAX_NUM_MUX_GPIOS];\n 27 | \tint i;\n 28 | \n 29 | \tif (!scope || !path)\n 30 | \t\treturn;\n 31 | \n 32 | \t/* Device */\n 33 | \tacpigen_write_scope(scope);\n 34 | \tacpigen_write_device(acpi_device_name(dev));\n 35 | \tacpigen_write_name_string(\"_HID\", ACPI_DT_NAMESPACE_HID);\n 36 | \tacpigen_write_STA(acpi_device_status(dev));\n 37 | \n 38 | \t/* Resources */\n 39 | \tacpigen_write_name(\"_CRS\");\n 40 | \tacpigen_write_resourcetemplate_header();\n 41 | \tfor (i = 0; i < config->mux_gpio_count; i++) {\n 42 | \t\tacpi_device_write_gpio(&config->mux_gpio[i]);\n 43 | \t\tparam[i].ref = path;\n 44 | \t\tparam[i].index = i;\n 45 | \t\tparam[i].pin = 0;\n 46 | \t\tparam[i].active_low = config->mux_gpio[i].active_low;\n 47 | \t}","lines":22,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/i2c/gpiomux/mux/mux.c#L26-L47"}]},
{"id":"OVF_37","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"low","difficulty":"easy","description":"`fsp_validate_component()` guards only that `file_size >= FSP_HDR_OFFSET + fsp_hdr_get_expected_min_length()` (72-80 bytes depending on spec, lines 62-65), and the comment in `looks_like_fsp_header()` explicitly states the intent is to avoid accessing out-of-bound bytes. But `fsp_identify()` then does `memcpy(hdr, fsp_blob, sizeof(struct fsp_header))` (line 51), and `sizeof(struct fsp_header)` is 84 bytes (the struct extends through `res5`). So the copy reads 4-12 bytes past the validated minimum region (`FSP_HDR_OFFSET` is `0x94`). For a truncated/malformed blob sized exactly at the validated minimum this is an out-of-bounds read past the mapped buffer; the explicit min-length guard does not actually cover the number of bytes `memcpy` consumes.","fix":"Make the size guard cover the actual copy, e.g. check `file_size < FSP_HDR_OFFSET + sizeof(struct fsp_header)` (or copy only `MIN(sizeof(struct fsp_header), file_size - FSP_HDR_OFFSET)` bytes and zero the remainder) before/inside `fsp_identify()` so `memcpy` never reads beyond the validated region.","locations":[{"ref":"src/drivers/intel/fsp2_0/util.c:49-56","code":" 49 | enum cb_err fsp_identify(struct fsp_header *hdr, const void *fsp_blob)\n 50 | {\n 51 | \tmemcpy(hdr, fsp_blob, sizeof(struct fsp_header));\n 52 | \tif (!looks_like_fsp_header(hdr))\n 53 | \t\treturn CB_ERR;\n 54 | \n 55 | \treturn CB_SUCCESS;\n 56 | }","lines":8,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/intel/fsp2_0/util.c#L49-L56"},{"ref":"src/drivers/intel/fsp2_0/util.c:58-70","code":" 58 | enum cb_err fsp_validate_component(struct fsp_header *hdr, void *fsp_file, size_t file_size)\n 59 | {\n 60 | \tvoid *raw_hdr = fsp_file + FSP_HDR_OFFSET;\n 61 | \n 62 | \tif (file_size < FSP_HDR_OFFSET + fsp_hdr_get_expected_min_length()) {\n 63 | \t\tprintk(BIOS_CRIT, \"FSP blob too small.\\n\");\n 64 | \t\treturn CB_ERR;\n 65 | \t}\n 66 | \n 67 | \tif (fsp_identify(hdr, raw_hdr) != CB_SUCCESS) {\n 68 | \t\tprintk(BIOS_CRIT, \"No valid FSP header\\n\");\n 69 | \t\treturn CB_ERR;\n 70 | \t}","lines":13,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/intel/fsp2_0/util.c#L58-L70"}]},
{"id":"OVF_38","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"negligible","difficulty":"very_easy","description":"In both ARM and ARM64 `dump_stack()` the loop starts at `i = bytes / sizeof(*ptr)` and the inner loop reads `*(ptr + j)` for `j` in `[i, i + line)`. On the first iteration this reads words `ptr[bytes/sizeof]` through `ptr[bytes/sizeof + line - 1]`, i.e. up to `line` words (32 bytes on ARM, 64 bytes on ARM64) past the intended `bytes`-sized dump window. It is a diagnostic dump printed immediately before `die()`, so impact is negligible, but it is a genuine out-of-bounds read (the start index should be `bytes/sizeof(*ptr) - line`).","fix":"Start the outer index one line below the top. ARM64 before:\n```c\n\tfor (i = bytes / sizeof(*ptr); i >= 0; i -= words_per_line) {\n```\nafter:\n```c\n\tfor (i = bytes / sizeof(*ptr) - words_per_line; i >= 0; i -= words_per_line) {\n```\nApply the analogous change (`- line`) in the ARM version.","locations":[{"ref":"src/arch/arm/armv7/exception.c:19-32","code":" 19 | static void dump_stack(uintptr_t addr, size_t bytes)\n 20 | {\n 21 | \tint i, j;\n 22 | \tconst int line = 8;\n 23 | \tuint32_t *ptr = (uint32_t *)(addr & ~(line * sizeof(*ptr) - 1));\n 24 | \n 25 | \tprintk(BIOS_ERR, \"Dumping stack:\\n\");\n 26 | \tfor (i = bytes / sizeof(*ptr); i >= 0; i -= line) {\n 27 | \t\tprintk(BIOS_ERR, \"%p: \", ptr + i);\n 28 | \t\tfor (j = i; j < i + line; j++)\n 29 | \t\t\tprintk(BIOS_ERR, \"%08x \", *(ptr + j));\n 30 | \t\tprintk(BIOS_ERR, \"\\n\");\n 31 | \t}\n 32 | }","lines":14,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/arch/arm/armv7/exception.c#L19-L32"},{"ref":"src/arch/arm64/armv8/exception.c:32-45","code":" 32 | static void dump_stack(uintptr_t addr, size_t bytes)\n 33 | {\n 34 | \tint i, j;\n 35 | \tconst int words_per_line = 8;\n 36 | \tuint64_t *ptr = (void *)ALIGN_DOWN(addr, words_per_line * sizeof(*ptr));\n 37 | \n 38 | \tprintk(BIOS_DEBUG, \"Dumping stack:\\n\");\n 39 | \tfor (i = bytes / sizeof(*ptr); i >= 0; i -= words_per_line) {\n 40 | \t\tprintk(BIOS_DEBUG, \"%p: \", ptr + i);\n 41 | \t\tfor (j = i; j < i + words_per_line; j++)\n 42 | \t\t\tprintk(BIOS_DEBUG, \"%016llx \", *(ptr + j));\n 43 | \t\tprintk(BIOS_DEBUG, \"\\n\");\n 44 | \t}\n 45 | }","lines":14,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/arch/arm64/armv8/exception.c#L32-L45"}]},
{"id":"OVF_39","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"negligible","difficulty":"easy","description":"In `LZ4_readLE32()` each byte is promoted to `int` before shifting, so `*((const uint8_t *)src + 3) << 24` can evaluate to a value greater than `INT_MAX` (up to `255 << 24 == 0xFF000000`) when the top byte's high bit is set, which is technically signed-integer overflow (undefined behavior) before the result is OR-ed into the `uint32_t` return value. It works on all real compilers/targets coreboot uses but is not strictly conforming.","fix":"Cast each byte to `uint32_t` before shifting, e.g. `return (uint32_t)((const uint8_t *)src)[3] << 24 | (uint32_t)((const uint8_t *)src)[2] << 16 | (uint32_t)((const uint8_t *)src)[1] << 8 | (const uint8_t *)src)[0];`.","locations":[{"ref":"src/commonlib/bsd/lz4_wrapper.c:23-29","code":" 23 | static uint32_t LZ4_readLE32(const void *src)\n 24 | {\n 25 | \treturn *((const uint8_t *)src + 3) << 24\n 26 | \t | *((const uint8_t *)src + 2) << 16\n 27 | \t | *((const uint8_t *)src + 1) << 8\n 28 | \t | *(const uint8_t *)src;\n 29 | }","lines":7,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/commonlib/bsd/lz4_wrapper.c#L23-L29"}]},
{"id":"OVF_40","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"negligible","difficulty":"easy","description":"`skip_atoi()` accumulates `result = result * 10 + (str[0] - '0')` with no overflow protection. For long digit runs the `unsigned int` result wraps silently. This is used in `dt_fixup_external()` to parse the byte offset of an external phandle fixup (`uint32_t offset = skip_atoi(&entry)`), so a crafted overlay fixup string could wrap the offset to an arbitrary small value; combined with the `offset + 4` overflow finding this can select an unexpected write location, though the bounds check normally still catches in-range-but-wrong offsets.","fix":"Document that `skip_atoi()` does not detect overflow, and in callers that consume attacker-influenced input (`dt_fixup_external`) validate the parsed offset against the property size (which the `offset > size - 4` fix already does once corrected).","locations":[{"ref":"src/commonlib/bsd/string.c:56-65","code":" 56 | unsigned int skip_atoi(char **ptr)\n 57 | {\n 58 | \tunsigned int result = 0;\n 59 | \tchar *str;\n 60 | \n 61 | \tfor (str = *ptr; isdigit(str[0]); str++)\n 62 | \t\tresult = result * 10 + (str[0] - '0');\n 63 | \t*ptr = str;\n 64 | \treturn result;\n 65 | }","lines":10,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/commonlib/bsd/string.c#L56-L65"}]},
{"id":"OVF_41","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"negligible","difficulty":"easy","description":"`power_unit = 2 << ((msr.lo & 0xf) - 1);` reads the energy-status-unit field of `MSR_PKG_POWER_SKU_UNIT` and subtracts 1 before using it as a shift count. If the field is 0 (`msr.lo & 0xf == 0`) the shift amount becomes -1, which is undefined behavior in C and would produce a bogus `power_unit` that is subsequently used as a divisor (`tdp / power_unit`, `msr.lo / power_unit`). Real Intel SKUs always report a nonzero ESU encoding, so this is essentially unreachable on production hardware, but it is genuine UB and appears unguarded in four places.","fix":"Guard the field before shifting/dividing, e.g. `unsigned int esu = msr.lo & 0xf; power_unit = esu ? (2 << (esu - 1)) : 1;`, or equivalently `power_unit = 1 << (msr.lo & 0xf)` which is well-defined for a 0 field and yields the same values for nonzero encodings.","locations":[{"ref":"src/cpu/intel/haswell/haswell_init.c:276","code":" 276 | \tpower_unit = 2 << ((msr.lo & 0xf) - 1);","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/cpu/intel/haswell/haswell_init.c#L276"},{"ref":"src/cpu/intel/model_206ax/model_206ax_init.c:116","code":" 116 | \tpower_unit = 2 << ((msr.lo & 0xf) - 1);","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/cpu/intel/model_206ax/model_206ax_init.c#L116"},{"ref":"src/cpu/intel/haswell/acpi.c:254","code":" 254 | \tpower_unit = 2 << ((msr.lo & 0xf) - 1);","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/cpu/intel/haswell/acpi.c#L254"},{"ref":"src/cpu/intel/model_206ax/acpi.c:268","code":" 268 | \tpower_unit = 2 << ((msr.lo & 0xf) - 1);","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/cpu/intel/model_206ax/acpi.c#L268"}]},
{"id":"LOGIC_1","type":"logic_error","type_label":"Logic Error","severity":"high","difficulty":"medium","description":"The quicksort implementation in pci_module.c has infinite recursion when partition() returns 0. This happens whenever all elements in the subarray are >= the pivot (e.g., all devices have equal BDF values, or two identical PCI devices exist). When partition returns 0: quicksort(list, 0) terminates immediately, but quicksort(&list[0], len - 0) is the same call with identical arguments, causing infinite recursion and a stack overflow. This can occur on systems with multiple functions of the same device appearing in CBFS scan order.","fix":"After partition, skip the recursive call if the pivot is at the start or end of the sub-range: quicksort(list, index) is already safe. For the right call, use: if (index + 1 < len) quicksort(&list[index + 1], len - index - 1). This excludes the pivot element (which is already in its final sorted position at list[index] after the swap on line 54).","locations":[{"ref":"payloads/coreinfo/pci_module.c:59-70","code":" 59 | static void quicksort(struct pci_devices *list, int len)\n 60 | {\n 61 | \tint index;\n 62 | \n 63 | \tif (len <= 1)\n 64 | \t\treturn;\n 65 | \n 66 | \tindex = partition(list, len);\n 67 | \n 68 | \tquicksort(list, index);\n 69 | \tquicksort(&(list[index]), len - index);\n 70 | }","lines":12,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/coreinfo/pci_module.c#L59-L70"}]},
{"id":"LOGIC_2","type":"logic_error","type_label":"Logic Error","severity":"high","difficulty":"very_easy","description":"mb_parse_mmap() advances ptr by mmap->size + sizeof(mmap->size). If a bootloader provides a corrupt mmap entry with size == 0, ptr never advances and the while loop spins forever. There is no lower bound check on mmap->size before the pointer arithmetic.","fix":"Add a guard: if (mmap->size < sizeof(*mmap) - sizeof(mmap->size)) break; (or return) before advancing ptr, to reject malformed entries and prevent an infinite loop.","locations":[{"ref":"payloads/libpayload/arch/x86/multiboot.c:64-71","code":" 64 | \twhile(ptr < (start + table->mmap_length)) {\n 65 | \t\tstruct multiboot_mmap *mmap = (struct multiboot_mmap *) ptr;\n 66 | \n 67 | \t\tif (mb_add_memrange(info, mmap->addr, mmap->length, mmap->type))\n 68 | \t\t\treturn;\n 69 | \n 70 | \t\tptr += (mmap->size + sizeof(mmap->size));\n 71 | \t}","lines":8,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/arch/x86/multiboot.c#L64-L71"}]},
{"id":"LOGIC_3","type":"logic_error","type_label":"Logic Error","severity":"high","difficulty":"very_easy","description":"In dos/pdcscrn.c _query_adapter_type(), BIOS INT 10h function 0x1a00 (VGA Identify Adapter) returns the\nactive display type in BL, not AL (AL is only used to confirm the function was handled, returning 0x1a).\nThe code checks regs.h.al == 0x1a correctly, but then switches on regs.h.al again, which is always 0x1a\nat that point. The adapter type from BL is never read. Every supported adapter type (MDA, CGA, EGA,\nVGA, MCGA) is thus dead code in the switch, and the default branch always fires, returning _CGA.\nThis causes incorrect adapter detection on all platforms when the VGA BIOS is present.","fix":"Change `switch (regs.h.al)` at line 304 to `switch (regs.h.bl)`. BL holds the active display combination code per the VGA BIOS spec.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/dos/pdcscrn.c:300-339","code":" 300 | if ((regs.h.al == 0x1a) && (retval == _NONE))\n 301 | {\n 302 | /* We know that the PS/2 video BIOS is alive and well. */\n 303 | \n 304 | switch (regs.h.al)\n 305 | {\n 306 | case 0:\n 307 | retval = _NONE;\n 308 | break;\n 309 | case 1:\n 310 | retval = _MDA;\n 311 | break;\n 312 | case 2:\n 313 | retval = _CGA;\n 314 | break;\n 315 | case 4:\n 316 | retval = _EGACOLOR;\n 317 | sizeable = TRUE;\n 318 | break;\n 319 | case 5:\n 320 | retval = _EGAMONO;\n 321 | break;\n 322 | case 26: /* ...alt. VGA BIOS... */\n 323 | case 7:\n 324 | retval = _VGACOLOR;\n 325 | sizeable = TRUE;\n 326 | break;\n 327 | case 8:\n 328 | retval = _VGAMONO;\n 329 | break;\n 330 | case 10:\n 331 | case 13:\n 332 | retval = _MCGACOLOR;\n 333 | break;\n 334 | case 12:\n 335 | retval = _MCGAMONO;\n 336 | break;\n 337 | default:\n 338 | retval = _CGA;\n 339 | }","lines":40,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/dos/pdcscrn.c#L300-L339"}]},
{"id":"LOGIC_4","type":"logic_error","type_label":"Logic Error","severity":"high","difficulty":"very_easy","description":"In os2/pdcscrn.c, the _screen_mode_equals() function compares the horizontal and vertical\nresolution fields incorrectly. Line 266 compares current.hres against oldmode->vres instead\nof oldmode->hres. This means the horizontal resolution is never truly compared, and the\nfunction may incorrectly return TRUE (modes are equal) when the horizontal resolution differs,\nor FALSE when it matches, leading to unnecessary or missed mode switches during screen restore.","fix":"Change `current.hres == oldmode->vres` to `current.hres == oldmode->hres` on line 266.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/os2/pdcscrn.c:255-268","code":" 255 | static bool _screen_mode_equals(VIOMODEINFO *oldmode)\n 256 | {\n 257 | VIOMODEINFO current = {0};\n 258 | \n 259 | VioGetMode(¤t, 0);\n 260 | \n 261 | return ((current.cb == oldmode->cb) &&\n 262 | (current.fbType == oldmode->fbType) &&\n 263 | (current.color == oldmode->color) &&\n 264 | (current.col == oldmode->col) &&\n 265 | (current.row == oldmode->row) &&\n 266 | (current.hres == oldmode->vres) &&\n 267 | (current.vres == oldmode->vres));\n 268 | }","lines":14,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/os2/pdcscrn.c#L255-L268"}]},
{"id":"LOGIC_5","type":"logic_error","type_label":"Logic Error","severity":"high","difficulty":"medium","description":"In pdcurses/addch.c waddch(), the default case for control characters (non-printable ASCII\nother than special cases) calls `waddch(win, ch + '@')` to display the caret notation second\ncharacter. However `ch` here is the original chtype which includes both the text byte AND all\nattribute bits (color pair, bold, etc.). Adding '@' (0x40) to the full chtype corrupts the\nattribute field when high attribute bits are set, producing wrong colors or visual artifacts.\nThe 0x7f case correctly uses `attr | '?'`, but the general control-char case does not follow\nthe same pattern.","fix":"Replace `waddch(win, ch + '@')` with `waddch(win, attr | (text + '@'))` to apply only the text increment while preserving the original attributes.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/addch.c:189-196","code":" 189 | default:\n 190 | /* handle control chars */\n 191 | \n 192 | if (waddch(win, attr | '^') == ERR)\n 193 | return ERR;\n 194 | \n 195 | return waddch(win, ch + '@');\n 196 | }","lines":8,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/addch.c#L189-L196"}]},
{"id":"LOGIC_6","type":"logic_error","type_label":"Logic Error","severity":"high","difficulty":"medium","description":"In initscr.c, the ripped-off line initialization loop (lines 178-188) calls newwin() for each ripped-off line and passes the result directly to (*linesripped[i].init)() without checking if newwin() returned NULL. A NULL WINDOW pointer is passed to the user callback, likely causing a crash.","fix":"Check the return value of newwin() in the loop; call exit() or return NULL on allocation failure, consistent with how curscr and pdc_lastscr are handled.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/initscr.c:178-188","code":" 178 | for (i = 0; i < linesrippedoff; i++)\n 179 | {\n 180 | if (linesripped[i].line < 0)\n 181 | (*linesripped[i].init)(newwin(1, COLS, LINES - 1, 0), COLS);\n 182 | else\n 183 | (*linesripped[i].init)(newwin(1, COLS,\n 184 | SP->linesrippedoffontop++, 0), COLS);\n 185 | \n 186 | SP->linesrippedoff++;\n 187 | LINES--;\n 188 | }","lines":11,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/initscr.c#L178-L188"}]},
{"id":"LOGIC_7","type":"logic_error","type_label":"Logic Error","severity":"high","difficulty":"very_easy","description":"pnoutrefresh() checks (sy2 >= COLS) instead of (sx2 >= COLS). The condition at line 178 reads '(sy2 >= LINES) || (sy2 >= COLS)' - both checks test sy2. The second check should test sx2 against COLS. As written, sx2 is never validated against the screen width, so a caller with sx2 >= COLS will not get ERR. The subsequent memcpy at line 199 writes curscr->_y[sline] + sx1 for num_cols columns, where num_cols = min(sx2-sx1+1, w->_maxx-px). If sx2 is very large, num_cols can exceed COLS - sx1, writing past the end of the curscr row buffer.","fix":"Change the second condition from (sy2 >= COLS) to (sx2 >= COLS): 'if (!w || !(w->_flags & (_PAD|_SUBPAD)) || (sy2 >= LINES) || (sx2 >= COLS))'","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/pad.c:178-200","code":" 178 | if (!w || !(w->_flags & (_PAD|_SUBPAD)) || (sy2 >= LINES) || (sy2 >= COLS))\n 179 | return ERR;\n 180 | \n 181 | if (py < 0)\n 182 | py = 0;\n 183 | if (px < 0)\n 184 | px = 0;\n 185 | if (sy1 < 0)\n 186 | sy1 = 0;\n 187 | if (sx1 < 0)\n 188 | sx1 = 0;\n 189 | \n 190 | if (sy2 < sy1 || sx2 < sx1)\n 191 | return ERR;\n 192 | \n 193 | num_cols = min((sx2 - sx1 + 1), (w->_maxx - px));\n 194 | \n 195 | while (sline <= sy2)\n 196 | {\n 197 | if (pline < w->_maxy)\n 198 | {\n 199 | memcpy(curscr->_y[sline] + sx1, w->_y[pline] + px,\n 200 | num_cols * sizeof(chtype));","lines":23,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/pad.c#L178-L200"}]},
{"id":"LOGIC_8","type":"logic_error","type_label":"Logic Error","severity":"high","difficulty":"very_easy","description":"`_nc_Copy_Argument()` recurses incorrectly for linked field types. On lines 148-149\nit passes the parent `typ` (the linked composite type) to both recursive calls,\ninstead of passing `typ->left` and `typ->right` respectively, as every other\nfunction in this file does. Compare with `_nc_Make_Argument()` (lines 100-101) and\n`_nc_Free_Argument()` (lines 190-191) which both correctly forward `typ->left` and\n`typ->right`. The consequence is that the recursive calls re-enter the\n`_LINKED_TYPE` branch with a non-linked TypeArgument (the left/right sub-argument),\ncausing incorrect behaviour and potential NULL-pointer dereference or memory\ncorruption when types with arguments are linked via `link_fieldtype()`.","fix":"Replace:\n p->left = _nc_Copy_Argument(typ, argp->left, err);\n p->right = _nc_Copy_Argument(typ, argp->right, err);\nwith:\n p->left = _nc_Copy_Argument(typ->left, argp->left, err);\n p->right = _nc_Copy_Argument(typ->right, argp->right, err);","locations":[{"ref":"payloads/libpayload/curses/form/fld_def.c:148-149","code":" 148 | \t p->left = _nc_Copy_Argument(typ, argp->left, err);\n 149 | \t p->right = _nc_Copy_Argument(typ, argp->right, err);","lines":2,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/fld_def.c#L148-L149"}]},
{"id":"LOGIC_9","type":"logic_error","type_label":"Logic Error","severity":"high","difficulty":"very_easy","description":"Adjust_Cursor_Position computes the column from a buffer offset using field->cols instead of field->dcols. For a dynamic (scrollable) field dcols > cols, so the division remainder is wrong: curcol = idx - cols * currow gives a value larger than dcols-1 when idx falls in a dynamically grown column range. This corrupts the cursor position after any word-navigation or field-begin/end operation on a grown single-line field.","fix":"Change 'form->curcol = idx - field->cols * form->currow;' to 'form->curcol = idx - field->dcols * form->currow;' to match the dcols-wide row stride used everywhere else.","locations":[{"ref":"payloads/libpayload/curses/form/frm_driver.c:466-468","code":" 466 | form->currow = idx / field->dcols;\n 467 | form->curcol = idx - field->cols * form->currow;\n 468 | #endif","lines":3,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/frm_driver.c#L466-L468"}]},
{"id":"LOGIC_10","type":"logic_error","type_label":"Logic Error","severity":"high","difficulty":"very_easy","description":"FE_Delete_Word passes a wrong (always non-positive) length to myADDNSTR. The expression '(int)(s - After_End_Of_Data(s, (int)(ep - s)))' subtracts a pointer that is >= s from s, yielding a value <= 0. When negative it is passed to waddnstr() as the length, which in many curses implementations treats a negative n as 'print all', potentially overflowing the line. The intent appears to be to copy the remainder of the line after the deleted word; the correct pointer arithmetic should be 'After_End_Of_Data(s, (int)(ep - s)) - s'.","fix":"Replace 'myADDNSTR(form->w, s, (int)(s - After_End_Of_Data(s, (int)(ep - s))));' with 'myADDNSTR(form->w, s, (int)(After_End_Of_Data(s, (int)(ep - s)) - s));'.","locations":[{"ref":"payloads/libpayload/curses/form/frm_driver.c:2848-2855","code":" 2848 | s = Get_First_Whitespace_Character(cp, (int)(ep - cp));\n 2849 | /* to begin of next word */\n 2850 | s = Get_Start_Of_Data(s, (int)(ep - s));\n 2851 | if ((s != cp) && !ISBLANK(*s))\n 2852 | {\n 2853 | /* copy remaining line to window */\n 2854 | myADDNSTR(form->w, s, (int)(s - After_End_Of_Data(s, (int)(ep - s))));\n 2855 | }","lines":8,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/frm_driver.c#L2848-L2855"}]},
{"id":"LOGIC_11","type":"logic_error","type_label":"Logic Error","severity":"high","difficulty":"very_easy","description":"In Is_Sub_String (case-sensitive branch), 'part++' is placed OUTSIDE the while-loop body,\nexecuting unconditionally once after the loop regardless of how the loop exited.\nIn the IgnoreCaseFlag branch, 'part++' is correctly inside the loop.\nThe result is: in case-sensitive mode, 'part' is always advanced by one extra position\nbefore the final check '(*part) ? FALSE : TRUE'. This makes single-character patterns\nalways appear to match (part ends at '\\0' after one extra increment), and makes\nmulti-character patterns report false negatives for correct matches, breaking\nthe pattern-search navigation in menu_driver.","fix":"Move 'part++' inside the while loop body for the case-sensitive branch, matching\nthe structure of the IgnoreCaseFlag branch:\n while (*string && *part)\n {\n if (*part != *string++)\n break;\n part++;\n }","locations":[{"ref":"payloads/libpayload/curses/menu/m_driver.c:82-89","code":" 82 | else\n 83 | {\n 84 | while (*string && *part)\n 85 | \tif (*part != *string++)\n 86 | \t break;\n 87 | part++;\n 88 | }\n 89 | return ((*part) ? FALSE : TRUE);","lines":8,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/menu/m_driver.c#L82-L89"}]},
{"id":"LOGIC_12","type":"logic_error","type_label":"Logic Error","severity":"high","difficulty":"medium","description":"In m_post.c _nc_Post_Item(), after entering the spc_rows > 1 block (line 154), isfore is turned off via wattroff and then the code turns on menu->back. Later at line 177 it checks `if (!isback) wattroff(menu->back)`. However, the `isback` flag was NOT set inside the spc_rows block even though menu->back attribute was turned on there, so wattroff for back is correctly called - but isfore was cleared inside the block (line 162-163) without clearing the outer `isfore` variable in sync. The outer attribute-removal block at lines 183-188 may then call `wattroff(fore)` again with isfore still TRUE if the code path through spc_rows did not clear it. Double wattroff calls on a color-pair attribute can corrupt terminal state on some implementations.","fix":"Inside the spc_rows > 1 block, after calling `wattroff(menu->fore)` or `wattroff(menu->grey)`, also set `isfore = FALSE` / `isgrey = FALSE` so the final cleanup at lines 183-188 does not repeat the wattroff.","locations":[{"ref":"payloads/libpayload/curses/menu/m_post.c:154-188","code":" 154 | if (menu->spc_rows > 1)\n 155 | \t{\n 156 | \t int j, k, ncy, ncx;\n 157 | \n 158 | \t assert(cx >= 0 && cy >= 0);\n 159 | \t getyx(menu->win, ncy, ncx);\n 160 | \t if (isgrey)\n 161 | \t wattroff(menu->win, menu->grey);\n 162 | \t else if (isfore)\n 163 | \t wattroff(menu->win, menu->fore);\n 164 | \t wattron(menu->win, menu->back);\n 165 | \t for (j = 1; j < menu->spc_rows; j++)\n 166 | \t {\n 167 | \t if ((item_y + j) < getmaxy(menu->win))\n 168 | \t\t{\n 169 | \t\t wmove(menu->win, item_y + j, item_x);\n 170 | \t\t for (k = 0; k < count; k++)\n 171 | \t\t waddch(menu->win, ' ');\n 172 | \t\t}\n 173 | \t if ((cy + j) < getmaxy(menu->win))\n 174 | \t\t(void)mvwaddch(menu->win, cy + j, cx - 1, menu->pad);\n 175 | \t }\n 176 | \t wmove(menu->win, ncy, ncx);\n 177 | \t if (!isback)\n 178 | \t wattroff(menu->win, menu->back);\n 179 | \t}\n 180 | }\n 181 | \n 182 | /* Remove attributes */\n 183 | if (isfore)\n 184 | wattroff(menu->win, menu->fore);\n 185 | if (isback)\n 186 | wattroff(menu->win, menu->back);\n 187 | if (isgrey)\n 188 | wattroff(menu->win, menu->grey);","lines":35,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/menu/m_post.c#L154-L188"}]},
{"id":"LOGIC_13","type":"logic_error","type_label":"Logic Error","severity":"high","difficulty":"medium","description":"In mouse.c insert_buf(), mouse_buf_idx is incremented without bounds checking against sizeof(mouse_buf) (which is 4). If the PS/2 stream becomes desynchronized and sends many bytes with bit 3 set, or if mouse_decode() is never called to reset mouse_buf_idx, mouse_buf_idx can exceed 3 and the write `mouse_buf[mouse_buf_idx++] = c` on line 171 overflows the 4-byte static array, corrupting adjacent static variables (x_axis, y_axis, etc.).","fix":"Add a bounds check before writing: `if (mouse_buf_idx >= sizeof(mouse_buf)) { mouse_buf_idx = 0; return; }` inside insert_buf(), before the assignment.","locations":[{"ref":"payloads/libpayload/drivers/i8042/mouse.c:164-172","code":" 164 | static void insert_buf(unsigned char c)\n 165 | {\n 166 | \t/* Validate input:\n 167 | \t * First byte shall have bit 3 set ! */\n 168 | \tif (!mouse_buf_idx && !(c & 8))\n 169 | \t\treturn;\n 170 | \n 171 | \tmouse_buf[mouse_buf_idx++] = c;\n 172 | }","lines":9,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/i8042/mouse.c#L164-L172"}]},
{"id":"LOGIC_14","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In cpuinfo_module.c, do_name() fills a 49-byte stack buffer with CPU brand string data (up to 48 bytes written across three CPUID calls, 16 bytes each), but never writes a null terminator. When all three CPUID leaves return non-zero eax values, p advances to name+48, and name[48] is never set to '\\0'. The buffer is then passed to mvwprintw as a '%s' format, causing it to read past the end of the buffer until a null byte is found in adjacent stack memory.","fix":"Add explicit null termination before the mvwprintw call: name[48] = '\\0'; Alternatively, change the array to char name[49] = {0}; to zero-initialize it.","locations":[{"ref":"payloads/coreinfo/cpuinfo_module.c:138-161","code":" 138 | \tchar name[49], *p;\n 139 | \tuint32_t eax, ebx, ecx, edx;\n 140 | \tint t;\n 141 | \n 142 | \tp = name;\n 143 | \n 144 | \tfor (uint32_t i = 0x80000002; i <= 0x80000004; i++) {\n 145 | \t\tdocpuid(i, &eax, &ebx, &ecx, &edx);\n 146 | \n 147 | \t\tif (eax == 0)\n 148 | \t\t\tbreak;\n 149 | \n 150 | \t\tfor (t = 0; t < 4; t++)\n 151 | \t\t\t*p++ = eax >> (8 * t);\n 152 | \t\tfor (t = 0; t < 4; t++)\n 153 | \t\t\t*p++ = ebx >> (8 * t);\n 154 | \t\tfor (t = 0; t < 4; t++)\n 155 | \t\t\t*p++ = ecx >> (8 * t);\n 156 | \t\tfor (t = 0; t < 4; t++)\n 157 | \t\t\t*p++ = edx >> (8 * t);\n 158 | \t}\n 159 | \n 160 | \tmvwprintw(win, row, 1, \"Processor: %s\", name);\n 161 | }","lines":24,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/coreinfo/cpuinfo_module.c#L138-L161"}]},
{"id":"LOGIC_15","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"very_easy","description":"In multiboot_module_init(), parse_header(mbaddr) is called unconditionally even when sysinfo_have_multiboot() returns 0 (no multiboot tables found). When tables_good == 0, mbaddr is set to 0 by sysinfo_have_multiboot(), and parse_header(0) casts the address 0 to struct multiboot_header*, reading flags and mmap_addr from the null address. On bare-metal this is a null pointer dereference causing undefined behavior (or a crash on systems with memory protection).","fix":"Guard parse_header with the tables_good check: if (tables_good) parse_header(mbaddr); This mirrors the pattern used in the redraw function.","locations":[{"ref":"payloads/coreinfo/multiboot_module.c:83-91","code":" 83 | static int multiboot_module_init(void)\n 84 | {\n 85 | \tunsigned long mbaddr;\n 86 | \ttables_good = sysinfo_have_multiboot(&mbaddr);\n 87 | \n 88 | \tparse_header(mbaddr);\n 89 | \n 90 | \treturn tables_good ? 0 : -1;\n 91 | }","lines":9,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/coreinfo/multiboot_module.c#L83-L91"}]},
{"id":"LOGIC_16","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"very_easy","description":"In pci_module_redraw(), show_config_space() is called unconditionally with menu_selected (which is 0 initially) even when devices_index == 0 (no PCI devices found). This causes pci_read_config8() to be called with devices[0].device which is uninitialized, resulting in reading arbitrary I/O port addresses and potentially causing hardware issues or incorrect display.","fix":"Guard the call: if (devices_index > 0) show_config_space(win, 4, 26, menu_selected);","locations":[{"ref":"payloads/coreinfo/pci_module.c:149","code":" 149 | \tshow_config_space(win, 4, 26, menu_selected);","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/coreinfo/pci_module.c#L149"}]},
{"id":"LOGIC_17","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In multiboot.c mb_parse_cmdline(), the function reads a character pointer from phys_to_virt(table->cmdline) with no check that MULTIBOOT_FLAGS_CMDLINE is actually set or that the translated pointer is non-NULL/valid. get_multiboot_info() does gate this call behind a flag check (line 123), but if the flag is set and cmdline is 0 (a null physical address), phys_to_virt(0) may return a usable virtual address pointing to the zero page, causing reads from arbitrary low memory while parsing the command line.","fix":"Validate that table->cmdline is non-zero before calling phys_to_virt and dereferencing c.","locations":[{"ref":"payloads/libpayload/arch/x86/multiboot.c:89-105","code":" 89 | static void mb_parse_cmdline(struct multiboot_header *table)\n 90 | {\n 91 | \textern int main_argc;\n 92 | \textern char *main_argv[];\n 93 | \tchar *c = phys_to_virt(table->cmdline);\n 94 | \n 95 | \twhile(*c != '\\0' && main_argc < MAX_ARGC_COUNT) {\n 96 | \t\tmain_argv[main_argc++] = c;\n 97 | \n 98 | \t\tfor( ; *c != '\\0' && !isspace(*c); c++);\n 99 | \n 100 | \t\tif (*c) {\n 101 | \t\t\t*c = 0;\n 102 | \t\t\tc++;\n 103 | \t\t}\n 104 | \t}\n 105 | }","lines":17,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/arch/x86/multiboot.c#L89-L105"}]},
{"id":"LOGIC_18","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In os2/pdcclip.c, PDC_clearclipboard() calls WinEmptyClipbrd(hab) without first calling\nWinOpenClipbrd(hab). On OS/2 PM, the clipboard must be opened by the caller before it can be\nemptied; calling WinEmptyClipbrd on an unopened clipboard is an error that is silently ignored\nhere. As a result, PDC_clearclipboard() never actually clears the OS/2 system clipboard. By\ncontrast, PDC_setclipboard() correctly calls WinOpenClipbrd before modifying it.","fix":"Add `if (!WinOpenClipbrd(hab)) { WinTerminate(hab); return PDC_CLIP_ACCESS_ERROR; }` after WinInitialize and before WinEmptyClipbrd, and add WinCloseClipbrd after emptying (it is already present in the current code).","locations":[{"ref":"payloads/libpayload/curses/PDCurses/os2/pdcclip.c:162-185","code":" 162 | int PDC_clearclipboard(void)\n 163 | {\n 164 | #ifndef EMXVIDEO\n 165 | HAB hab;\n 166 | PTIB ptib;\n 167 | PPIB ppib;\n 168 | #endif\n 169 | PDC_LOG((\"PDC_clearclipboard() - called\\n\"));\n 170 | \n 171 | #ifndef EMXVIDEO\n 172 | DosGetInfoBlocks(&ptib, &ppib);\n 173 | ppib->pib_ultype = 3;\n 174 | hab = WinInitialize(0);\n 175 | \n 176 | WinEmptyClipbrd(hab);\n 177 | \n 178 | WinCloseClipbrd(hab);\n 179 | WinTerminate(hab);\n 180 | \n 181 | return PDC_CLIP_SUCCESS;\n 182 | #else\n 183 | return PDC_CLIP_ACCESS_ERROR;\n 184 | #endif\n 185 | }","lines":24,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/os2/pdcclip.c#L162-L185"}]},
{"id":"LOGIC_19","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In os2/pdckbd.c PDC_get_key(), the variable keyInfo (of type KBDKEYINFO) is declared only\ninside `#ifndef EMXVIDEO` (line 321), but it is used unconditionally at line 419 outside any\npreprocessor guard:\n if (keyInfo.fsState & (KBDSTF_LEFTSHIFT|KBDSTF_RIGHTSHIFT))\nWhen compiling with EMXVIDEO defined, `keyInfo` is not in scope at that point, causing a\ncompile error (or if the compiler tolerates it, undefined behavior from an uninitialized\nvariable). Shift-key detection for modifier keys is therefore broken or non-compilable in\nthe EMXVIDEO path.","fix":"Wrap the `if (keyInfo.fsState & ...)` block and the shift-key switch at lines 419-453 in `#ifndef EMXVIDEO ... #endif`, similar to the rest of the OS/2-specific keyboard handling.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/os2/pdckbd.c:319-322","code":" 319 | int key, scan;\n 320 | #ifndef EMXVIDEO\n 321 | KBDKEYINFO keyInfo = {0};\n 322 | #endif","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/os2/pdckbd.c#L319-L322"},{"ref":"payloads/libpayload/curses/PDCurses/os2/pdckbd.c:419-453","code":" 419 | if (keyInfo.fsState & (KBDSTF_LEFTSHIFT|KBDSTF_RIGHTSHIFT))\n 420 | {\n 421 | switch (key)\n 422 | {\n 423 | case KEY_HOME: /* Shift Home */\n 424 | key = KEY_SHOME;\n 425 | break;\n 426 | case KEY_UP: /* Shift Up */\n 427 | key = KEY_SUP;\n 428 | break;\n 429 | case KEY_PPAGE: /* Shift PgUp */\n 430 | key = KEY_SPREVIOUS;\n 431 | break;\n 432 | case KEY_LEFT: /* Shift Left */\n 433 | key = KEY_SLEFT;\n 434 | break;\n 435 | case KEY_RIGHT: /* Shift Right */\n 436 | key = KEY_SRIGHT;\n 437 | break;\n 438 | case KEY_END: /* Shift End */\n 439 | key = KEY_SEND;\n 440 | break;\n 441 | case KEY_DOWN: /* Shift Down */\n 442 | key = KEY_SDOWN;\n 443 | break;\n 444 | case KEY_NPAGE: /* Shift PgDn */\n 445 | key = KEY_SNEXT;\n 446 | break;\n 447 | case KEY_IC: /* Shift Ins */\n 448 | key = KEY_SIC;\n 449 | break;\n 450 | case KEY_DC: /* Shift Del */\n 451 | key = KEY_SDC;\n 452 | }\n 453 | }","lines":35,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/os2/pdckbd.c#L419-L453"}]},
{"id":"LOGIC_20","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In wchgat() (attr.c line 304), when n < 0, endpos is computed as 'win->_maxx - 1'. But startpos is win->_curx, which could equal win->_maxx (cursor at rightmost position). In that case, endpos (win->_maxx - 1) < startpos, so the for loop body never executes, but the code then unconditionally updates _firstch and _lastch using startpos and endpos. Since endpos < startpos, _lastch[n] = endpos = win->_maxx-1 may be set to a value smaller than startpos, corrupting the dirty region tracking. Additionally when n >= 0, 'min(startpos + n, win->_maxx) - 1' can yield -1 when startpos=0 and n=0, causing endpos=-1 and the for loop to be skipped, but again the firstch/lastch update runs unconditionally.","fix":"Guard the firstch/lastch update block with 'if (startpos <= endpos)' to avoid updating the dirty region when the range is empty.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/attr.c:291-321","code":" 291 | int wchgat(WINDOW *win, int n, attr_t attr, short color, const void *opts)\n 292 | {\n 293 | chtype *dest, newattr;\n 294 | int startpos, endpos;\n 295 | \n 296 | PDC_LOG((\"wchgat() - called\\n\"));\n 297 | \n 298 | if (!win)\n 299 | return ERR;\n 300 | \n 301 | newattr = (attr & A_ATTRIBUTES) | COLOR_PAIR(color);\n 302 | \n 303 | startpos = win->_curx;\n 304 | endpos = ((n < 0) ? win->_maxx : min(startpos + n, win->_maxx)) - 1;\n 305 | dest = win->_y[win->_cury];\n 306 | \n 307 | for (n = startpos; n <= endpos; n++)\n 308 | dest[n] = (dest[n] & A_CHARTEXT) | newattr;\n 309 | \n 310 | n = win->_cury;\n 311 | \n 312 | if (startpos < win->_firstch[n] || win->_firstch[n] == _NO_CHANGE)\n 313 | win->_firstch[n] = startpos;\n 314 | \n 315 | if (endpos > win->_lastch[n])\n 316 | win->_lastch[n] = endpos;\n 317 | \n 318 | PDC_sync(win);\n 319 | \n 320 | return OK;\n 321 | }","lines":31,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/attr.c#L291-L321"}]},
{"id":"LOGIC_21","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"very_easy","description":"wclrtoeol() dereferences win->_cury before checking whether 'win' is NULL (line 56 calls win->_cury and win->_curx inside PDC_LOG before the NULL check on line 58). PDC_LOG is a macro that may expand to a fprintf involving the arguments; on many builds it expands to nothing, so this is benign in practice. However, the pattern is dangerous: the NULL guard appears after the first field access, violating the usual convention.","fix":"Move the 'if (!win) return ERR;' check to before the PDC_LOG call, or ensure PDC_LOG does not dereference win.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/clear.c:50-59","code":" 50 | int wclrtoeol(WINDOW *win)\n 51 | {\n 52 | int x, y, minx;\n 53 | chtype blank, *ptr;\n 54 | \n 55 | PDC_LOG((\"wclrtoeol() - called: Row: %d Col: %d\\n\",\n 56 | win->_cury, win->_curx));\n 57 | \n 58 | if (!win)\n 59 | return ERR;","lines":10,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/clear.c#L50-L59"}]},
{"id":"LOGIC_22","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In color.c, start_color() calls 'memset(pair_set, 0, PDC_COLOR_PAIRS)' (line 124). pair_set is an array of bool, not char. On platforms where sizeof(bool) > 1, this only zeroes the first byte of each bool element, potentially leaving garbage in the remaining bytes. The correct call is 'memset(pair_set, 0, sizeof(pair_set))'.","fix":"Change to 'memset(pair_set, 0, sizeof(pair_set));' to correctly zero all bytes of the bool array.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/color.c:124","code":" 124 | memset(pair_set, 0, PDC_COLOR_PAIRS);","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/color.c#L124"}]},
{"id":"LOGIC_23","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In color.c, init_pair() validates fg and bg against 'first_col' (line 143). When use_default_colors() has been called, first_col is set to -1, which means the lower bound check 'fg < first_col' becomes 'fg < -1'. Since fg is a short, negative values fg == -1 (meaning 'use default') would pass the check, but fg == -2 or lower would be incorrectly accepted. The intent is to allow -1 but not other negatives, yet this is not enforced.","fix":"When first_col == -1, explicitly allow fg >= -1 and bg >= -1 rather than using the generic 'fg < first_col' comparison.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/color.c:142-144","code":" 142 | if (!pdc_color_started || pair < 1 || pair >= COLOR_PAIRS ||\n 143 | fg < first_col || fg >= COLORS || bg < first_col || bg >= COLORS)\n 144 | return ERR;","lines":3,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/color.c#L142-L144"}]},
{"id":"LOGIC_24","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In wdeleteln() (deleteln.c), the inner loop at line 69 runs 'for (y = win->_cury; y < win->_bmarg; y++)'. This stops at _bmarg-1 (exclusive), so it does not shift the line at _bmarg upward. Then at line 79, 'if (win->_cury <= win->_bmarg)' assigns the blank temp line to win->_y[win->_bmarg]. This is correct only when the scrolling region is exactly the window: _bmarg = _maxy-1. But when _bmarg < _maxy-1, lines below _bmarg are not affected, which is the correct behaviour. However, if _cury > _bmarg (cursor is below the scroll region), the for loop executes 0 iterations and the blank line is still assigned to _bmarg per the 'if' on line 79, which is wrong -- a deletion at a cursor position outside the scroll region should not modify the bottom margin line.","fix":"Add a guard at the start of wdeleteln(): if win->_cury > win->_bmarg, return OK (or ERR) without modifying the window, since a delete at a position outside the scrolling region is a no-op.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/deleteln.c:53-87","code":" 53 | int wdeleteln(WINDOW *win)\n 54 | {\n 55 | chtype blank, *temp, *ptr;\n 56 | int y;\n 57 | \n 58 | PDC_LOG((\"wdeleteln() - called\\n\"));\n 59 | \n 60 | if (!win)\n 61 | return ERR;\n 62 | \n 63 | /* wrs (4/10/93) account for window background */\n 64 | \n 65 | blank = win->_bkgd;\n 66 | \n 67 | temp = win->_y[win->_cury];\n 68 | \n 69 | for (y = win->_cury; y < win->_bmarg; y++)\n 70 | {\n 71 | win->_y[y] = win->_y[y + 1];\n 72 | win->_firstch[y] = 0;\n 73 | win->_lastch[y] = win->_maxx - 1;\n 74 | }\n 75 | \n 76 | for (ptr = temp; (ptr - temp < win->_maxx); ptr++)\n 77 | *ptr = blank; /* make a blank line */\n 78 | \n 79 | if (win->_cury <= win->_bmarg)\n 80 | {\n 81 | win->_firstch[win->_bmarg] = 0;\n 82 | win->_lastch[win->_bmarg] = win->_maxx - 1;\n 83 | win->_y[win->_bmarg] = temp;\n 84 | }\n 85 | \n 86 | return OK;\n 87 | }","lines":35,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/deleteln.c#L53-L87"}]},
{"id":"LOGIC_25","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In getch.c, the wgetch() function uses a static local buffer 'static int buffer[_INBUFSIZ]' (line 159) shared across all calls and all windows. This means input buffering is global state, which is problematic if multiple windows or threads call wgetch() concurrently. More critically, the static buffer state (c_pindex, c_gindex) is also file-static (lines 87-88), so they are shared across all windows. This is not thread-safe and could lead to data races.","fix":"Move the buffer and its indices into the SCREEN struct (SP) or document that wgetch() is not re-entrant.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/getch.c:84-90","code":" 84 | #define _INBUFSIZ 512 /* size of terminal input buffer */\n 85 | #define NUNGETCH 256 /* max # chars to ungetch() */\n 86 | \n 87 | static int c_pindex = 0; /* putter index */\n 88 | static int c_gindex = 1; /* getter index */\n 89 | static int c_ungind = 0; /* ungetch() push index */\n 90 | static int c_ungch[NUNGETCH]; /* array of ungotten chars */","lines":7,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/getch.c#L84-L90"},{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/getch.c:157-159","code":" 157 | int wgetch(WINDOW *win)\n 158 | {\n 159 | static int buffer[_INBUFSIZ]; /* character buffer */","lines":3,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/getch.c#L157-L159"}]},
{"id":"LOGIC_26","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In inchstr.c, winchnstr() always writes a terminating (chtype)0 sentinel at ch[n] (line 77), even when n has been clamped to 'win->_maxx - win->_curx'. If the caller passed a buffer of exactly n elements (i.e., n chtype slots), the NUL write goes one past the end. The same pattern appears throughout the callers (inchstr passes maxx-curx as n, then winchnstr writes n+1 elements). The function documentation says 'ending at the right margin, or after n elements' -- callers must provide n+1 allocation, which is not documented.","fix":"Document that the caller must allocate n+1 chtype elements to accommodate the terminating zero, or reduce the copy count by one before writing the sentinel.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/inchstr.c:59-80","code":" 59 | int winchnstr(WINDOW *win, chtype *ch, int n)\n 60 | {\n 61 | chtype *src;\n 62 | int i;\n 63 | \n 64 | PDC_LOG((\"winchnstr() - called\\n\"));\n 65 | \n 66 | if (!win || !ch || n < 0)\n 67 | return ERR;\n 68 | \n 69 | if ((win->_curx + n) > win->_maxx)\n 70 | n = win->_maxx - win->_curx;\n 71 | \n 72 | src = win->_y[win->_cury] + win->_curx;\n 73 | \n 74 | for (i = 0; i < n; i++)\n 75 | *ch++ = *src++;\n 76 | \n 77 | *ch = (chtype)0;\n 78 | \n 79 | return OK;\n 80 | }","lines":22,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/inchstr.c#L59-L80"}]},
{"id":"LOGIC_27","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In initscr.c, the sprintf() call at line 224 writes into the fixed-size global ttytype[128] with the format 'pdcurses|PDCurses for %s'. If PDC_sysname() returns a string longer than 109 characters (128 minus the 19-character prefix), the buffer overflows. This is a stack-global overflow. No bounds are enforced.","fix":"Use snprintf(ttytype, sizeof(ttytype), \"pdcurses|PDCurses for %s\", PDC_sysname()) to prevent overflow.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/initscr.c:93","code":" 93 | char ttytype[128];","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/initscr.c#L93"},{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/initscr.c:224","code":" 224 | sprintf(ttytype, \"pdcurses|PDCurses for %s\", PDC_sysname());","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/initscr.c#L224"}]},
{"id":"LOGIC_28","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In initscr.c, if newwin() for curscr succeeds but newwin() for pdc_lastscr fails, the code calls exit(2) without freeing the memory allocated for curscr. Similarly, if stdscr allocation fails, both curscr and pdc_lastscr are leaked. This is an early-boot firmware context where memory leaks before exit matter less, but if PDC_scr_open or later allocations fail, the partially-initialized SP struct may be accessed by atexit handlers or destructors.","fix":"Free previously allocated windows before calling exit(), or use a common cleanup path.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/initscr.c:157-196","code":" 157 | if ((curscr = newwin(LINES, COLS, 0, 0)) == (WINDOW *)NULL)\n 158 | {\n 159 | fprintf(stderr, \"initscr(): Unable to create curscr.\\n\");\n 160 | exit(2);\n 161 | }\n 162 | \n 163 | if ((pdc_lastscr = newwin(LINES, COLS, 0, 0)) == (WINDOW *)NULL)\n 164 | {\n 165 | fprintf(stderr, \"initscr(): Unable to create pdc_lastscr.\\n\");\n 166 | exit(2);\n 167 | }\n 168 | \n 169 | wattrset(pdc_lastscr, (chtype)(-1));\n 170 | werase(pdc_lastscr);\n 171 | \n 172 | PDC_slk_initialize();\n 173 | LINES -= SP->slklines;\n 174 | \n 175 | /* We have to sort out ripped off lines here, and reduce the height\n 176 | of stdscr by the number of lines ripped off */\n 177 | \n 178 | for (i = 0; i < linesrippedoff; i++)\n 179 | {\n 180 | if (linesripped[i].line < 0)\n 181 | (*linesripped[i].init)(newwin(1, COLS, LINES - 1, 0), COLS);\n 182 | else\n 183 | (*linesripped[i].init)(newwin(1, COLS,\n 184 | SP->linesrippedoffontop++, 0), COLS);\n 185 | \n 186 | SP->linesrippedoff++;\n 187 | LINES--;\n 188 | }\n 189 | \n 190 | linesrippedoff = 0;\n 191 | \n 192 | if (!(stdscr = newwin(LINES, COLS, SP->linesrippedoffontop, 0)))\n 193 | {\n 194 | fprintf(stderr, \"initscr(): Unable to create stdscr.\\n\");\n 195 | exit(1);\n 196 | }","lines":40,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/initscr.c#L157-L196"}]},
{"id":"LOGIC_29","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In insstr.c (non-wide path), the n clamping logic is inverted: 'if (n < 0 || n < len) n = len;' (line 83). This means when n is a positive value less than the string length (the caller's intent is to insert at most n characters), it is silently overridden to len, inserting the entire string. The correct behavior for winsnstr() is to insert at most n characters; n should only be set to len when n is negative (meaning 'no limit'). When n is a valid positive limit smaller than the string length, it should be respected.","fix":"Change the condition to 'if (n < 0 || n > len) n = len;' so that a caller-supplied n that is smaller than len is honored as an upper bound.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/insstr.c:81-84","code":" 81 | len = strlen(str);\n 82 | \n 83 | if (n < 0 || n < len)\n 84 | n = len;","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/insstr.c#L81-L84"}]},
{"id":"LOGIC_30","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"The same inverted n clamping exists in wins_nwstr() for the wide-character path (lines 191-192): 'if (n < 0 || n < len) n = len;'. Same effect: a positive n smaller than the wide string length is ignored, and the full string is always inserted.","fix":"Change to 'if (n < 0 || n > len) n = len;'.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/insstr.c:188-198","code":" 188 | for (len = 0, p = wstr; *p; p++)\n 189 | len++;\n 190 | \n 191 | if (n < 0 || n < len)\n 192 | n = len;\n 193 | \n 194 | while (n)\n 195 | if (winsch(win, wstr[--n]) == ERR)\n 196 | return ERR;\n 197 | \n 198 | return OK;","lines":11,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/insstr.c#L188-L198"}]},
{"id":"LOGIC_31","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In keyname.c, the keyname() function accesses key_name[key - KEY_MIN] (line 108) after confirming has_key(key) returns TRUE (i.e., key is in [KEY_MIN, KEY_MAX]). However, has_key() returns TRUE for any key in that range, but the key_name[] array has a fixed number of entries. If KEY_MAX - KEY_MIN + 1 > number of entries in key_name[], an out-of-bounds array access occurs. The table must be kept exactly in sync with the key range defined in curses.h, which is fragile and not compile-time checked.","fix":"Add a compile-time assertion that ARRAY_SIZE(key_name) == KEY_MAX - KEY_MIN + 1, or add a runtime bounds check before indexing.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/keyname.c:35-108","code":" 35 | const char *keyname(int key)\n 36 | {\n 37 | /* Key names must be in exactly the same order as in curses.h */\n 38 | \n 39 | static const char *key_name[] =\n 40 | {\n 41 | \"KEY_BREAK\", \"KEY_DOWN\", \"KEY_UP\", \"KEY_LEFT\", \"KEY_RIGHT\",\n 42 | \"KEY_HOME\", \"KEY_BACKSPACE\", \"KEY_F0\", \"KEY_F(1)\", \"KEY_F(2)\",\n 43 | \"KEY_F(3)\", \"KEY_F(4)\", \"KEY_F(5)\", \"KEY_F(6)\", \"KEY_F(7)\",\n 44 | \"KEY_F(8)\", \"KEY_F(9)\", \"KEY_F(10)\", \"KEY_F(11)\", \"KEY_F(12)\",\n 45 | \"KEY_F(13)\", \"KEY_F(14)\", \"KEY_F(15)\", \"KEY_F(16)\", \"KEY_F(17)\",\n 46 | \"KEY_F(18)\", \"KEY_F(19)\", \"KEY_F(20)\", \"KEY_F(21)\", \"KEY_F(22)\",\n 47 | \"KEY_F(23)\", \"KEY_F(24)\", \"KEY_F(25)\", \"KEY_F(26)\", \"KEY_F(27)\",\n 48 | \"KEY_F(28)\", \"KEY_F(29)\", \"KEY_F(30)\", \"KEY_F(31)\", \"KEY_F(32)\",\n 49 | \"KEY_F(33)\", \"KEY_F(34)\", \"KEY_F(35)\", \"KEY_F(36)\", \"KEY_F(37)\",\n 50 | \"KEY_F(38)\", \"KEY_F(39)\", \"KEY_F(40)\", \"KEY_F(41)\", \"KEY_F(42)\",\n 51 | \"KEY_F(43)\", \"KEY_F(44)\", \"KEY_F(45)\", \"KEY_F(46)\", \"KEY_F(47)\",\n 52 | \"KEY_F(48)\", \"KEY_F(49)\", \"KEY_F(50)\", \"KEY_F(51)\", \"KEY_F(52)\",\n 53 | \"KEY_F(53)\", \"KEY_F(54)\", \"KEY_F(55)\", \"KEY_F(56)\", \"KEY_F(57)\",\n 54 | \"KEY_F(58)\", \"KEY_F(59)\", \"KEY_F(60)\", \"KEY_F(61)\", \"KEY_F(62)\",\n 55 | \"KEY_F(63)\", \"KEY_DL\", \"KEY_IL\", \"KEY_DC\", \"KEY_IC\", \"KEY_EIC\",\n 56 | \"KEY_CLEAR\", \"KEY_EOS\", \"KEY_EOL\", \"KEY_SF\", \"KEY_SR\",\n 57 | \"KEY_NPAGE\", \"KEY_PPAGE\", \"KEY_STAB\", \"KEY_CTAB\", \"KEY_CATAB\",\n 58 | \"KEY_ENTER\", \"KEY_SRESET\", \"KEY_RESET\", \"KEY_PRINT\", \"KEY_LL\",\n 59 | \"KEY_ABORT\", \"KEY_SHELP\", \"KEY_LHELP\", \"KEY_BTAB\", \"KEY_BEG\",\n 60 | \"KEY_CANCEL\", \"KEY_CLOSE\", \"KEY_COMMAND\", \"KEY_COPY\",\n 61 | \"KEY_CREATE\", \"KEY_END\", \"KEY_EXIT\", \"KEY_FIND\", \"KEY_HELP\",\n 62 | \"KEY_MARK\", \"KEY_MESSAGE\", \"KEY_MOVE\", \"KEY_NEXT\", \"KEY_OPEN\",\n 63 | \"KEY_OPTIONS\", \"KEY_PREVIOUS\", \"KEY_REDO\", \"KEY_REFERENCE\",\n 64 | \"KEY_REFRESH\", \"KEY_REPLACE\", \"KEY_RESTART\", \"KEY_RESUME\",\n 65 | \"KEY_SAVE\", \"KEY_SBEG\", \"KEY_SCANCEL\", \"KEY_SCOMMAND\",\n 66 | \"KEY_SCOPY\", \"KEY_SCREATE\", \"KEY_SDC\", \"KEY_SDL\", \"KEY_SELECT\",\n 67 | \"KEY_SEND\", \"KEY_SEOL\", \"KEY_SEXIT\", \"KEY_SFIND\", \"KEY_SHOME\",\n 68 | \"KEY_SIC\", \"UNKNOWN KEY\", \"KEY_SLEFT\", \"KEY_SMESSAGE\",\n 69 | \"KEY_SMOVE\", \"KEY_SNEXT\", \"KEY_SOPTIONS\", \"KEY_SPREVIOUS\",\n 70 | \"KEY_SPRINT\", \"KEY_SREDO\", \"KEY_SREPLACE\", \"KEY_SRIGHT\",\n 71 | \"KEY_SRSUME\", \"KEY_SSAVE\", \"KEY_SSUSPEND\", \"KEY_SUNDO\",\n 72 | \"KEY_SUSPEND\", \"KEY_UNDO\", \"ALT_0\", \"ALT_1\", \"ALT_2\", \"ALT_3\",\n 73 | \"ALT_4\", \"ALT_5\", \"ALT_6\", \"ALT_7\", \"ALT_8\", \"ALT_9\", \"ALT_A\",\n 74 | \"ALT_B\", \"ALT_C\", \"ALT_D\", \"ALT_E\", \"ALT_F\", \"ALT_G\", \"ALT_H\",\n 75 | \"ALT_I\", \"ALT_J\", \"ALT_K\", \"ALT_L\", \"ALT_M\", \"ALT_N\", \"ALT_O\",\n 76 | \"ALT_P\", \"ALT_Q\", \"ALT_R\", \"ALT_S\", \"ALT_T\", \"ALT_U\", \"ALT_V\",\n 77 | \"ALT_W\", \"ALT_X\", \"ALT_Y\", \"ALT_Z\", \"CTL_LEFT\", \"CTL_RIGHT\",\n 78 | \"CTL_PGUP\", \"CTL_PGDN\", \"CTL_HOME\", \"CTL_END\", \"KEY_A1\",\n 79 | \"KEY_A2\", \"KEY_A3\", \"KEY_B1\", \"KEY_B2\", \"KEY_B3\", \"KEY_C1\",\n 80 | \"KEY_C2\", \"KEY_C3\", \"PADSLASH\", \"PADENTER\", \"CTL_PADENTER\",\n 81 | \"ALT_PADENTER\", \"PADSTOP\", \"PADSTAR\", \"PADMINUS\", \"PADPLUS\",\n 82 | \"CTL_PADSTOP\", \"CTL_PADCENTER\", \"CTL_PADPLUS\", \"CTL_PADMINUS\",\n 83 | \"CTL_PADSLASH\", \"CTL_PADSTAR\", \"ALT_PADPLUS\", \"ALT_PADMINUS\",\n 84 | \"ALT_PADSLASH\", \"ALT_PADSTAR\", \"ALT_PADSTOP\", \"CTL_INS\",\n 85 | \"ALT_DEL\", \"ALT_INS\", \"CTL_UP\", \"CTL_DOWN\", \"CTL_TAB\",\n 86 | \"ALT_TAB\", \"ALT_MINUS\", \"ALT_EQUAL\", \"ALT_HOME\", \"ALT_PGUP\",\n 87 | \"ALT_PGDN\", \"ALT_END\", \"ALT_UP\", \"ALT_DOWN\", \"ALT_RIGHT\",\n 88 | \"ALT_LEFT\", \"ALT_ENTER\", \"ALT_ESC\", \"ALT_BQUOTE\",\n 89 | \"ALT_LBRACKET\", \"ALT_RBRACKET\", \"ALT_SEMICOLON\", \"ALT_FQUOTE\",\n 90 | \"ALT_COMMA\", \"ALT_STOP\", \"ALT_FSLASH\", \"ALT_BKSP\", \"CTL_BKSP\",\n 91 | \"PAD0\", \"CTL_PAD0\", \"CTL_PAD1\", \"CTL_PAD2\", \"CTL_PAD3\",\n 92 | \"CTL_PAD4\", \"CTL_PAD5\", \"CTL_PAD6\", \"CTL_PAD7\",\"CTL_PAD8\",\n 93 | \"CTL_PAD9\", \"ALT_PAD0\", \"ALT_PAD1\", \"ALT_PAD2\", \"ALT_PAD3\",\n 94 | \"ALT_PAD4\", \"ALT_PAD5\", \"ALT_PAD6\", \"ALT_PAD7\", \"ALT_PAD8\",\n 95 | \"ALT_PAD9\", \"CTL_DEL\", \"ALT_BSLASH\", \"CTL_ENTER\",\n 96 | \"SHF_PADENTER\", \"SHF_PADSLASH\", \"SHF_PADSTAR\", \"SHF_PADPLUS\",\n 97 | \"SHF_PADMINUS\", \"SHF_UP\", \"SHF_DOWN\", \"SHF_IC\", \"SHF_DC\",\n 98 | \"KEY_MOUSE\", \"KEY_SHIFT_L\", \"KEY_SHIFT_R\", \"KEY_CONTROL_L\",\n 99 | \"KEY_CONTROL_R\", \"KEY_ALT_L\", \"KEY_ALT_R\", \"KEY_RESIZE\",\n 100 | \"KEY_SUP\", \"KEY_SDOWN\"\n 101 | };\n 102 | \n 103 | PDC_LOG((\"keyname() - called: key %d\\n\", key));\n 104 | \n 105 | if ((key >= 0) && (key < 0x80))\n 106 | return unctrl((chtype)key);\n 107 | \n 108 | return has_key(key) ? key_name[key - KEY_MIN] : \"UNKNOWN KEY\";","lines":74,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/keyname.c#L35-L108"}]},
{"id":"LOGIC_32","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"_calculate_obscure() returns immediately when a malloc() for a PANELOBS node fails (line 272), leaving a partially-constructed obscure list on the current panel and leaving all subsequent panels with their old (stale) obscure lists. The early return means _override(pan, 1) is not called for the current panel and no panels below it in the stack get their obscure lists rebuilt. This leads to incorrect panel overdraw tracking: overlapping areas may not be redrawn when they should be, causing visual corruption.","fix":"On malloc failure, free any PANELOBS nodes already allocated for the current panel (reset pan->obscure and free the partial list), then return or propagate the error. Consider returning an error code from _calculate_obscure() so callers can handle it.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/panel.c:251-292","code":" 251 | static void _calculate_obscure(void)\n 252 | {\n 253 | PANEL *pan, *pan2;\n 254 | PANELOBS *tobs; /* \"this\" one */\n 255 | PANELOBS *lobs; /* last one */\n 256 | \n 257 | pan = _bottom_panel;\n 258 | \n 259 | while (pan)\n 260 | {\n 261 | if (pan->obscure)\n 262 | _free_obscure(pan);\n 263 | \n 264 | lobs = (PANELOBS *)0;\n 265 | pan2 = _bottom_panel;\n 266 | \n 267 | while (pan2)\n 268 | {\n 269 | if (_panels_overlapped(pan, pan2))\n 270 | {\n 271 | if ((tobs = malloc(sizeof(PANELOBS))) == NULL)\n 272 | return;\n 273 | \n 274 | tobs->pan = pan2;\n 275 | dPanel(\"obscured\", pan2);\n 276 | tobs->above = (PANELOBS *)0;\n 277 | \n 278 | if (lobs)\n 279 | lobs->above = tobs;\n 280 | else\n 281 | pan->obscure = tobs;\n 282 | \n 283 | lobs = tobs;\n 284 | }\n 285 | \n 286 | pan2 = pan2->above;\n 287 | }\n 288 | \n 289 | _override(pan, 1);\n 290 | pan = pan->above;\n 291 | }\n 292 | }","lines":42,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/panel.c#L251-L292"}]},
{"id":"LOGIC_33","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"panel_window() dereferences 'pan' without a NULL check, while every other panel function that takes a PANEL pointer begins with 'if (!pan) return ERR'. Passing NULL to panel_window() causes a NULL pointer dereference crash. The POSIX panel specification requires panel_window() to return NULL on error, implying it should handle NULL input gracefully.","fix":"Add a NULL guard: 'if (!pan) return NULL;' before accessing pan->win.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/panel.c:545-550","code":" 545 | WINDOW *panel_window(const PANEL *pan)\n 546 | {\n 547 | PDC_LOG((\"panel_window() - called\\n\"));\n 548 | \n 549 | return pan->win;\n 550 | }","lines":6,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/panel.c#L545-L550"}]},
{"id":"LOGIC_34","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In sdl1/pdcdisp.c PDC_transform_line(), before the main rendering loop the\ncode computes and queues a single wide rectangle covering all `len` columns (lines 254-275).\nHowever, the rectangle merging logic at lines 266-275 can append `dest` (full-width rectangle)\nto uprect without first checking whether rectcount has already reached MAXRECT. The MAXRECT\nguard at line 248-249 only flushes before starting, but after the flush rectcount is 0 and\na new entry is added unconditionally. Inside the per-character loop at line 272, rectcount\nis incremented without a bounds check. If len is large and many unmerged rects accumulate,\nrectcount can overflow the uprect[MAXRECT] array.","fix":"Before each `uprect[rectcount++] = dest;` assignment (lines 272 and 275),\ncheck `if (rectcount < MAXRECT)` or call PDC_update_rects() to flush first.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/sdl1/pdcdisp.c:241-301","code":" 241 | void PDC_transform_line(int lineno, int x, int len, const chtype *srcp)\n 242 | {\n 243 | SDL_Rect src, dest, lastrect;\n 244 | int j;\n 245 | \n 246 | PDC_LOG((\"PDC_transform_line() - called: lineno=%d\\n\", lineno));\n 247 | \n 248 | if (rectcount == MAXRECT)\n 249 | PDC_update_rects();\n 250 | \n 251 | src.h = pdc_fheight;\n 252 | src.w = pdc_fwidth;\n 253 | \n 254 | dest.y = pdc_fheight * lineno + pdc_yoffset;\n 255 | dest.x = pdc_fwidth * x + pdc_xoffset;\n 256 | dest.h = pdc_fheight;\n 257 | dest.w = pdc_fwidth * len;\n 258 | \n 259 | /* if the previous rect was just above this one, with the same width\n 260 | and horizontal position, then merge the new one with it instead\n 261 | of adding a new entry */\n 262 | \n 263 | if (rectcount)\n 264 | lastrect = uprect[rectcount - 1];\n 265 | \n 266 | if (rectcount && lastrect.x == dest.x && lastrect.w == dest.w)\n 267 | {\n 268 | if (lastrect.y + lastrect.h == dest.y)\n 269 | uprect[rectcount - 1].h = lastrect.h + pdc_fheight;\n 270 | else\n 271 | if (lastrect.y != dest.y)\n 272 | uprect[rectcount++] = dest;\n 273 | }\n 274 | else\n 275 | uprect[rectcount++] = dest;\n 276 | \n 277 | dest.w = pdc_fwidth;\n 278 | \n 279 | for (j = 0; j < len; j++)\n 280 | {\n 281 | chtype ch = srcp[j];\n 282 | \n 283 | _set_attr(ch);\n 284 | #ifdef CHTYPE_LONG\n 285 | if (ch & A_ALTCHARSET && !(ch & 0xff80))\n 286 | ch = (ch & (A_ATTRIBUTES ^ A_ALTCHARSET)) | acs_map[ch & 0x7f];\n 287 | #endif\n 288 | if (backgr == -1)\n 289 | SDL_LowerBlit(pdc_tileback, &dest, pdc_screen, &dest);\n 290 | \n 291 | src.x = (ch & 0xff) % 32 * pdc_fwidth;\n 292 | src.y = (ch & 0xff) / 32 * pdc_fheight;\n 293 | \n 294 | SDL_LowerBlit(pdc_font, &src, pdc_screen, &dest);\n 295 | \n 296 | if (ch & (A_UNDERLINE|A_LEFTLINE|A_RIGHTLINE))\n 297 | _highlight(&src, &dest, ch);\n 298 | \n 299 | dest.x += pdc_fwidth;\n 300 | }\n 301 | }","lines":61,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/sdl1/pdcdisp.c#L241-L301"}]},
{"id":"LOGIC_35","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"PDC_get_rows() and PDC_get_columns() in sdl1/pdcgetsc.c divide by pdc_fheight\nand pdc_fwidth respectively (lines 22, 31). These are set in PDC_scr_open() from the font surface\ndimensions. If the font surface dimensions are not multiples of 8 (fheight) or 32 (fwidth) the\nfont character cells will be zero-sized (pdc_fheight = font->h / 8, pdc_fwidth = font->w / 32).\nA zero divisor causes a division-by-zero crash. The same issue applies to PDC_gotoyx and\nPDC_transform_line which use these values in arithmetic.","fix":"After computing pdc_fheight and pdc_fwidth (pdcscrn.c lines 123-124), assert or\ncheck that both are > 0 and return ERR if not, to prevent later division-by-zero crashes.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/sdl1/pdcgetsc.c:18-32","code":" 18 | int PDC_get_rows(void)\n 19 | {\n 20 | PDC_LOG((\"PDC_get_rows() - called\\n\"));\n 21 | \n 22 | return pdc_sheight / pdc_fheight;\n 23 | }\n 24 | \n 25 | /* return width of screen/viewport */\n 26 | \n 27 | int PDC_get_columns(void)\n 28 | {\n 29 | PDC_LOG((\"PDC_get_columns() - called\\n\"));\n 30 | \n 31 | return pdc_swidth / pdc_fwidth;\n 32 | }","lines":15,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/sdl1/pdcgetsc.c#L18-L32"},{"ref":"payloads/libpayload/curses/PDCurses/sdl1/pdcscrn.c:123-124","code":" 123 | pdc_fheight = pdc_font->h / 8;\n 124 | pdc_fwidth = pdc_font->w / 32;","lines":2,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/sdl1/pdcscrn.c#L123-L124"}]},
{"id":"LOGIC_36","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"very_easy","description":"free_form calls Disconnect_Fields(form) which already frees form->page (frm_def.c:138-140) and sets it to NULL. Immediately after, free_form checks 'if (form->page) free(form->page)' - which is unreachable because Disconnect_Fields just set it to NULL. The check is dead code, but it is misleading because if the NULL assignment inside Disconnect_Fields were ever removed the code would double-free.","fix":"Remove the redundant 'if (form->page) free(form->page);' block in free_form (lines 375-376). Disconnect_Fields already owns the page lifetime.","locations":[{"ref":"payloads/libpayload/curses/form/frm_def.c:374-377","code":" 374 | Disconnect_Fields(form);\n 375 | if (form->page)\n 376 | free(form->page);\n 377 | free(form);","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/frm_def.c#L374-L377"},{"ref":"payloads/libpayload/curses/form/frm_def.c:138-140","code":" 138 | if (form->page)\n 139 | \tfree(form->page);\n 140 | form->page = (_PAGE *) 0;","lines":3,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/frm_def.c#L138-L140"}]},
{"id":"LOGIC_37","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"FE_Delete_Previous uses field->cols instead of field->dcols when deciding whether the current line's content fits on the previous line after a merge (line 2762-2763: 'field->cols - (int)(prev_end - prev_line)'). For dynamically grown fields where dcols > cols, this underestimates available space and may incorrectly deny a valid delete-previous operation, causing a confusing E_REQUEST_DENIED to the user.","fix":"Change 'field->cols - (int)(prev_end - prev_line)' to 'field->dcols - (int)(prev_end - prev_line)' to correctly account for the actual dynamic column width.","locations":[{"ref":"payloads/libpayload/curses/form/frm_driver.c:2762-2764","code":" 2762 | if ((int)(this_end - this_line) >\n 2763 | \t (field->cols - (int)(prev_end - prev_line)))\n 2764 | \treturnCode(E_REQUEST_DENIED);","lines":3,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/frm_driver.c#L2762-L2764"}]},
{"id":"LOGIC_38","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In frm_req_name.c, form_request_by_name uses strncpy(buf, str, sizeof(buf)) where\nbuf is 16 bytes. If 'str' is 16 or more characters long, strncpy does NOT append a\nnull terminator, leaving buf unterminated. The subsequent toupper loop correctly\nstops at sizeof(buf) iterations and does not overrun the buffer, but the final\nstrncmp(request_names[i], buf, sizeof(buf)) compares 16 bytes of an unterminated\n'buf'. While this is technically bounded by sizeof(buf), a string of exactly 16\ncharacters would incorrectly match a request name that shares the first 16 bytes,\neven if they differ after that. More importantly, any string >= 16 chars loses\nits suffix silently without the caller being notified.","fix":"Force null termination after strncpy:\n strncpy(buf, str, sizeof(buf) - 1);\n buf[sizeof(buf) - 1] = '\\0';\nThis ensures buf is always null-terminated before the loop and strncmp.","locations":[{"ref":"payloads/libpayload/curses/form/frm_req_name.c:147-165","code":" 147 | unsigned int i = 0;\n 148 | char buf[16];\n 149 | \n 150 | T((T_CALLED(\"form_request_by_name(%s)\"), _nc_visbuf(str)));\n 151 | \n 152 | if (str)\n 153 | {\n 154 | strncpy(buf, str, sizeof(buf));\n 155 | while ((i < sizeof(buf)) && (buf[i] != '\\0'))\n 156 | \t{\n 157 | \t buf[i] = (char)toupper(UChar(buf[i]));\n 158 | \t i++;\n 159 | \t}\n 160 | \n 161 | for (i = 0; i < A_SIZE; i++)\n 162 | \t{\n 163 | \t if (strncmp(request_names[i], buf, sizeof(buf)) == 0)\n 164 | \t returnCode(MIN_FORM_COMMAND + (int)i);\n 165 | \t}","lines":19,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/frm_req_name.c#L147-L165"}]},
{"id":"LOGIC_39","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"very_easy","description":"In m_opts.c set_menu_opts(), opts is masked with ALL_MENU_OPTS on line 60 before the dead-code guard on line 62. The check `opts & ~ALL_MENU_OPTS` always evaluates to 0/false because all unrecognized bits were already cleared by the mask on the previous line, so E_BAD_ARGUMENT is never returned for invalid option bits. The dead check provides false confidence about input validation.","fix":"Remove the masking on line 60 and let the check on line 62 fire first, then apply the mask only after validation: `if (opts & ~ALL_MENU_OPTS) RETURN(E_BAD_ARGUMENT); opts &= ALL_MENU_OPTS;`","locations":[{"ref":"payloads/libpayload/curses/menu/m_opts.c:60-63","code":" 60 | opts &= ALL_MENU_OPTS;\n 61 | \n 62 | if (opts & ~ALL_MENU_OPTS)\n 63 | RETURN(E_BAD_ARGUMENT);","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/menu/m_opts.c#L60-L63"}]},
{"id":"LOGIC_40","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"very_easy","description":"In m_opts.c menu_opts_off() and menu_opts_on(), the same pre-masking-then-dead-check pattern is repeated (lines 124-126 and 157-159). The `opts & ~ALL_MENU_OPTS` test is always false after `opts &= ALL_MENU_OPTS`. This is the same issue as in set_menu_opts().","fix":"Validate before masking: check `opts & ~ALL_MENU_OPTS` first, return E_BAD_ARGUMENT if nonzero, then apply the mask.","locations":[{"ref":"payloads/libpayload/curses/menu/m_opts.c:124-126","code":" 124 | opts &= ALL_MENU_OPTS;\n 125 | if (opts & ~ALL_MENU_OPTS)\n 126 | RETURN(E_BAD_ARGUMENT);","lines":3,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/menu/m_opts.c#L124-L126"},{"ref":"payloads/libpayload/curses/menu/m_opts.c:157-159","code":" 157 | opts &= ALL_MENU_OPTS;\n 158 | if (opts & ~ALL_MENU_OPTS)\n 159 | RETURN(E_BAD_ARGUMENT);","lines":3,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/menu/m_opts.c#L157-L159"}]},
{"id":"LOGIC_41","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In pdcdisp.c PDC_transform_line(), the serial console path (lines 152-153) calls `serial_set_cursor(lineno, x)` with arguments (lineno=row, x=col) swapped compared to the expected (col, row) order used in PDC_gotoyx(). PDC_gotoyx() calls `serial_set_cursor(col, row)` with col first. The transform_line call passes row as first arg and col as second, which is reversed. This would position the serial cursor incorrectly at the start of each transformed line.","fix":"Change line 152 to `serial_set_cursor(x, lineno);` to match the (col, row) convention used by PDC_gotoyx().","locations":[{"ref":"payloads/libpayload/curses/pdcurses-backend/pdcdisp.c:149-154","code":" 149 | if (curses_flags & F_ENABLE_SERIAL) {\n 150 | serial_end_bold();\n 151 | serial_end_altcharset();\n 152 | serial_set_cursor(lineno, x);\n 153 | }\n 154 | #endif","lines":6,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/pdcurses-backend/pdcdisp.c#L149-L154"}]},
{"id":"LOGIC_42","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In pdckbd.c handle_escape(), the escape sequence matching loop (lines 71-84) checks `if (t == len)` to declare a match, but this only confirms that all `len` characters of the buffer matched the first `len` characters of the escape code sequence. If a sequence in escape_codes[] is longer than `len` (i.e., `*p` is not '\\0' at position t), the loop breaks early and still declares a match, because the exit condition `t == len` fires before exhausting the pattern. This can cause partial matches: e.g., a 2-char buffer `[A` would match `[17~` at the 2-char prefix level if a code in the table had `[A` as a prefix of a longer sequence.","fix":"After the inner loop, also check that `*p == '\\0'` to confirm the full pattern was consumed: `if (t == len && *p == '\\0') return escape_codes[i].key;`","locations":[{"ref":"payloads/libpayload/curses/pdcurses-backend/pdckbd.c:71-84","code":" 71 | \tfor(i = 0; escape_codes[i].seq != NULL; i++) {\n 72 | \t\tconst char *p = escape_codes[i].seq;\n 73 | \n 74 | \t\tfor(t = 0; t < len; t++) {\n 75 | \t\t\tif (!*p || *p != buffer[t])\n 76 | \t\t\t\tbreak;\n 77 | \t\t\tp++;\n 78 | \t\t}\n 79 | \n 80 | \t\tif (t == len)\n 81 | \t\t\treturn escape_codes[i].key;\n 82 | \t}\n 83 | \n 84 | \treturn 0;","lines":14,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/pdcurses-backend/pdckbd.c#L71-L84"}]},
{"id":"LOGIC_43","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In pdcscrn.c PDC_init_pair(), the parameter `pair` is a `short` and is used as `pdc_atrtab[pair * PDC_OFFSET + i]` without bounds checking. If `pair` is negative or >= PDC_COLOR_PAIRS, this accesses pdc_atrtab out of bounds. pdc_atrtab is allocated as `PDC_COLOR_PAIRS * PDC_OFFSET` bytes but there is no validation of the pair argument.","fix":"Add `if (pair < 0 || pair >= PDC_COLOR_PAIRS) return;` at the start of PDC_init_pair().","locations":[{"ref":"payloads/libpayload/curses/pdcurses-backend/pdcscrn.c:110-137","code":" 110 | void PDC_init_pair(short pair, short fg, short bg)\n 111 | {\n 112 | unsigned char att, temp_bg;\n 113 | chtype i;\n 114 | \n 115 | fg = curstoreal[fg];\n 116 | bg = curstoreal[bg];\n 117 | \n 118 | for (i = 0; i < PDC_OFFSET; i++)\n 119 | {\n 120 | att = fg | (bg << 4);\n 121 | \n 122 | if (i & (A_REVERSE >> PDC_ATTR_SHIFT))\n 123 | att = bg | (fg << 4);\n 124 | if (i & (A_UNDERLINE >> PDC_ATTR_SHIFT))\n 125 | att = 1;\n 126 | if (i & (A_INVIS >> PDC_ATTR_SHIFT))\n 127 | {\n 128 | temp_bg = att >> 4;\n 129 | att = temp_bg << 4 | temp_bg;\n 130 | }\n 131 | if (i & (A_BOLD >> PDC_ATTR_SHIFT))\n 132 | att |= 8;\n 133 | if (i & (A_BLINK >> PDC_ATTR_SHIFT))\n 134 | att |= 128;\n 135 | \n 136 | pdc_atrtab[pair * PDC_OFFSET + i] = att;\n 137 | }","lines":28,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/pdcurses-backend/pdcscrn.c#L110-L137"}]},
{"id":"LOGIC_44","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In pdcscrn.c PDC_pair_content(), the pair parameter is also unchecked, allowing out-of-bounds read from pdc_atrtab. Additionally, the return value of realtocurs[] indexing with `pdc_atrtab[...] & 0x0F` (or `>> 4`) is not bounds-checked before indexing realtocurs[16], which could be out of range if pdc_atrtab contains garbage from a failed or partial PDC_init_pair.","fix":"Validate pair in [0, PDC_COLOR_PAIRS) and validate the nibble values in [0, 15] before indexing realtocurs.","locations":[{"ref":"payloads/libpayload/curses/pdcurses-backend/pdcscrn.c:140-146","code":" 140 | int PDC_pair_content(short pair, short *fg, short *bg)\n 141 | {\n 142 | *fg = realtocurs[pdc_atrtab[pair * PDC_OFFSET] & 0x0F];\n 143 | *bg = realtocurs[(pdc_atrtab[pair * PDC_OFFSET] & 0xF0) >> 4];\n 144 | \n 145 | return OK;\n 146 | }","lines":7,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/pdcurses-backend/pdcscrn.c#L140-L146"}]},
{"id":"LOGIC_45","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In tinycurses.c wrefresh(), the function returns early on line 904 (`return wnoutrefresh(win);`) leaving all the code below (lines 907-927) unreachable dead code. The dead block references `curscr`, `newscr`, and `doupdate()` (commented out), and contains a `code` variable used without initialization in the surviving path after the goto. This is dead code that was never cleaned up and could mislead future maintainers.","fix":"Remove the dead code from line 907 to the end of wrefresh(), or if it is needed, remove the early return and implement doupdate() properly.","locations":[{"ref":"payloads/libpayload/curses/tinycurses.c:902-927","code":" 902 | int wrefresh(WINDOW *win)\n 903 | {\n 904 | \t// FIXME\n 905 | \treturn wnoutrefresh(win);\n 906 | \n 907 | \t// XXX\n 908 | \tint code;\n 909 | \n 910 | \tif (win == curscr) {\n 911 | \t\tcurscr->_clear = TRUE;\n 912 | \t\t// code = doupdate();\n 913 | \t} else if ((code = wnoutrefresh(win)) == OK) {\n 914 | \t\tif (win->_clear)\n 915 | \t\t\tnewscr->_clear = TRUE;\n 916 | \t\t// code = doupdate();\n 917 | \t\t/*\n 918 | \t\t * Reset the clearok() flag in case it was set for the special\n 919 | \t\t * case in hardscroll.c (if we don't reset it here, we'll get 2\n 920 | \t\t * refreshes because the flag is copied from stdscr to newscr).\n 921 | \t\t * Resetting the flag shouldn't do any harm, anyway.\n 922 | \t\t */\n 923 | \t\twin->_clear = FALSE;\n 924 | \t}\n 925 | \n 926 | \treturn code;\n 927 | }","lines":26,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/tinycurses.c#L902-L927"}]},
{"id":"LOGIC_46","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In tinycurses.c wscrl(), when n is negative (scroll down), the loop `for (y = 0; y <= (win->_maxy - n); y++)` with negative n would compute `win->_maxy - n > win->_maxy`, causing the loop to iterate past win->_maxy and access win->_line[y] out of bounds. The function returns ERR early only if `!win->_scroll`, not if `n < 0`, so negative n values lead to out-of-bounds array access.","fix":"Add `if (n < 0) return ERR;` (or implement reverse scrolling with proper bounds), and ensure the loop bound `win->_maxy - n` does not exceed the allocated array size.","locations":[{"ref":"payloads/libpayload/curses/tinycurses.c:929-975","code":" 929 | int wscrl(WINDOW *win, int n)\n 930 | {\n 931 | \tint x, y;\n 932 | \n 933 | \tif (!win->_scroll)\n 934 | \t\treturn ERR;\n 935 | \n 936 | \tif (n == 0)\n 937 | \t\treturn OK;\n 938 | \n 939 | \tfor (y = 0; y <= (win->_maxy - n); y++) {\n 940 | \t\twin->_line[y].firstchar = win->_line[y + n].firstchar;\n 941 | \t\twin->_line[y].lastchar = win->_line[y + n].lastchar;\n 942 | \t\tfor (x = 0; x <= win->_maxx; x++) {\n 943 | \t\t\tif ((win->_line[y].text[x].chars[0] != win->_line[y + n].text[x].chars[0]) ||\n 944 | \t\t\t\t\t(win->_line[y].text[x].attr != win->_line[y + n].text[x].attr)) {\n 945 | \t\t\t\tif (win->_line[y].firstchar == _NOCHANGE)\n 946 | \t\t\t\t\twin->_line[y].firstchar = x;\n 947 | \n 948 | \t\t\t\twin->_line[y].lastchar = x;\n 949 | \n 950 | \t\t\t\twin->_line[y].text[x].chars[0] = win->_line[y + n].text[x].chars[0];\n 951 | \t\t\t\twin->_line[y].text[x].attr = win->_line[y + n].text[x].attr;\n 952 | \t\t\t}\n 953 | \t\t}\n 954 | \t}\n 955 | \n 956 | \tfor (y = (win->_maxy+1 - n); y <= win->_maxy; y++) {\n 957 | \t\tfor (x = 0; x <= win->_maxx; x++) {\n 958 | \t\t\tif ((win->_line[y].text[x].chars[0] != ' ') ||\n 959 | \t\t\t\t\t(win->_line[y].text[x].attr != A_NORMAL)) {\n 960 | \t\t\t\tif (win->_line[y].firstchar == _NOCHANGE)\n 961 | \t\t\t\t\twin->_line[y].firstchar = x;\n 962 | \n 963 | \t\t\t\twin->_line[y].lastchar = x;\n 964 | \n 965 | \t\t\t\twin->_line[y].text[x].chars[0] = ' ';\n 966 | \t\t\t\twin->_line[y].text[x].attr = A_NORMAL;\n 967 | \t\t\t}\n 968 | \t\t}\n 969 | \t}\n 970 | \n 971 | \t// _nc_scroll_window(win, n, win->_regtop, win->_regbottom, win->_nc_bkgd);\n 972 | \t// _nc_synchook(win);\n 973 | \n 974 | \treturn OK;\n 975 | }","lines":47,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/tinycurses.c#L929-L975"}]},
{"id":"LOGIC_47","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In cbmem_console.c do_write(), the memcpy at line 52 writes `count` bytes starting at `cbmem_cons->body + (cbmem_cons->cursor & CURSOR_MASK)` without verifying that `(cbmem_cons->cursor & CURSOR_MASK) + count <= cbmem_cons->size`. cbmem_console_write() calls do_write only after verifying the condition, so in practice this is safe - but do_write itself has no safety check and could be misused if called directly. The bigger concern is that cbmem_console_write() calls do_write for the wrap-around partial write using `still_fits` as the count. If cbmem_cons->cursor has been corrupted externally (e.g., by coreboot writing to it simultaneously), still_fits could be > size, causing overflow.","fix":"Add an assert or clamp in do_write: `if ((cbmem_cons->cursor & CURSOR_MASK) + count > cbmem_cons->size) count = cbmem_cons->size - (cbmem_cons->cursor & CURSOR_MASK);`","locations":[{"ref":"payloads/libpayload/drivers/cbmem_console.c:48-54","code":" 48 | static void do_write(const void *buffer, size_t count)\n 49 | {\n 50 | \tstruct cbmem_console *const cbmem_cons = phys_to_virt(cbmem_console_p);\n 51 | \n 52 | \tmemcpy(cbmem_cons->body + (cbmem_cons->cursor & CURSOR_MASK), buffer, count);\n 53 | \tcbmem_cons->cursor += count;\n 54 | }","lines":7,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/cbmem_console.c#L48-L54"}]},
{"id":"LOGIC_48","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In cbmem_console.c cbmem_console_snapshot(), the overflow path reads `console_p->body[oldc]` for oldc in [cursor, size). Then the non-overflow path reads `console_p->body[oldc]` for oldc in [0, cursor). But newc (the write cursor into console_c) accumulates bytes from both passes without checking that newc < size+1 before each `snapshot_putc` call. snapshot_putc writes `console[*cursor] = c` without checking if `*cursor < size`. With an overflow console, size bytes are read from the body but snapshot_putc could write newc up to size because some chars are filtered (markers and non-printable), meaning newc <= size. However, if no chars are filtered, newc reaches `size` and `console_c[newc] = '\\0'` on line 137 would write at exactly size, which is one past the valid allocation of size bytes (malloc(size+1) allocates size+1 bytes, so index `size` is valid). This is actually safe by the +1 allocation, but the logic is fragile.","fix":"Add a bounds check in snapshot_putc: `if (*cursor >= allocated_size) return;` to make it robust. Pass the allocation size as a parameter.","locations":[{"ref":"payloads/libpayload/drivers/cbmem_console.c:100-139","code":" 100 | char *cbmem_console_snapshot(void)\n 101 | {\n 102 | \tconst struct cbmem_console *const console_p = phys_to_virt(cbmem_console_p);\n 103 | \tchar *console_c;\n 104 | \tuint32_t size, cursor, overflow, newc, oldc;\n 105 | \n 106 | \tif (!console_p) {\n 107 | \t\tprintf(\"ERROR: No cbmem console found in coreboot table\\n\");\n 108 | \t\treturn NULL;\n 109 | \t}\n 110 | \n 111 | \tcursor = console_p->cursor & CURSOR_MASK;\n 112 | \toverflow = console_p->cursor & OVERFLOW;\n 113 | \tif (!overflow && cursor < console_p->size)\n 114 | \t\tsize = cursor;\n 115 | \telse\n 116 | \t\tsize = console_p->size;\n 117 | \n 118 | \tconsole_c = malloc(size + 1);\n 119 | \tif (!console_c) {\n 120 | \t\tprintf(\"ERROR: Not enough memory for console (size = %u)\\n\",\n 121 | \t\t size);\n 122 | \t\treturn NULL;\n 123 | \t}\n 124 | \n 125 | \tnewc = 0;\n 126 | \tif (overflow) {\n 127 | \t\tif (cursor >= size) {\n 128 | \t\t\tprintf(\"ERROR: CBMEM console struct is corrupted\\n\");\n 129 | \t\t\tfree(console_c);\n 130 | \t\t\treturn NULL;\n 131 | \t\t}\n 132 | \t\tfor (oldc = cursor; oldc < size; oldc++)\n 133 | \t\t\tsnapshot_putc(console_c, &newc, console_p->body[oldc]);\n 134 | \t}\n 135 | \tfor (oldc = 0; oldc < size && oldc < cursor; oldc++)\n 136 | \t\tsnapshot_putc(console_c, &newc, console_p->body[oldc]);\n 137 | \tconsole_c[newc] = '\\0';\n 138 | \n 139 | \treturn console_c;","lines":40,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/cbmem_console.c#L100-L139"}]},
{"id":"LOGIC_49","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In i8042.c fifo_push(), when the FIFO is full (tx wraps around to equal rx), rx is incremented to drop the oldest byte. But the increment is done as `fifo->rx++` before modulo, and the modulo is applied after: `fifo->rx = fifo->rx % fifo->len`. This is correct for the modulo, but if fifo->len is 0 (e.g., from a failed initialization that somehow let buf=NULL and len=0 slip through), this is a division-by-zero. fifo_init prevents len=0 only implicitly through malloc behavior.","fix":"Assert or check fifo->len > 0 in fifo_push, or guard fifo_init against len=0.","locations":[{"ref":"payloads/libpayload/drivers/i8042/i8042.c:77-83","code":" 77 | static void fifo_push(struct fifo *fifo, u8 c)\n 78 | {\n 79 | \tfifo->buf[fifo->tx++] = c;\n 80 | \tfifo->tx = fifo->tx % fifo->len;\n 81 | \tif (fifo->tx == fifo->rx)\n 82 | \t\tfifo->rx++;\n 83 | \tfifo->rx = fifo->rx % fifo->len;","lines":7,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/i8042/i8042.c#L77-L83"}]},
{"id":"LOGIC_50","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In i8042.c i8042_cmd_with_data(), after calling i8042_write_data(data) on line 348, the return value is `ret` from the earlier i8042_cmd() call (line 351: `return ret`), not the result of i8042_write_data. i8042_write_data itself returns void and cannot signal failure. If the data write times out (i8042_wait_cmd_rdy fails inside i8042_write_data), the caller receives E_OK (ret=0) falsely indicating success. This is particularly harmful in i8042_set_kbd_translation() which trusts this return value.","fix":"Change i8042_write_data() to return int indicating success/failure, check it in i8042_cmd_with_data(), and propagate the error.","locations":[{"ref":"payloads/libpayload/drivers/i8042/i8042.c:342-351","code":" 342 | static int i8042_cmd_with_data(const u8 cmd, const u8 data)\n 343 | {\n 344 | \tconst int ret = i8042_cmd(cmd);\n 345 | \tif (ret != 0)\n 346 | \t\treturn ret;\n 347 | \n 348 | \ti8042_write_data(data);\n 349 | \n 350 | \treturn ret;\n 351 | }","lines":10,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/i8042/i8042.c#L342-L351"}]},
{"id":"LOGIC_51","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In i8042.c i8042_data_poll(), the status byte `c` is read once before the loop, then rechecked inside the loop, but when the status is 0xFF it silently drops data and exits (line 364: `while ((c != 0xFF) && (c & OBF))`). If the controller is in a transient bad state and returns 0xFF once, the entire poll loop exits, possibly leaving valid data in the OBF from a prior read that has already been consumed. The OBF is checked on the stale `c` variable (which was updated by the previous iteration's `read_status()` on line 371), not a fresh read - actually line 371 does re-read status. The 0xFF exit is correct as a 'no controller' check but is also silently triggered by transient bus glitches, losing keyboard data.","fix":"Consider logging a warning on 0xFF status or treating it as 'no data' rather than a hard exit, to be resilient to transient bus glitches.","locations":[{"ref":"payloads/libpayload/drivers/i8042/i8042.c:356-373","code":" 356 | static void i8042_data_poll(void)\n 357 | {\n 358 | \tu8 c;\n 359 | \n 360 | \tif (!initialized)\n 361 | \t\treturn;\n 362 | \n 363 | \tc = read_status();\n 364 | \twhile ((c != 0xFF) && (c & OBF)) {\n 365 | \t\tconst u8 in = read_data();\n 366 | \t\t/* Assume \"second PS/2 port output buffer full\" flag works */\n 367 | \t\tstruct fifo *const fifo = (c & 0x20) ? aux_fifo : ps2_fifo;\n 368 | \t\tif (fifo)\n 369 | \t\t\tfifo_push(fifo, in);\n 370 | \n 371 | \t\tc = read_status();\n 372 | \t}\n 373 | }","lines":18,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/i8042/i8042.c#L356-L373"}]},
{"id":"LOGIC_52","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In keyboard.c keyboard_getchar(), after the MEDIA_KEY_PREFIX special case, the check `if (!(ch & 0x80) && ch < 0x59)` gates the keyboard map lookup. For the MEDIA_KEY_PREFIX (0xE0) path, ch is first set to 0xE0 then overwritten by the second keyboard_get_scancode() result. If media_key_mapping_callback is NULL and the MEDIA_KEY_PREFIX path exits the if block, ch retains the value 0xE0 (break code = bit 7 set), so the check `ch & 0x80` is true and the scancode is silently dropped, returning 0. This means extended keys are silently ignored when the media callback is not installed, which is the intended fallback but may surprise callers expecting arrow-key codes.","fix":"Document or explicitly handle the case where no media_key_mapping_callback is installed and E0-prefixed keys are received; or implement basic E0 key translation inline.","locations":[{"ref":"payloads/libpayload/drivers/i8042/keyboard.c:558-591","code":" 558 | \tif ((media_key_mapping_callback != NULL) && (ch == MEDIA_KEY_PREFIX)) {\n 559 | \t\tch = keyboard_get_scancode();\n 560 | \t\treturn media_key_mapping_callback(ch);\n 561 | \t}\n 562 | \n 563 | \tif (!(ch & 0x80) && ch < 0x59) {\n 564 | \t\tshift =\n 565 | \t\t (modifier & KB_MOD_SHIFT) ^ (modifier & KB_MOD_CAPSLOCK) ? 1 : 0;\n 566 | \n 567 | \t\tif (modifier & KB_MOD_ALT)\n 568 | \t\t\tshift += 2;\n 569 | \n 570 | \t\tret = map->map[shift][ch];\n 571 | \n 572 | \t\tif (modifier & KB_MOD_CTRL) {\n 573 | \t\t\tswitch (ret) {\n 574 | \t\t\tcase 'a' ... 'z':\n 575 | \t\t\t\tret &= 0x1f;\n 576 | \t\t\t\tbreak;\n 577 | \t\t\tcase KEY_DC:\n 578 | \t\t\t\t/* vulcan nerve pinch */\n 579 | \t\t\t\tif ((modifier & KB_MOD_ALT) && reset_handler)\n 580 | \t\t\t\t\treset_handler();\n 581 | \t\t\t\t__fallthrough;\n 582 | \t\t\tdefault:\n 583 | \t\t\t\tret = 0;\n 584 | \t\t\t}\n 585 | \t\t}\n 586 | \t}\n 587 | \n 588 | \tif (ch == 0x5e)\n 589 | \t\tret = POWER_BUTTON;\n 590 | \n 591 | \treturn ret;","lines":34,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/i8042/keyboard.c#L558-L591"}]},
{"id":"LOGIC_53","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In keyboard.c keyboard_getchar(), when `ch == MEDIA_KEY_PREFIX (0xE0)`, keyboard_get_scancode() is called again to read the second byte and the result is passed to media_key_mapping_callback(). However, keyboard_get_scancode() itself processes modifier keys and updates the `modifier` variable. An E0-prefixed scancode (like extended arrow keys or numpad keys) will have its second byte processed as if it were a standalone scancode, potentially modifying the modifier state incorrectly (e.g., extended shift codes 0xE0 0x2A / 0xE0 0x36 would set KB_MOD_SHIFT). This is only a problem if media_key_mapping_callback is not set for such codes; if it is NULL the code falls through to the scancode table lookup.","fix":"The MEDIA_KEY_PREFIX path should call i8042_read_data_ps2() directly to get the raw second byte without passing through keyboard_get_scancode()'s modifier-state logic.","locations":[{"ref":"payloads/libpayload/drivers/i8042/keyboard.c:558-561","code":" 558 | \tif ((media_key_mapping_callback != NULL) && (ch == MEDIA_KEY_PREFIX)) {\n 559 | \t\tch = keyboard_get_scancode();\n 560 | \t\treturn media_key_mapping_callback(ch);\n 561 | \t}","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/i8042/keyboard.c#L558-L561"}]},
{"id":"LOGIC_54","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In keyboard.c keyboard_set_layout(), the strncmp uses `strlen(keyboard_layouts[i].country)` as the comparison length. If the caller passes a longer string with the same prefix (e.g., 'usage'), it would match 'us'. This allows ambiguous or unintended layout selection. Comparing against the full string length of the input would be more correct.","fix":"Use `strcmp(keyboard_layouts[i].country, country) == 0` for an exact match, or use `strncmp` with the maximum of both string lengths.","locations":[{"ref":"payloads/libpayload/drivers/i8042/keyboard.c:604-607","code":" 604 | \tfor (i=0; i<ARRAY_SIZE(keyboard_layouts); i++) {\n 605 | \t\tif (strncmp(keyboard_layouts[i].country, country,\n 606 | \t\t\t\t\tstrlen(keyboard_layouts[i].country)))\n 607 | \t\t\tcontinue;","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/i8042/keyboard.c#L604-L607"}]},
{"id":"LOGIC_55","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In mouse.c mouse_decode(), the z_axis calculation on lines 154 and 156 is `(mouse_buf[3] & 0x7) - (mouse_buf[3] & 0x08) ? 8 : 0`. Due to C operator precedence, this parses as `(mouse_buf[3] & 0x7) - ((mouse_buf[3] & 0x08) ? 8 : 0)`. When the sign bit (0x08) is set, the scroll delta should be negative (scroll up), yielding a value in range [0..7] - 8 = [-8..-1]. This is correct in intent, but the subtraction `(mouse_buf[3] & 0x7) - 8` when the lower nibble is 0 gives -8, and when it is 7 gives -1. However both is_intellimouse and is_explorer_intellimouse use identical code; this is actually correct behavior, but it is extremely confusing and hard to verify. More critically: if neither is_intellimouse nor is_explorer_intellimouse is set but mouse_buf_idx reaches 4 somehow, the 4th byte is never validated and the function falls through to the 3-byte path with idx < 3 check (which passes because idx == 4 >= 3), so it processes only 3 bytes while a 4th is sitting in the buffer unprocessed.","fix":"Add parentheses to the z_axis expression for clarity. For the 3-byte protocol case, enforce `mouse_buf_idx == 3` (not just >= 3) to avoid processing stale 4th bytes.","locations":[{"ref":"payloads/libpayload/drivers/i8042/mouse.c:125-161","code":" 125 | static void mouse_decode(void)\n 126 | {\n 127 | \t/* Buffer full check and sanity check */\n 128 | \tif (is_intellimouse) {\n 129 | \t\tif (mouse_buf_idx < 4)\n 130 | \t\t\treturn;\n 131 | \t\tif ((mouse_buf[3] & 0x10) != (mouse_buf[3] & 0x08)) {\n 132 | \t\t\tmouse_buf_idx = 0;\n 133 | \t\t\treturn;\n 134 | \t\t}\n 135 | \t} else if (is_explorer_intellimouse) {\n 136 | \t\tif (mouse_buf_idx < 4)\n 137 | \t\t\treturn;\n 138 | \t\tif (mouse_buf[3] & 0xc0) {\n 139 | \t\t\tmouse_buf_idx = 0;\n 140 | \t\t\treturn;\n 141 | \t\t}\n 142 | \t} else {\n 143 | \t\tif (mouse_buf_idx < 3)\n 144 | \t\t\treturn;\n 145 | \t}\n 146 | \n 147 | \t/* Common protocol */\n 148 | \tx_axis += mouse_buf[1] ? mouse_buf[1] - ((mouse_buf[0] << 4) & 0x100) : 0;\n 149 | \ty_axis += mouse_buf[2] ? ((mouse_buf[0] << 3) & 0x100) - mouse_buf[2] : 0;\n 150 | \tbuttons = mouse_buf[0] & 0x7;\n 151 | \n 152 | \t/* Extended protocol */\n 153 | \tif (is_intellimouse) {\n 154 | \t\tz_axis += (mouse_buf[3] & 0x7) - (mouse_buf[3] & 0x08) ? 8 : 0;\n 155 | \t} else if (is_explorer_intellimouse) {\n 156 | \t\tz_axis += (mouse_buf[3] & 0x7) - (mouse_buf[3] & 0x08) ? 8 : 0;\n 157 | \t\tbuttons = (mouse_buf[0] & 0x7) | (mouse_buf[3] & 0x30) >> 1;\n 158 | \t}\n 159 | \n 160 | \tmouse_buf_idx = 0;\n 161 | }","lines":37,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/i8042/mouse.c#L125-L161"}]},
{"id":"LOGIC_56","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In nvram.c rtc_write_clock(), the 12-hour format encoding for midnight is wrong. The MC146818 datasheet specifies that in 12-hour mode, midnight is encoded as hour=12 with the PM bit (0x80) set. The write path (lines 191-194 and 205-209) uses (time->tm_hour > 12) as the condition to set the PM bit, so hour==0 (midnight) writes dec2bcd(0) or 0 with no PM flag. When read back by rtc_read_clock() (which correctly decodes MC146818 convention), midnight would be misinterpreted. The read path uses (reg8 & 0x80 ? +12 : 0) and % 24, so it correctly handles 12|0x80 = midnight, but the write does not produce that encoding.","fix":"For midnight (hour==0) in 12-hour mode, write 12 with the PM bit set. Change the condition to (time->tm_hour >= 12) and adjust the hour: int h12 = time->tm_hour % 12; if (h12 == 0) h12 = 12; reg8 = dec2bcd(h12) | (time->tm_hour >= 12 ? 0x80 : 0);","locations":[{"ref":"payloads/libpayload/drivers/nvram.c:190-197","code":" 190 | \t\tif (!(statusB & NVRAM_RTC_FORMAT_24HOUR)) {\n 191 | \t\t\tif (time->tm_hour > 12)\n 192 | \t\t\t\treg8 = dec2bcd(time->tm_hour - 12) | 0x80;\n 193 | \t\t\telse\n 194 | \t\t\t\treg8 = dec2bcd(time->tm_hour);\n 195 | \t\t} else {\n 196 | \t\t\treg8 = dec2bcd(time->tm_hour);\n 197 | \t\t}","lines":8,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/nvram.c#L190-L197"},{"ref":"payloads/libpayload/drivers/nvram.c:205-212","code":" 205 | \t\tif (!(statusB & NVRAM_RTC_FORMAT_24HOUR)) {\n 206 | \t\t\tif (time->tm_hour > 12)\n 207 | \t\t\t\treg8 = (time->tm_hour - 12) | 0x80;\n 208 | \t\t\telse\n 209 | \t\t\t\treg8 = time->tm_hour;\n 210 | \t\t} else {\n 211 | \t\t\treg8 = time->tm_hour;\n 212 | \t\t}","lines":8,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/nvram.c#L205-L212"}]},
{"id":"LOGIC_57","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"lookup_cmos_entry() computes len = strnlen(name, CB_CMOS_MAX_NAME_LENGTH) and then uses memcmp(cmos_entry->name, name, len) to find the entry. This performs a prefix match: a search for 'foo' will match an entry named 'foobar' because memcmp only compares the first 3 bytes. Any option whose name is a prefix of another option's name will be incorrectly matched.","fix":"Compare exactly CB_CMOS_MAX_NAME_LENGTH bytes, or ensure the entry name is also null-terminated and use strncmp instead: if (strncmp((const char*)cmos_entry->name, name, CB_CMOS_MAX_NAME_LENGTH) == 0). The same issue exists in lookup_cmos_enum_core() with text/CB_CMOS_MAX_TEXT_LENGTH.","locations":[{"ref":"payloads/libpayload/drivers/options.c:157-172","code":" 157 | static struct cb_cmos_entries *lookup_cmos_entry(struct cb_cmos_option_table *option_table, const char *name)\n 158 | {\n 159 | \tstruct cb_cmos_entries *cmos_entry;\n 160 | \tint len = name ? strnlen(name, CB_CMOS_MAX_NAME_LENGTH) : 0;\n 161 | \n 162 | \t/* CMOS entries are located right after the option table */\n 163 | \tcmos_entry = first_cmos_entry(option_table);\n 164 | \twhile (cmos_entry) {\n 165 | \t\tif (memcmp((const char*)cmos_entry->name, name, len) == 0)\n 166 | \t\t\treturn cmos_entry;\n 167 | \t\tcmos_entry = next_cmos_entry(cmos_entry);\n 168 | \t}\n 169 | \n 170 | \tprintf(\"ERROR: No such CMOS option (%s)\\n\", name);\n 171 | \treturn NULL;\n 172 | }","lines":16,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/options.c#L157-L172"}]},
{"id":"LOGIC_58","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"lookup_cmos_enum_core() also uses a prefix-only memcmp for the text/label comparison (line 248). A search for enum label 'on' will match 'once', 'only', etc. The same fix applies as for lookup_cmos_entry.","fix":"Use strncmp(cmos_enum->text, text, CB_CMOS_MAX_TEXT_LENGTH) == 0 instead of memcmp with the user-string length.","locations":[{"ref":"payloads/libpayload/drivers/options.c:248","code":" 248 | \t\t ((text == NULL) || (memcmp((const char*)cmos_enum->text, text, len) == 0))) {","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/options.c#L248"}]},
{"id":"LOGIC_59","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In set_option_from_string() case 's', the buffer is allocated and strncpy'd using cmos_entry->length, which is measured in BITS, not bytes. For a 64-bit option field, this allocates 64 bytes and copies up to 64 characters, when only 8 bytes (64/8) are meaningful. The corresponding get path correctly converts bits to bytes with (cmos_entry->length+7)/8. The mismatch means the string option is allocated and filled with far more data than set_cmos_value will actually store (it only writes cmos_entry->length/8 bytes).","fix":"Use int byte_len = (cmos_entry->length + 7) / 8; for malloc and strncpy limit, matching the get path.","locations":[{"ref":"payloads/libpayload/drivers/options.c:362-366","code":" 362 | \t\t\traw = malloc(cmos_entry->length);\n 363 | \t\t\tif (!raw)\n 364 | \t\t\t\treturn 1;\n 365 | \t\t\tmemset(raw, 0x00, cmos_entry->length);\n 366 | \t\t\tstrncpy(raw, value, cmos_entry->length);","lines":5,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/options.c#L362-L366"}]},
{"id":"LOGIC_60","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In pci_ops.c find_on_bus(), when a bridge is found, only the secondary bus number is extracted from REG_PRIMARY_BUS (bits 15:8). find_on_bus() is then called recursively on that secondary bus. This correctly recurses into downstream buses because each bridge found on any bus will itself be recursed into. However, there is no protection against bus-number cycles involving three or more buses: if bus A has a bridge to bus B, and bus B has a bridge back to bus A, the same-bus check (busses != bus) only prevents A->A or B->B directly; A->B->A->B... is an infinite recursion.","fix":"Add a visited-bus bitmask (256 bits = 32 bytes) to track buses already enumerated, and skip recursion into any already-visited bus.","locations":[{"ref":"payloads/libpayload/drivers/pci_ops.c:60-71","code":" 60 | \t\tif (hdr == HEADER_TYPE_BRIDGE || hdr == HEADER_TYPE_CARDBUS) {\n 61 | \t\t\tunsigned int busses;\n 62 | \t\t\tbusses = pci_read_config32(PCI_DEV(bus, slot, func),\n 63 | \t\t\t\t\t\t REG_PRIMARY_BUS);\n 64 | \t\t\tbusses = (busses >> 8) & 0xFF;\n 65 | \n 66 | \t\t\t/* Avoid recursion if the new bus is the same as\n 67 | \t\t\t * the old bus (insert lame The Who joke here) */\n 68 | \n 69 | \t\t\tif ((busses != bus) &&\n 70 | \t\t\t find_on_bus(busses, vid, did, dev))\n 71 | \t\t\t\treturn 1;","lines":12,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/pci_ops.c#L60-L71"}]},
{"id":"LOGIC_61","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In `mmc_set_bus_width` the reference buffer `ext_csd` is declared but never populated (there is no `mmc_send_ext_csd(ctrlr, ext_csd)` call before the loop). The loop switches the bus width, re-reads the device EXT_CSD into `test_csd`, and then compares fields of `test_csd` against `ext_csd`. Since `ext_csd` holds uninitialized stack contents, the comparison at lines 370-380 (`ext_csd[EXT_CSD_PARTITIONING_SUPPORT] == test_csd[...]`, `memcmp(&ext_csd[EXT_CSD_SEC_CNT], &test_csd[EXT_CSD_SEC_CNT], 4)`, etc.) compares garbage. This is reachable for eMMC v4 cards on controllers without HS200/HS400 (the early `break` at line 356 is not taken), where it will typically make every width fail verification so the code falls through to `width == 0` and configures a 1-bit bus (`SET_BUS_WIDTH(ctrlr, 1)`), a large performance regression, and is undefined behavior (reading uninitialized memory). This is a porting regression from depthcharge, where `ext_csd` was read once before the loop.","fix":"Read the reference EXT_CSD into `ext_csd` before the width loop, e.g. after the buffer declarations add `err = mmc_send_ext_csd(ctrlr, ext_csd); if (err) return err;` so that the post-switch `test_csd` comparison validates against a known-good copy.","locations":[{"ref":"src/commonlib/storage/mmc.c:341-386","code":" 341 | int mmc_set_bus_width(struct storage_media *media)\n 342 | {\n 343 | \tstruct sd_mmc_ctrlr *ctrlr = media->ctrlr;\n 344 | \tint err;\n 345 | \tint width;\n 346 | \n 347 | \tALLOC_CACHE_ALIGN_BUFFER(unsigned char, ext_csd, EXT_CSD_SIZE);\n 348 | \tALLOC_CACHE_ALIGN_BUFFER(unsigned char, test_csd, EXT_CSD_SIZE);\n 349 | \n 350 | \t/* Set the bus width */\n 351 | \terr = 0;\n 352 | \tfor (width = EXT_CSD_BUS_WIDTH_8; width >= 0; width--) {\n 353 | \t\t/* If HS200 is switched, Bus Width has been 8-bit */\n 354 | \t\tif ((media->caps & DRVR_CAP_HS200) ||\n 355 | \t\t (media->caps & DRVR_CAP_HS400))\n 356 | \t\t\tbreak;\n 357 | \n 358 | \t\t/* Set the card to use 4 bit*/\n 359 | \t\terr = mmc_switch(media, EXT_CSD_BUS_WIDTH, width);\n 360 | \t\tif (err)\n 361 | \t\t\tcontinue;\n 362 | \n 363 | \t\tif (!width) {\n 364 | \t\t\tSET_BUS_WIDTH(ctrlr, 1);\n 365 | \t\t\tbreak;\n 366 | \t\t}\n 367 | \t\tSET_BUS_WIDTH(ctrlr, 4 * width);\n 368 | \n 369 | \t\terr = mmc_send_ext_csd(ctrlr, test_csd);\n 370 | \t\tif (!err &&\n 371 | \t\t (ext_csd[EXT_CSD_PARTITIONING_SUPPORT] ==\n 372 | \t\t test_csd[EXT_CSD_PARTITIONING_SUPPORT]) &&\n 373 | \t\t (ext_csd[EXT_CSD_ERASE_GROUP_DEF] ==\n 374 | \t\t test_csd[EXT_CSD_ERASE_GROUP_DEF]) &&\n 375 | \t\t (ext_csd[EXT_CSD_REV] ==\n 376 | \t\t test_csd[EXT_CSD_REV]) &&\n 377 | \t\t (ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] ==\n 378 | \t\t test_csd[EXT_CSD_HC_ERASE_GRP_SIZE]) &&\n 379 | \t\t memcmp(&ext_csd[EXT_CSD_SEC_CNT],\n 380 | \t\t\t &test_csd[EXT_CSD_SEC_CNT], 4) == 0) {\n 381 | \t\t\tmedia->caps |= width;\n 382 | \t\t\tbreak;\n 383 | \t\t}\n 384 | \t}\n 385 | \treturn err;\n 386 | }","lines":46,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/commonlib/storage/mmc.c#L341-L386"}]},
{"id":"LOGIC_62","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"very_easy","description":"`set_max_ratio()` computes `perf_ctl` in both branches but never writes it to the MSR - the `wrmsr(IA32_PERF_CTL, perf_ctl)` call is missing. This is a regression introduced by commit `763d31293f` (\"cpu/intel/model_206xx: Enable turbo mode on BSP\"), whose whole stated purpose is to make the BSP apply the max/turbo ratio after `BIOS_RESET_CPL`. The refactor accidentally dropped the `wrmsr` while restructuring the if/else, so `set_max_ratio()` now only prints a debug line and has no effect at all. The sibling functions `set_max_ratio()` in `model_206ax_init.c` (line 403) and `haswell_init.c` (line 467) both retain the `wrmsr(IA32_PERF_CTL, perf_ctl)`, confirming this is an omission. Impact: the BSP no longer gets its P-state/turbo ratio programmed during MP init, so Arrandale/Clarkdale boot at the lowest P-state until the OS reprograms it (slower payload/bootloader), defeating the commit.","fix":"Restore the missing MSR write after the if/else, matching the `model_206ax`/`haswell` versions.\n\nBefore:\n```c\n\t} else {\n\t\t/* Platform Info bits 15:8 give max ratio (no Turbo mode) */\n\t\tmsr = rdmsr(MSR_PLATFORM_INFO);\n\t\tperf_ctl.lo = msr.lo & 0xff00;\n\t}\n\n\tprintk(BIOS_DEBUG, \"model_x065x: frequency set to %d\\n\",\n```\nAfter:\n```c\n\t} else {\n\t\t/* Platform Info bits 15:8 give max ratio (no Turbo mode) */\n\t\tmsr = rdmsr(MSR_PLATFORM_INFO);\n\t\tperf_ctl.lo = msr.lo & 0xff00;\n\t}\n\twrmsr(IA32_PERF_CTL, perf_ctl);\n\n\tprintk(BIOS_DEBUG, \"model_x065x: frequency set to %d\\n\",\n```","locations":[{"ref":"src/cpu/intel/model_2065x/model_2065x_init.c:79-97","code":" 79 | static void set_max_ratio(void)\n 80 | {\n 81 | \tmsr_t msr, perf_ctl;\n 82 | \n 83 | \tperf_ctl.hi = 0;\n 84 | \n 85 | \t/* Check for configurable TDP option */\n 86 | \tif (get_turbo_state() == TURBO_ENABLED) {\n 87 | \t\tmsr = rdmsr(MSR_TURBO_RATIO_LIMIT);\n 88 | \t\tperf_ctl.lo = (msr.lo & 0xff) << 8;\n 89 | \t} else {\n 90 | \t\t/* Platform Info bits 15:8 give max ratio (no Turbo mode) */\n 91 | \t\tmsr = rdmsr(MSR_PLATFORM_INFO);\n 92 | \t\tperf_ctl.lo = msr.lo & 0xff00;\n 93 | \t}\n 94 | \n 95 | \tprintk(BIOS_DEBUG, \"model_x065x: frequency set to %d\\n\",\n 96 | \t ((perf_ctl.lo >> 8) & 0xff) * IRONLAKE_BCLK);\n 97 | }","lines":19,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/cpu/intel/model_2065x/model_2065x_init.c#L79-L97"}]},
{"id":"LOGIC_63","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"very_easy","description":"In `coalesce_capsules()` the chunk length is computed as `2 * MiB - (addr % 2 * MiB)`. Because `%` and `*` have equal precedence and associate left-to-right, this parses as `2 * MiB - ((addr % 2) * MiB)`, i.e. it depends only on whether `addr` is odd or even, not on the offset within the 2 MiB mapping window. The intent (matching `map_range()`, which maps a 2 MiB window aligned down to `ALIGN_DOWN(base, 2 * MiB)`) is the bytes remaining in the current window: `2 * MiB - (addr % (2 * MiB))`. With the buggy form, when `addr` is even but not 2 MiB-aligned (e.g. `addr = 0x300000`, `data_left >= 2 * MiB`), `piece_len` becomes up to `2 * MiB` while the real window has fewer bytes available; the subsequent `map_range(addr, piece_len)` then hits `base - aligned_base + len > 2 * MiB` and calls `die()`, aborting capsule processing (or copies a wrong-sized chunk). This is on the UEFI capsule-update coalescing path.","fix":"Add explicit parentheses so the modulo uses the full window size.\n```c\n// before\nuint64_t piece_len = MIN(data_left, 2 * MiB - (addr % 2 * MiB));\n// after\nuint64_t piece_len = MIN(data_left, 2 * MiB - (addr % (2 * MiB)));\n```","locations":[{"ref":"src/drivers/efi/capsules.c:658","code":" 658 | \t\t\tuint64_t piece_len = MIN(data_left, 2 * MiB - (addr % 2 * MiB));","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/efi/capsules.c#L658"}]},
{"id":"LOGIC_64","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"very_easy","description":"In `gsmi_exec()` the `GSMI_CMD_SET_EVENT_LOG` input-validation guard is logically inverted. The comment says \"Make sure the input is usable\", so the code should `break` (reject) when the input is malformed, i.e. when `sel->type != 1` OR `sel->data_ptr == 0` OR `sel->data_len` does not match. Instead it uses `&&` and `sel->data_ptr != 0`, so the `break` only fires when all three are simultaneously true, which essentially never happens for real inputs. As a result `sel->data_len` is never actually validated: an SMI caller can supply `sel->type == 1` with a `data_len` smaller than `sizeof(struct gsmi_set_eventlog_type1)`, and the handler will still dereference `type1->type`/`type1->instance` (reading past the caller-supplied buffer inside SMM). The De Morgan negation was applied to the operands but not to the connective.","fix":"Reject unusable input with OR logic and a null check:\n```c\n/* Make sure the input is usable */\nif (sel->type != 1 || sel->data_ptr == 0 ||\n sel->data_len != sizeof(struct gsmi_set_eventlog_type1))\n\tbreak;\n```\n(before: `if (sel->type != 1 && sel->data_ptr != 0 && sel->data_len != sizeof(struct gsmi_set_eventlog_type1))`)","locations":[{"ref":"src/drivers/elog/gsmi.c:67-70","code":" 67 | \t\t/* Make sure the input is usable */\n 68 | \t\tif (sel->type != 1 && sel->data_ptr != 0 &&\n 69 | \t\t sel->data_len != sizeof(struct gsmi_set_eventlog_type1))\n 70 | \t\t\tbreak;","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/elog/gsmi.c#L67-L70"}]},
{"id":"LOGIC_65","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In `rtc_set_time_date()` the values placed into the `struct rtc_time time` are already BCD-encoded (or pre-converted), but `rv3028c7_rtc_set()` then applies `bin2bcd()` to every field again, producing a double/garbled conversion and writing a wrong date to the RTC. In the user-date branch, `time.mday = bin2bcd(config->user_day)` etc. store BCD, while `rv3028c7_rtc_set()` does `bin2bcd(time->mday)` a second time; note `time.wday = config->user_weekday` is NOT pre-converted, an internal inconsistency proving the bug. In the build-date branch, `coreboot_build_date` fields are already BCD (`struct bcd_date`, cf. `pcf8523`/`rx6110sa` which write them raw), yet they are fed through `bin2bcd()` in `rv3028c7_rtc_set()`. `rv3028c7_rtc_get()` correctly treats registers as BCD (`bcd2bin`), confirming `rtc_time` should hold plain binary. Result: after a power-loss (`PORF_BIT`) event the RTC is programmed with an incorrect date/time.","fix":"Populate `time` with plain binary values so the single `bin2bcd()` in `rv3028c7_rtc_set()` is correct. For the user branch drop the pre-conversion (`time.mday = config->user_day; time.mon = config->user_month; time.year = config->user_year;`). For the build-date branch convert BCD to binary first (`time.wday = bcd2bin(coreboot_build_date.weekday); time.mday = bcd2bin(coreboot_build_date.day); time.mon = bcd2bin(coreboot_build_date.month); time.year = bcd2bin(coreboot_build_date.year);`).","locations":[{"ref":"src/drivers/i2c/rv3028c7/rv3028c7.c:114-141","code":" 114 | static void rtc_set_time_date(struct device *dev)\n 115 | {\n 116 | \tstruct drivers_i2c_rv3028c7_config *config = dev->chip_info;\n 117 | \tstruct rtc_time time;\n 118 | \n 119 | \t/* Whenever a new date is set, the time is set to 00:00:00. */\n 120 | \ttime.sec = 0;\n 121 | \ttime.min = 0;\n 122 | \ttime.hour = 0;\n 123 | \tif (config->set_user_date) {\n 124 | \t\ttime.wday = config->user_weekday;\n 125 | \t\ttime.mday = bin2bcd(config->user_day);\n 126 | \t\ttime.mon = bin2bcd(config->user_month);\n 127 | \t\ttime.year = bin2bcd(config->user_year);\n 128 | \t\tprintk(BIOS_DEBUG, \"%s: Use user date.\\n\", dev->chip_ops->name);\n 129 | \t} else {\n 130 | \t\ttime.wday = coreboot_build_date.weekday;\n 131 | \t\ttime.mday = coreboot_build_date.day;\n 132 | \t\ttime.mon = coreboot_build_date.month;\n 133 | \t\ttime.year = coreboot_build_date.year;\n 134 | \t\tprintk(BIOS_DEBUG, \"%s: Use coreboot build date.\\n\", dev->chip_ops->name);\n 135 | \t}\n 136 | \tif (rv3028c7_rtc_set(dev, &time) != 0) {\n 137 | \t\tprintk(BIOS_ERR, \"%s: Not able to set date and time!\\n\", dev->chip_ops->name);\n 138 | \t} else {\n 139 | \t\tprintk(BIOS_NOTICE, \"%s: Date and time set.\\n\", dev->chip_ops->name);\n 140 | \t}\n 141 | }","lines":28,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/i2c/rv3028c7/rv3028c7.c#L114-L141"},{"ref":"src/drivers/i2c/rv3028c7/rv3028c7.c:67-87","code":" 67 | static int rv3028c7_rtc_set(struct device *dev, struct rtc_time *time)\n 68 | {\n 69 | \tconst uint8_t buf[7] = {\n 70 | \t\tbin2bcd(time->sec),\n 71 | \t\tbin2bcd(time->min),\n 72 | \t\tbin2bcd(time->hour),\n 73 | \t\tbin2bcd(time->wday),\n 74 | \t\tbin2bcd(time->mday),\n 75 | \t\tbin2bcd(time->mon),\n 76 | \t\tbin2bcd(time->year % 100)\n 77 | \t};\n 78 | \n 79 | \tfor (size_t i = 0; i < ARRAY_SIZE(buf); i++) {\n 80 | \t\tif (i2c_dev_writeb_at(dev, i, buf[i]) < 0) {\n 81 | \t\t\tprintk(BIOS_ERR, \"%s: Failed to write register 0x%02zx!\\n\",\n 82 | \t\t\t\tdev->chip_ops->name, i);\n 83 | \t\t\treturn 1;\n 84 | \t\t}\n 85 | \t}\n 86 | \treturn 0;\n 87 | }","lines":21,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/i2c/rv3028c7/rv3028c7.c#L67-L87"}]},
{"id":"LOGIC_66","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"very_easy","description":"In `mp_startup_this_ap()` the bounds check uses `>` instead of `>=`, an off-by-one that fails to reject `processor_number == get_cpu_count()`. Valid processor indices are `0 .. get_cpu_count()-1`, so a request for index `get_cpu_count()` (which the FSP is allowed to make through the MP Services PPI) passes validation and is forwarded to `mp_run_on_aps()` as an out-of-range logical CPU number. Note the sibling function `mp_get_processor_info()` correctly uses `>=` (`processor_number >= MIN(get_cpu_count(), CONFIG_MAX_CPUS)`), confirming the intended semantics.","fix":"Change the comparison to `>=`:\n\nBefore:\n```c\n\tif (processor_number > get_cpu_count())\n\t\treturn FSP_NOT_FOUND;\n```\nAfter:\n```c\n\tif (processor_number >= get_cpu_count())\n\t\treturn FSP_NOT_FOUND;\n```","locations":[{"ref":"src/drivers/intel/fsp2_0/ppi/mp_service_ppi.c:165-166","code":" 165 | \tif (processor_number > get_cpu_count())\n 166 | \t\treturn FSP_NOT_FOUND;","lines":2,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/intel/fsp2_0/ppi/mp_service_ppi.c#L165-L166"}]},
{"id":"LOGIC_67","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"medium","description":"In `do_silicon_init()` a temporary write-combining MTRR is reserved via `temp_mtrr_index = soc_mark_gfx_memory()` whenever `CONFIG(BMP_LOGO) && CONFIG(USE_COREBOOT_FOR_BMP_RENDERING)` is set (lines 128-129), regardless of FSP version. However the matching cleanup `clear_var_mtrr(temp_mtrr_index)` only runs inside the FSP 2.2 multi-phase block (lines 238-241). For platforms that are NOT `PLATFORM_USES_FSP2_2`, the function returns early at line 180 (`if (!CONFIG(PLATFORM_USES_FSP2_2)) return;`) after rendering the logo at lines 165-173, so the temporary WC MTRR is never released. This leaks a variable MTRR and leaves a write-combining memory type covering graphics memory in effect for the rest of boot, which can corrupt the effective caching type of that region and exhaust the limited variable-MTRR pool.","fix":"Move the MTRR cleanup out of the FSP-2.2-only block so it runs on every path where `temp_mtrr_index` may have been set. For example, in the pre-2.2 logo block (after line 172) also add:\n```c\nif (CONFIG(USE_COREBOOT_FOR_BMP_RENDERING) && temp_mtrr_index >= 0)\n\tclear_var_mtrr(temp_mtrr_index);\n```\nor better, clear the temp MTRR once near the end of `do_silicon_init()` regardless of FSP version before any `return`.","locations":[{"ref":"src/drivers/intel/fsp2_0/silicon_init.c:128-129","code":" 128 | \tif (CONFIG(BMP_LOGO) && CONFIG(USE_COREBOOT_FOR_BMP_RENDERING))\n 129 | \t\ttemp_mtrr_index = soc_mark_gfx_memory();","lines":2,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/intel/fsp2_0/silicon_init.c#L128-L129"},{"ref":"src/drivers/intel/fsp2_0/silicon_init.c:164-180","code":" 164 | \t/* Only applies for SoC platforms prior to FSP 2.2 specification. */\n 165 | \tif (!CONFIG(PLATFORM_USES_FSP2_2) && CONFIG(BMP_LOGO)) {\n 166 | \t\tif (CONFIG(USE_COREBOOT_FOR_BMP_RENDERING))\n 167 | \t\t\tsoc_load_logo_by_coreboot();\n 168 | \t\t/*\n 169 | \t\t * This applies regardless of whether FSP or coreboot handled\n 170 | \t\t * the rendering.\n 171 | \t\t */\n 172 | \t\ttimestamp_add_now(TS_FIRMWARE_SPLASH_RENDERED);\n 173 | \t}\n 174 | \n 175 | \t/* Reinitialize CPUs if FSP-S has done MP Init */\n 176 | \tif (CONFIG(USE_INTEL_FSP_MP_INIT) && !fsp_is_multi_phase_init_enabled())\n 177 | \t\tdo_mpinit_after_fsp();\n 178 | \n 179 | \tif (!CONFIG(PLATFORM_USES_FSP2_2))\n 180 | \t\treturn;","lines":17,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/intel/fsp2_0/silicon_init.c#L164-L180"},{"ref":"src/drivers/intel/fsp2_0/silicon_init.c:238-241","code":" 238 | \t\t/* Clear temporary Write Combine (WC) MTRR */\n 239 | \t\tif (temp_mtrr_index >= 0)\n 240 | \t\t\tclear_var_mtrr(temp_mtrr_index);\n 241 | \t}","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/intel/fsp2_0/silicon_init.c#L238-L241"}]},
{"id":"LOGIC_68","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"medium","description":"In both bootlog_module_handle() and timestamps_module_handle(), the KEY_NPAGE and KEY_PPAGE cases have swapped comments. KEY_NPAGE is labeled 'Page up' but actually moves the cursor forward (increasing g_line), and KEY_PPAGE is labeled 'Page down' but decreases g_line. In ncurses, KEY_NPAGE is Next Page (Page Down) and KEY_PPAGE is Previous Page (Page Up), so the behavior is correct but the comments are backwards.","fix":"Swap the comments: KEY_NPAGE should be labeled 'Page down' and KEY_PPAGE should be labeled 'Page up'.","locations":[{"ref":"payloads/coreinfo/bootlog_module.c:190-194","code":" 190 | \tcase KEY_NPAGE: /* Page up */\n 191 | \t\tg_line -= LINES_SHOWN;\n 192 | \t\tbreak;\n 193 | \tcase KEY_PPAGE: /* Page down */\n 194 | \t\tg_line += LINES_SHOWN;","lines":5,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/coreinfo/bootlog_module.c#L190-L194"},{"ref":"payloads/coreinfo/timestamps_module.c:252-256","code":" 252 | \tcase KEY_NPAGE: /* Page up */\n 253 | \t\tg_line -= LINES_SHOWN;\n 254 | \t\tbreak;\n 255 | \tcase KEY_PPAGE: /* Page down */\n 256 | \t\tg_line += LINES_SHOWN;","lines":5,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/coreinfo/timestamps_module.c#L252-L256"}]},
{"id":"LOGIC_69","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"medium","description":"In cbfs_module_redraw(), when a file has an empty filename (strlen == 0), the code calls findfile(filenames[i])->type without checking if findfile returns NULL. Since CBFS is traversed from the beginning each call, findfile() on an empty string could return NULL if the CBFS structure is inconsistent or the file was logically deleted between calls. The NULL dereference causes a crash.","fix":"Check the findfile return value before dereferencing: struct cbfile *cf = findfile(filenames[i]); if (cf && cf->type == COMPONENT_NULL) ... else if (cf) ... else ...","locations":[{"ref":"payloads/coreinfo/cbfs_module.c:137-141","code":" 137 | \t\tif (strlen(filenames[i]) == 0) {\n 138 | \t\t\tif (findfile(filenames[i])->type == COMPONENT_NULL)\n 139 | \t\t\t\tmvwprintw(win, row, 1, \"<free space>\");\n 140 | \t\t\telse\n 141 | \t\t\t\tmvwprintw(win, row, 1, \"<unnamed>\");","lines":5,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/coreinfo/cbfs_module.c#L137-L141"}]},
{"id":"LOGIC_70","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"very_easy","description":"In curses.h, BUTTON1_TRIPLE_CLICKED (0x00000010L) and BUTTON1_MOVED (0x00000010L) share the same numeric value, and similarly for buttons 2 (0x200) and 3 (0x4000). Code testing these values cannot distinguish between a triple-click and a move event for any button.","fix":"Assign BUTTON1_MOVED (and equivalents for buttons 2 and 3) a distinct bit value not already used by TRIPLE_CLICKED. This matches the comment that describes MOVED as a PDCurses extension.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/curses.h:159-174","code":" 159 | #define BUTTON1_TRIPLE_CLICKED 0x00000010L\n 160 | #define BUTTON1_MOVED 0x00000010L /* PDCurses */\n 161 | \n 162 | #define BUTTON2_RELEASED 0x00000020L\n 163 | #define BUTTON2_PRESSED 0x00000040L\n 164 | #define BUTTON2_CLICKED 0x00000080L\n 165 | #define BUTTON2_DOUBLE_CLICKED 0x00000100L\n 166 | #define BUTTON2_TRIPLE_CLICKED 0x00000200L\n 167 | #define BUTTON2_MOVED 0x00000200L /* PDCurses */\n 168 | \n 169 | #define BUTTON3_RELEASED 0x00000400L\n 170 | #define BUTTON3_PRESSED 0x00000800L\n 171 | #define BUTTON3_CLICKED 0x00001000L\n 172 | #define BUTTON3_DOUBLE_CLICKED 0x00002000L\n 173 | #define BUTTON3_TRIPLE_CLICKED 0x00004000L\n 174 | #define BUTTON3_MOVED 0x00004000L /* PDCurses */","lines":16,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/curses.h#L159-L174"}]},
{"id":"LOGIC_71","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"very_easy","description":"In firework.c main(), the draw loop writes mvaddstr(LINES - row, ...) starting with row == 0 on the first iteration, which evaluates to row LINES. Curses rows are 0-indexed (valid range 0..LINES-1), so LINES is out of range. This typically causes the string to be silently discarded (the curses clipping) but the trajectory is drawn one row off: the rocket base appears off-screen on the first step.","fix":"Start the drawing at LINES - 1 - row: mvaddstr(LINES - 1 - row, row * direction + start, ...);","locations":[{"ref":"payloads/libpayload/curses/PDCurses/demos/firework.c:59-70","code":" 59 | for (row = 0; row < diff; row++)\n 60 | {\n 61 | mvaddstr(LINES - row, row * direction + start,\n 62 | (direction < 0) ? \"\\\\\" : \"/\");\n 63 | \n 64 | if (flag++)\n 65 | {\n 66 | myrefresh();\n 67 | erase();\n 68 | flag = 0;\n 69 | }\n 70 | }","lines":12,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/demos/firework.c#L59-L70"}]},
{"id":"LOGIC_72","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"medium","description":"In newdemo.c WaitForUser(), nodelay() is called twice in succession with opposite values (TRUE then FALSE) without a matching getch() in between. halfdelay(50) sets a half-second timeout, but then nodelay(FALSE) immediately overrides it, so the half-delay has no effect. This is dead/redundant setup code that makes the timing behavior hard to reason about.","fix":"Remove the initial nodelay(stdscr, TRUE) call since halfdelay(50) is what provides the intended timeout behavior.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/demos/newdemo.c:58-65","code":" 58 | nodelay(stdscr, TRUE);\n 59 | halfdelay(50);\n 60 | \n 61 | ch = getch();\n 62 | \n 63 | nodelay(stdscr, FALSE);\n 64 | nocbreak(); /* Reset the halfdelay() value */\n 65 | cbreak();","lines":8,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/demos/newdemo.c#L58-L65"}]},
{"id":"LOGIC_73","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"medium","description":"In tui.c padstr(), the fmt[] buffer is only 10 bytes. The format pattern written into it is '%%-%ds' (with sprintf expanding %d to the decimal representation of length). If length has more than 4 digits (i.e., >= 10000), the resulting format string exceeds 9 characters plus null terminator, overflowing fmt[10]. While COLS >= 10000 is unrealistic today, it is an unbounded assumption with no assertion.","fix":"Increase fmt[] to at least 20 bytes to accommodate any plausible column width, or use snprintf with the correct size.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/demos/tui.c:73-75","code":" 73 | char fmt[10];\n 74 | \n 75 | sprintf(fmt, (int)strlen(s) > length ? \"%%.%ds\" : \"%%-%ds\", length);","lines":3,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/demos/tui.c#L73-L75"}]},
{"id":"LOGIC_74","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"medium","description":"In tuidemo.c showfile(), EOF detection relies on strlen(buf) == 0 after fgets(). This works correctly because buf[0] is set to '\\0' before each fgets call, but a read error (ferror) is indistinguishable from EOF: both leave buf[0] == '\\0', so an I/O error causes silent termination of the display rather than an error message.","fix":"Check the return value of fgets directly: if (!fgets(buf, MAXSTRLEN, fp)) { ateof = TRUE; } instead of relying on strlen(buf) == 0.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/demos/tuidemo.c:92-99","code":" 92 | buf[0] = '\\0';\n 93 | fgets(buf, MAXSTRLEN, fp);\n 94 | \n 95 | if (strlen(buf))\n 96 | bodymsg(buf);\n 97 | else\n 98 | ateof = TRUE;\n 99 | }","lines":8,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/demos/tuidemo.c#L92-L99"}]},
{"id":"LOGIC_75","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"very_easy","description":"In border.c, wborder() does not check for a window that is too small (maxy < 1 or maxx < 1). If the window has height 1 or width 1, ymax=0 and xmax=0. The horizontal loop 'for (i = 1; i < xmax; i++)' runs 0 times (fine), but the corner assignments write to _y[0][0] and _y[0][xmax] which are the same cell (_y[0][0]) -- it gets written twice (tl, then br), leaving br in that cell. Similarly _y[ymax][0] and _y[ymax][xmax] are the same. This produces a visually garbled single-cell 'border'.","fix":"Add a check at the start of wborder(): if the window is too small (maxy < 2 or maxx < 2), return ERR.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/border.c:131-179","code":" 131 | int wborder(WINDOW *win, chtype ls, chtype rs, chtype ts, chtype bs,\n 132 | chtype tl, chtype tr, chtype bl, chtype br)\n 133 | {\n 134 | int i, ymax, xmax;\n 135 | \n 136 | PDC_LOG((\"wborder() - called\\n\"));\n 137 | \n 138 | if (!win)\n 139 | return ERR;\n 140 | \n 141 | ymax = win->_maxy - 1;\n 142 | xmax = win->_maxx - 1;\n 143 | \n 144 | ls = _attr_passthru(win, ls ? ls : ACS_VLINE);\n 145 | rs = _attr_passthru(win, rs ? rs : ACS_VLINE);\n 146 | ts = _attr_passthru(win, ts ? ts : ACS_HLINE);\n 147 | bs = _attr_passthru(win, bs ? bs : ACS_HLINE);\n 148 | tl = _attr_passthru(win, tl ? tl : ACS_ULCORNER);\n 149 | tr = _attr_passthru(win, tr ? tr : ACS_URCORNER);\n 150 | bl = _attr_passthru(win, bl ? bl : ACS_LLCORNER);\n 151 | br = _attr_passthru(win, br ? br : ACS_LRCORNER);\n 152 | \n 153 | for (i = 1; i < xmax; i++)\n 154 | {\n 155 | win->_y[0][i] = ts;\n 156 | win->_y[ymax][i] = bs;\n 157 | }\n 158 | \n 159 | for (i = 1; i < ymax; i++)\n 160 | {\n 161 | win->_y[i][0] = ls;\n 162 | win->_y[i][xmax] = rs;\n 163 | }\n 164 | \n 165 | win->_y[0][0] = tl;\n 166 | win->_y[0][xmax] = tr;\n 167 | win->_y[ymax][0] = bl;\n 168 | win->_y[ymax][xmax] = br;\n 169 | \n 170 | for (i = 0; i <= ymax; i++)\n 171 | {\n 172 | win->_firstch[i] = 0;\n 173 | win->_lastch[i] = xmax;\n 174 | }\n 175 | \n 176 | PDC_sync(win);\n 177 | \n 178 | return OK;\n 179 | }","lines":49,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/border.c#L131-L179"}]},
{"id":"LOGIC_76","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"very_easy","description":"In debug.c, the debug log timestamp uses 'localtime(&now)' which returns a pointer to a static buffer. If the code is ever used in a multi-threaded environment, concurrent PDC_debug() calls could corrupt each other's time strings. Additionally, the strftime format '\"At: %8.8ld - %s \"' intermixes a long (%ld, the clock value) with %s (the time string) but the format literal '\"At: %8.8ld - %s \"' is passed to fprintf as a fixed string with arguments in order, so there is no format mismatch. Still, the buffer size for hms is exactly 9 -- strftime with format '%H:%M:%S' produces exactly 8 chars + NUL, so this is correct but very tight with no margin for locale-specific differences.","fix":"Use localtime_r() in threaded environments. Increase hms buffer size for safety.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/debug.c:58-60","code":" 58 | time(&now);\n 59 | strftime(hms, 9, \"%H:%M:%S\", localtime(&now));\n 60 | fprintf(dbfp, \"At: %8.8ld - %s \", (long) clock(), hms);","lines":3,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/debug.c#L58-L60"}]},
{"id":"LOGIC_77","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"very_easy","description":"In getch.c, when buffering input (nocbreak/noraw mode), the backspace handler at line 276 decrements c_pindex below c_gindex without checking this bound: 'if (c_pindex > c_gindex) c_pindex--'. If c_gindex is 0 and c_pindex is 0 (equal), the backspace is silently discarded. But if multiple backspaces are pressed rapidly, c_pindex could theoretically reach 0 then be prevented from going negative by the guard -- this is correct. However after flushinp() sets c_gindex=1 and c_pindex=0, a subsequent backspace with c_pindex(0) <= c_gindex(1) is silently discarded even if there are notional characters, because the buffer is in an inconsistent state after flushinp().","fix":"After flushinp(), reset c_gindex to 0 (not 1) to be consistent with c_pindex=0, or ensure flushinp() leaves indices in a mutually consistent state.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/getch.c:275-279","code":" 275 | if (key == '\\b')\n 276 | {\n 277 | if (c_pindex > c_gindex)\n 278 | c_pindex--;\n 279 | }","lines":5,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/getch.c#L275-L279"},{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/getch.c:323-334","code":" 323 | int flushinp(void)\n 324 | {\n 325 | PDC_LOG((\"flushinp() - called\\n\"));\n 326 | \n 327 | PDC_flushinp();\n 328 | \n 329 | c_gindex = 1; /* set indices to kill buffer */\n 330 | c_pindex = 0;\n 331 | c_ungind = 0; /* clear c_ungch array */\n 332 | \n 333 | return OK;\n 334 | }","lines":12,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/getch.c#L323-L334"}]},
{"id":"LOGIC_78","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"very_easy","description":"In setsyx() (getyx.c, lines 138-141), when exactly one of y or x equals -1 (but not both), the function silently returns OK without doing anything. This differs from the documented behavior: setsyx(-1,-1) should set leaveit=TRUE; setsyx(y,x) should move the cursor. Having only one coordinate be -1 is an error condition that the function silently ignores instead of returning ERR.","fix":"Return ERR when exactly one of y/x is -1, or document the current behavior explicitly.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/getyx.c:138-141","code":" 138 | else if (y == -1 || x == -1)\n 139 | {\n 140 | return OK;\n 141 | }","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/getyx.c#L138-L141"}]},
{"id":"LOGIC_79","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"very_easy","description":"In inopts.c, the wtimeout() function has a commented-out line '/*win->_nodelay = TRUE;*/' (line 297) in the positive-delay branch. According to curses semantics, a positive delay should set the window into a timed blocking read, which requires nodelay to be FALSE (block until timeout). The comment suggests an earlier implementation tried setting nodelay=TRUE here. The current code leaves nodelay unchanged (whatever it was before wtimeout() was called), which may leave a previously-set nodelay=TRUE active when a positive delay is requested, causing wgetch() to return ERR immediately instead of waiting.","fix":"Explicitly set 'win->_nodelay = FALSE;' in the positive-delay branch so that wgetch() will block for _delayms milliseconds rather than returning immediately.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/inopts.c:291-299","code":" 291 | else\n 292 | {\n 293 | /* This causes the read on the window to delay for the number of\n 294 | milliseconds. Also forces the window into non-blocking read\n 295 | mode */\n 296 | \n 297 | /*win->_nodelay = TRUE;*/\n 298 | win->_delayms = delay;\n 299 | }","lines":9,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/inopts.c#L291-L299"}]},
{"id":"LOGIC_80","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"very_easy","description":"In outopts.c, wsetscrreg() validates the scroll region with the condition 'top <= win->_cury && win->_cury <= bottom' (line 125-126). This ties setting the scroll region to the current cursor position -- you cannot set a scroll region that does not contain the current cursor. The X/Open specification for wsetscrreg() says only that top and bottom must be within the window boundaries; it does not require the cursor to be within the region. This overly restrictive check will return ERR unexpectedly.","fix":"Remove the cursor position checks. The condition should be: 'win && 0 <= top && top <= bottom && bottom < win->_maxy'.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/outopts.c:121-135","code":" 121 | int wsetscrreg(WINDOW *win, int top, int bottom)\n 122 | {\n 123 | PDC_LOG((\"wsetscrreg() - called: top %d bottom %d\\n\", top, bottom));\n 124 | \n 125 | if (win && 0 <= top && top <= win->_cury &&\n 126 | win->_cury <= bottom && bottom < win->_maxy)\n 127 | {\n 128 | win->_tmarg = top;\n 129 | win->_bmarg = bottom;\n 130 | \n 131 | return OK;\n 132 | }\n 133 | else\n 134 | return ERR;\n 135 | }","lines":15,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/outopts.c#L121-L135"}]},
{"id":"LOGIC_81","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"medium","description":"_copy_win() in overlay.c does not reset the 'lc' (last-changed column) variable between row iterations. 'lc' is initialized to 0 before the outer loop and is only updated inside the inner loop when a character actually changes. If row N has changes (setting lc) and row N+1 has no changes (fc == _NO_CHANGE), the branch 'if (*minchng == _NO_CHANGE) { *minchng = fc; *maxchng = lc; }' writes the stale lc from row N into maxchng[N+1]. Since minchng[N+1] remains _NO_CHANGE, the refresh code will skip this row anyway, so there is no visual corruption, but the state is inconsistent and wastes effort if the row later becomes dirty.","fix":"Reset 'lc' at the start of each row iteration (alongside 'fc = _NO_CHANGE') so its value is never carried over between rows: add 'lc = _NO_CHANGE;' at line 77 (same place as 'fc = _NO_CHANGE;'). Adjust the maxchng update logic accordingly.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/overlay.c:56-113","code":" 56 | int lc = 0;\n 57 | int xdiff = src_bc - src_tc;\n 58 | int ydiff = src_br - src_tr;\n 59 | \n 60 | if (!src_w || !dst_w)\n 61 | return ERR;\n 62 | \n 63 | minchng = dst_w->_firstch;\n 64 | maxchng = dst_w->_lastch;\n 65 | \n 66 | for (y1 = 0; y1 < dst_tr; y1++)\n 67 | {\n 68 | minchng++;\n 69 | maxchng++;\n 70 | }\n 71 | \n 72 | for (line = 0; line < ydiff; line++)\n 73 | {\n 74 | w1ptr = src_w->_y[line + src_tr] + src_tc;\n 75 | w2ptr = dst_w->_y[line + dst_tr] + dst_tc;\n 76 | \n 77 | fc = _NO_CHANGE;\n 78 | \n 79 | for (col = 0; col < xdiff; col++)\n 80 | {\n 81 | if ((*w1ptr) != (*w2ptr) &&\n 82 | !((*w1ptr & A_CHARTEXT) == ' ' && _overlay))\n 83 | {\n 84 | *w2ptr = *w1ptr;\n 85 | \n 86 | if (fc == _NO_CHANGE)\n 87 | fc = col + dst_tc;\n 88 | \n 89 | lc = col + dst_tc;\n 90 | }\n 91 | \n 92 | w1ptr++;\n 93 | w2ptr++;\n 94 | }\n 95 | \n 96 | if (*minchng == _NO_CHANGE)\n 97 | {\n 98 | *minchng = fc;\n 99 | *maxchng = lc;\n 100 | }\n 101 | else if (fc != _NO_CHANGE)\n 102 | {\n 103 | if (fc < *minchng)\n 104 | *minchng = fc;\n 105 | if (lc > *maxchng)\n 106 | *maxchng = lc;\n 107 | }\n 108 | \n 109 | minchng++;\n 110 | maxchng++;\n 111 | }\n 112 | \n 113 | return OK;","lines":58,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/overlay.c#L56-L113"}]},
{"id":"LOGIC_82","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"medium","description":"In sdl1/pdcclip.c PDC_setclipboard(), contents is copied with strcpy() (line\n86) using a buffer allocated to `length + 1` bytes, where `length` is caller-supplied. If the\nactual string length of `contents` is greater than `length` (caller passes a shorter length than\nthe string), strcpy copies beyond the allocation, overflowing the heap buffer.","fix":"Use `memcpy(pdc_SDL_clipboard, contents, length); pdc_SDL_clipboard[length] = '\\0';`\ninstead of strcpy, which respects the caller-supplied length.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/sdl1/pdcclip.c:71-89","code":" 71 | int PDC_setclipboard(const char *contents, long length)\n 72 | {\n 73 | PDC_LOG((\"PDC_setclipboard() - called\\n\"));\n 74 | \n 75 | if (pdc_SDL_clipboard)\n 76 | {\n 77 | free(pdc_SDL_clipboard);\n 78 | pdc_SDL_clipboard = NULL;\n 79 | }\n 80 | \n 81 | if (contents)\n 82 | {\n 83 | if ((pdc_SDL_clipboard = malloc(length + 1)) == NULL)\n 84 | return PDC_CLIP_MEMORY_ERROR;\n 85 | \n 86 | strcpy(pdc_SDL_clipboard, contents);\n 87 | }\n 88 | \n 89 | return PDC_CLIP_SUCCESS;","lines":19,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/sdl1/pdcclip.c#L71-L89"}]},
{"id":"LOGIC_83","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"medium","description":"`init_pair()` rejects `index == 0` (returns ERR), but `pair_content()` allows\n`index == 0` (only rejects `index < 0`). The color_pairs[0] entry is initialized\nto `0x07` (white-on-black default pair). This inconsistency means the default\npair (index 0) can be read but not written, which is intentional per POSIX, but\nthe divergent guard conditions (`index == 0 || index > 255` vs `index < 0 ||\nindex > 255`) make the intent invisible and fragile. A future maintainer adding\nnegative-index handling to `init_pair` might accidentally align it with\n`pair_content`'s check and silently allow writes to pair 0.","fix":"Add a comment to both functions explaining why the index==0 treatment differs, or use a shared inline helper that encodes the policy explicitly.","locations":[{"ref":"payloads/libpayload/curses/colors.c:45-46","code":" 45 | \tif (index == 0 || index > 255)\n 46 | \t\treturn ERR;","lines":2,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/colors.c#L45-L46"},{"ref":"payloads/libpayload/curses/colors.c:54-55","code":" 54 | \tif (index < 0 || index > 255)\n 55 | \t\treturn ERR;","lines":2,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/colors.c#L54-L55"}]},
{"id":"LOGIC_84","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"medium","description":"In data_ahead (frm_data.c), when checking for off-screen data in a multi-line field, the window position passed to wmove uses 'pos' as the row but pos is an absolute buffer row index, not a window-relative row. After pos exceeds field->rows (the visible window height), wmove will fail and Only_Padding will treat the ERR result as 'no data found' (returns TRUE), causing data_ahead to underreport off-screen content for fields taller than their visible area.","fix":"When form->w is a pad (scrollable multi-line field), wmove coordinates must be relative to the pad's total size, which they are since form->w is allocated as newpad(drows, dcols). Verify that the pad is large enough for the absolute row 'pos'; if form->w is a derived window instead, the row must be adjusted by subtracting form->toprow.","locations":[{"ref":"payloads/libpayload/curses/form/frm_data.c:173-184","code":" 173 | \t pos = form->toprow + field->rows;\n 174 | \t while (pos < field->drows)\n 175 | \t {\n 176 | \t cursor_moved = TRUE;\n 177 | \t wmove(form->w, pos, 0);\n 178 | \t pos++;\n 179 | \t if (!Only_Padding(form->w, field->cols, field->pad))\n 180 | \t\t{\n 181 | \t\t result = TRUE;\n 182 | \t\t break;\n 183 | \t\t}\n 184 | \t }","lines":12,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/frm_data.c#L173-L184"}]},
{"id":"LOGIC_85","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"medium","description":"_nc_First_Active_Field (frm_driver.c) falls back to returning *first (the pmin field) when no visible field is found on the page. However, the variable 'first' points to form->field[pmin], which is a pointer into the field pointer array; the dereference (*first) is correct. But the function comment says it 'hopes the best', meaning the returned field may be invisible and non-selectable. Callers such as Associate_Fields call form_driver(form, FIRST_ACTIVE_MAGIC) which assigns this potentially invisible field as form->current without further validation, allowing subsequent driver operations to act on an invisible field.","fix":"After _nc_First_Active_Field returns, verify the returned field is at least selectable before assigning it as form->current, or treat a form page with no usable fields as an error condition.","locations":[{"ref":"payloads/libpayload/curses/form/frm_driver.c:3256-3295","code":" 3256 | NCURSES_EXPORT(FIELD *)\n 3257 | _nc_First_Active_Field(FORM *form)\n 3258 | {\n 3259 | FIELD **last_on_page = &form->field[form->page[form->curpage].pmax];\n 3260 | FIELD *proposed = Next_Field_On_Page(*last_on_page);\n 3261 | \n 3262 | if (proposed == *last_on_page)\n 3263 | {\n 3264 | /* there might be the special situation, where there is no\n 3265 | active and visible field on the current page. We then select\n 3266 | the first visible field on this readonly page\n 3267 | */\n 3268 | if (Field_Is_Not_Selectable(proposed))\n 3269 | \t{\n 3270 | \t FIELD **field = &form->field[proposed->index];\n 3271 | \t FIELD **first = &form->field[form->page[form->curpage].pmin];\n 3272 | \n 3273 | \t do\n 3274 | \t {\n 3275 | \t field = (field == last_on_page) ? first : field + 1;\n 3276 | \t if (((*field)->opts & O_VISIBLE))\n 3277 | \t\tbreak;\n 3278 | \t }\n 3279 | \t while (proposed != (*field));\n 3280 | \n 3281 | \t proposed = *field;\n 3282 | \n 3283 | \t if ((proposed == *last_on_page) && !(proposed->opts & O_VISIBLE))\n 3284 | \t {\n 3285 | \t /* This means, there is also no visible field on the page.\n 3286 | \t So we propose the first one and hope the very best...\n 3287 | \t Some very clever user has designed a readonly and invisible\n 3288 | \t page on this form.\n 3289 | \t */\n 3290 | \t proposed = *first;\n 3291 | \t }\n 3292 | \t}\n 3293 | }\n 3294 | return (proposed);\n 3295 | }","lines":40,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/frm_driver.c#L3256-L3295"},{"ref":"payloads/libpayload/curses/form/frm_def.c:271-273","code":" 271 | \t{\n 272 | \t form->curpage = 0;\n 273 | \t form_driver(form, FIRST_ACTIVE_MAGIC);","lines":3,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/frm_def.c#L271-L273"}]},
{"id":"LOGIC_86","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"medium","description":"In Next_Enum (fty_enum.c), when no exact match is found in the keyword list and\ncnt drops to -1, the code wraps to args->kwds[0] and sets the field to *kwds\n(the first keyword). However, if the current field content is empty and matches\nthe dummy empty array exactly via the Compare(dummy,...) path, *kwds at that point\nis args->kwds[0] only if cnt wrapped to 0; but if cnt went negative (-1), the\nassignment 'kwds = args->kwds' points to kwds[0]. The subtle issue is that\n'cnt <= 0' triggers a reset to args->kwds (start), but the comparison afterwards\nuses 'cnt >= 0' which is FALSE when cnt == -1 after the last decrement. So if\nno match is found AND the field is not empty matching dummy, FALSE is correctly\nreturned. But when cnt exits the loop at -1 (no match found), the dummy compare\nmay advance past the array. The condition at line 358 'if (cnt <= 0)' resets kwds\nbut then 'if (cnt >= 0)' on line 360 is only true if cnt==0 not cnt==-1, creating\nan off-by-one: wrapping occurs but the next item is skipped when the field is empty.","fix":"After finding no match (cnt exhausted to -1) and wrapping kwds, the intent is to\nload the first keyword. Separate the wrap and the write logic more clearly,\nand verify cnt==0 (last item) vs cnt==-1 (exhausted) to correctly handle cycling.","locations":[{"ref":"payloads/libpayload/curses/form/fty_enum.c:343-367","code":" 343 | Next_Enum(FIELD *field, const void *argp)\n 344 | {\n 345 | const enumARG *args = (const enumARG *)argp;\n 346 | char **kwds = args->kwds;\n 347 | bool ccase = args->checkcase;\n 348 | int cnt = args->count;\n 349 | unsigned char *bp = (unsigned char *)field_buffer(field, 0);\n 350 | \n 351 | if (kwds)\n 352 | {\n 353 | while (cnt--)\n 354 | \t{\n 355 | \t if (Compare((unsigned char *)(*kwds++), bp, ccase) == EXACT)\n 356 | \t break;\n 357 | \t}\n 358 | if (cnt <= 0)\n 359 | \tkwds = args->kwds;\n 360 | if ((cnt >= 0) || (Compare((const unsigned char *)dummy, bp, ccase) == EXACT))\n 361 | \t{\n 362 | \t set_field_buffer(field, 0, *kwds);\n 363 | \t return TRUE;\n 364 | \t}\n 365 | }\n 366 | return FALSE;\n 367 | }","lines":25,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/fty_enum.c#L343-L367"}]},
{"id":"LOGIC_87","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"medium","description":"In m_global.c _nc_Link_Items, the column-major branch uses variable 'i' (declared\nat the top scope as 'int i, j') as both the outer row-major loop variable (set by\nthe row-major branch above, now out of scope) and as a computed index inside the\ncolumn-major branch at line 449: 'item = menu->items[i = (col * Number_Of_Rows + row)]'.\nWhile 'i' is correctly reassigned on each iteration, mixing two loop variables\n(j for the iteration count, i for the flat index) reduces clarity and creates\na hidden dependency on the declaration scope. Additionally, variable 'last_pos'\nreturned by compile() in fty_regex.c is assigned but never used in either\nHAVE_REGEXP_H_FUNCS or HAVE_REGEXPR_H_FUNCS path.","fix":"In _nc_Link_Items, rename the flat index to 'flat_idx' and declare it inside\nthe else block for clarity.\nIn fty_regex.c, remove the unused 'last_pos' variable or suppress the warning,\nsince the result of compile() is only relevant via the side-effect on reg_errno.","locations":[{"ref":"payloads/libpayload/curses/menu/m_global.c:447-492","code":" 447 | \t for (j = 0; j < Number_Of_Items; j++)\n 448 | \t {\n 449 | \t item = menu->items[i = (col * Number_Of_Rows + row)];\n 450 | \n 451 | \t Last_in_Column = (menu->cols - 1) * Number_Of_Rows + row;\n 452 | \n 453 | \t item->left = (col) ?\n 454 | \t\tmenu->items[i - Number_Of_Rows] :\n 455 | \t\t(cycle ? (Last_in_Column >= Number_Of_Items) ?\n 456 | \t\t menu->items[Last_in_Column - Number_Of_Rows] :\n 457 | \t\t menu->items[Last_in_Column] :\n 458 | \t\t (ITEM *) 0);\n 459 | \n 460 | \t item->right = ((i + Number_Of_Rows) < Number_Of_Items)\n 461 | \t\t?\n 462 | \t\tmenu->items[i + Number_Of_Rows] :\n 463 | \t\t(cycle ? menu->items[row] : (ITEM *) 0);\n 464 | \n 465 | \t Last_in_Row = col * Number_Of_Rows + (Number_Of_Rows - 1);\n 466 | \n 467 | \t item->up = (row) ?\n 468 | \t\tmenu->items[i - 1] :\n 469 | \t\t(cycle ?\n 470 | \t\t menu->items[(Last_in_Row >= Number_Of_Items) ?\n 471 | \t\t\t Number_Of_Items - 1 :\n 472 | \t\t\t Last_in_Row] :\n 473 | \t\t (ITEM *) 0);\n 474 | \n 475 | \t item->down = (row < (Number_Of_Rows - 1))\n 476 | \t\t?\n 477 | \t\t(menu->items[((i + 1) < Number_Of_Items) ?\n 478 | \t\t\t i + 1 :\n 479 | \t\t\t (col - 1) * Number_Of_Rows + row + 1]) :\n 480 | \t\t(cycle ?\n 481 | \t\t menu->items[col * Number_Of_Rows] :\n 482 | \t\t (ITEM *) 0\n 483 | \t\t);\n 484 | \n 485 | \t item->x = col;\n 486 | \t item->y = row;\n 487 | \t if ((++row) == Number_Of_Rows)\n 488 | \t\t{\n 489 | \t\t col++;\n 490 | \t\t row = 0;\n 491 | \t\t}\n 492 | \t }","lines":46,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/menu/m_global.c#L447-L492"},{"ref":"payloads/libpayload/curses/form/fty_regex.c:164-169","code":" 164 | #if HAVE_REGEXP_H_FUNCS\n 165 | \t\t char *last_pos = compile(rx, buf, &buf[blen], '\\0');\n 166 | \n 167 | #else /* HAVE_REGEXPR_H_FUNCS */\n 168 | \t\t char *last_pos = compile(rx, buf, &buf[blen]);\n 169 | #endif","lines":6,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/fty_regex.c#L164-L169"}]},
{"id":"LOGIC_88","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"very_easy","description":"In m_req_name.c menu_request_by_name(), after the inner toupper loop terminates (when i reaches sizeof(buf) or a NUL), i is reused for the outer search loop starting at line 116. The variable `i` is declared as `unsigned int`, so `i = 0` before the outer loop is fine, but the reuse across two semantically distinct loop bodies is confusing and error-prone; if the inner loop exits due to i == sizeof(buf) (16), the outer loop's `for (i = 0; ...)` correctly resets i. This is not a runtime bug but indicates fragile code.","fix":"Use separate variable names for the two loops, e.g., `unsigned int j` for the toupper pass.","locations":[{"ref":"payloads/libpayload/curses/menu/m_req_name.c:102-122","code":" 102 | unsigned int i = 0;\n 103 | char buf[16];\n 104 | \n 105 | T((T_CALLED(\"menu_request_by_name(%s)\"), _nc_visbuf(str)));\n 106 | \n 107 | if (str)\n 108 | {\n 109 | strncpy(buf, str, sizeof(buf));\n 110 | while ((i < sizeof(buf)) && (buf[i] != '\\0'))\n 111 | \t{\n 112 | \t buf[i] = (char)toupper(UChar(buf[i]));\n 113 | \t i++;\n 114 | \t}\n 115 | \n 116 | for (i = 0; i < A_SIZE; i++)\n 117 | \t{\n 118 | \t if (strncmp(request_names[i], buf, sizeof(buf)) == 0)\n 119 | \t returnCode(MIN_MENU_COMMAND + (int)i);\n 120 | \t}\n 121 | }\n 122 | RETURN(E_NO_MATCH);","lines":21,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/menu/m_req_name.c#L102-L122"}]},
{"id":"LOGIC_89","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"medium","description":"In pdcscrn.c PDC_scr_open(), if SP allocation succeeds but pdc_atrtab allocation fails (line 64), the already-allocated SP is not freed before returning ERR, leaking SP.","fix":"Check allocations separately: if SP allocation succeeds but pdc_atrtab fails, free(SP) before returning ERR. Or check them with an if/else chain.","locations":[{"ref":"payloads/libpayload/curses/pdcurses-backend/pdcscrn.c:61-65","code":" 61 | SP = calloc(1, sizeof(SCREEN));\n 62 | pdc_atrtab = calloc(PDC_COLOR_PAIRS * PDC_OFFSET, 1);\n 63 | \n 64 | if (!SP || !pdc_atrtab)\n 65 | return ERR;","lines":5,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/pdcurses-backend/pdcscrn.c#L61-L65"}]},
{"id":"LOGIC_90","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"very_easy","description":"In tinycurses.c vwprintw(), vsprintf writes into the global `sprintf_tmp[1024]` buffer (line 497). This is a fixed-size global buffer with no length protection; a format string producing more than 1024 bytes will overflow into adjacent globals. The comment on line 163 calls this 'Ugly (and insecure!) hack!'. In a bare-metal BIOS context this can cause silent data corruption in other global variables.","fix":"Replace vsprintf with vsnprintf(sprintf_tmp, sizeof(sprintf_tmp), fmt, argp) to prevent overflow.","locations":[{"ref":"payloads/libpayload/curses/tinycurses.c:163-164","code":" 163 | // FIXME: Ugly (and insecure!) hack!\n 164 | char sprintf_tmp[1024];","lines":2,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/tinycurses.c#L163-L164"},{"ref":"payloads/libpayload/curses/tinycurses.c:495-501","code":" 495 | int vwprintw(WINDOW *win, const char *fmt, va_list argp)\n 496 | {\n 497 | \tvsprintf((char *)&sprintf_tmp, fmt, argp);\n 498 | \n 499 | \t/* TODO: Error handling? */\n 500 | \treturn waddstr(win, (char *)&sprintf_tmp);\n 501 | }","lines":7,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/tinycurses.c#L495-L501"}]},
{"id":"LOGIC_91","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"medium","description":"In cbmem_console.c cbmem_console_write(), do_write is called with a potentially zero-length `count` on the last iteration when `still_fits == count` (i.e., count becomes 0 after `count -= still_fits`). The subsequent do_write(buffer, 0) is harmless (memcpy of 0 bytes) but the loop condition `(cbmem_cons->cursor & CURSOR_MASK) + count >= cbmem_cons->size` with count=0 would be false, so the loop exits correctly. However, if cbmem_cons->size is 0 (pathological but possible if the console was improperly initialized), the loop condition divides no bytes but `still_fits = 0 - 0 = 0`, leading to an infinite loop because count never decreases below 0.","fix":"Add a guard at the top of cbmem_console_write(): `if (!cbmem_cons || !cbmem_cons->size) return;`","locations":[{"ref":"payloads/libpayload/drivers/cbmem_console.c:66-81","code":" 66 | void cbmem_console_write(const void *buffer, size_t count)\n 67 | {\n 68 | \tstruct cbmem_console *const cbmem_cons = phys_to_virt(cbmem_console_p);\n 69 | \n 70 | \twhile ((cbmem_cons->cursor & CURSOR_MASK) + count >=\n 71 | \t cbmem_cons->size) {\n 72 | \t\tsize_t still_fits = cbmem_cons->size - (cbmem_cons->cursor & CURSOR_MASK);\n 73 | \t\tdo_write(buffer, still_fits);\n 74 | \t\tcbmem_cons->cursor &= ~CURSOR_MASK;\n 75 | \t\tcbmem_cons->cursor |= OVERFLOW;\n 76 | \t\tbuffer += still_fits;\n 77 | \t\tcount -= still_fits;\n 78 | \t}\n 79 | \n 80 | \tdo_write(buffer, count);\n 81 | }","lines":16,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/cbmem_console.c#L66-L81"}]},
{"id":"LOGIC_92","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"medium","description":"In mouse.c mouse_state(), mouse_buf_idx is a module-level variable but mouse_sample() calls insert_buf() and mouse_decode(), which can leave mouse_buf_idx at an intermediate count between calls. If mouse_state() is called while a partial packet is being assembled (e.g., only 2 bytes of a 3-byte packet arrived), the x_axis and y_axis reported will be stale or zero even though data has arrived. This is expected for streaming protocols, but the zero-and-reset of x_axis/y_axis on every call to mouse_state() means partial data that hasn't been decoded yet is effectively discarded from the accumulator perspective. This is a design-level issue rather than a crash bug.","fix":"Document that get_state() may return 0 for axes even when bytes are in-flight. Consider returning a 'data_ready' flag or only resetting accumulators when a full packet was decoded.","locations":[{"ref":"payloads/libpayload/drivers/i8042/mouse.c:189-210","code":" 189 | static void mouse_state(int *x, int *y, int *z, u32 *b)\n 190 | {\n 191 | \tif (!initialized)\n 192 | \t\treturn;\n 193 | \n 194 | \tmouse_sample();\n 195 | \n 196 | \tif (x) {\n 197 | \t\t*x = x_axis;\n 198 | \t\tx_axis = 0;\n 199 | \t}\n 200 | \tif (y) {\n 201 | \t\t*y = y_axis;\n 202 | \t\ty_axis = 0;\n 203 | \t}\n 204 | \tif (z) {\n 205 | \t\t*z = z_axis;\n 206 | \t\tz_axis = 0;\n 207 | \t}\n 208 | \tif (b)\n 209 | \t\t*b = buttons;\n 210 | }","lines":22,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/i8042/mouse.c#L189-L210"}]},
{"id":"LOGIC_93","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"very_easy","description":"In mouse_cursor.c mouse_cusor_is_fast() (line 77), the function name has a typo: 'cusor' instead of 'cursor'. This is a cosmetic issue but affects API clarity.","fix":"Rename to mouse_cursor_is_fast().","locations":[{"ref":"payloads/libpayload/drivers/mouse_cursor.c:76-79","code":" 76 | static u8 mouse_cusor_is_fast(int x, int y)\n 77 | {\n 78 | \treturn (x * x + y * y) > (mouse_acceleration * mouse_acceleration);\n 79 | }","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/mouse_cursor.c#L76-L79"}]},
{"id":"LOGIC_94","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"medium","description":"pci_ops.c pci_read_resource() takes an int bar parameter with no bounds check. A negative bar or bar > 5 results in an out-of-range config space offset (0x10 + bar*4), which for a negative bar would access vendor/device ID registers, and for large bar values would access beyond the 256-byte standard config space.","fix":"Add a bounds check: if (bar < 0 || bar > 5) return 0xFFFFFFFF; before the config read.","locations":[{"ref":"payloads/libpayload/drivers/pci_ops.c:83-85","code":" 83 | u32 pci_read_resource(pcidev_t dev, int bar)\n 84 | {\n 85 | \treturn pci_read_config32(dev, 0x10 + (bar * 4));","lines":3,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/pci_ops.c#L83-L85"}]},
{"id":"LOGIC_95","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"medium","description":"In serial/8250.c, serial_putchar() busy-waits on the TX empty bit (bit 5 of LSR) with no timeout (line 150), and serial_getchar() busy-waits for data ready with no timeout (line 168). If the UART is locked up or misconfigured, both functions spin forever, hanging the payload with no way to recover.","fix":"Add a retry counter or a time-based deadline to both loops. On timeout, serial_putchar can silently drop the character, and serial_getchar can return -1 to indicate no data.","locations":[{"ref":"payloads/libpayload/drivers/serial/8250.c:150","code":" 150 | \twhile ((serial_read_reg(0x05) & 0x20) == 0) ;","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/serial/8250.c#L150"},{"ref":"payloads/libpayload/drivers/serial/8250.c:168","code":" 168 | \twhile (!serial_havechar()) ;","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/serial/8250.c#L168"}]},
{"id":"LOGIC_96","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"easy","description":"In `write_acpi_tables()` the locals `char oem_id[6], oem_table_id[8];` are only initialized in the non-QEMU path (from `slic_file` or the `OEM_ID`/`ACPI_TABLE_CREATOR` defaults). In the QEMU `fw_cfg_acpi_tables()` branch they are used while still uninitialized: `acpi_write_rsdp(rsdp, existing_rsdt, xsdt, oem_id)` copies `oem_id` into `rsdp->oem_id` (via `memcpy(rsdp->oem_id, oem_id, 6)`), and `memcpy(&ssdt->oem_table_id, oem_table_id, 8)` copies the uninitialized `oem_table_id` into the synthesized SSDT. This reads indeterminate stack bytes (undefined behavior) and puts garbage OEM identifiers into the RSDP and SSDT on QEMU targets that use `fw_cfg` (e.g. `qemu-i440fx`/`q35`).","fix":"Initialize the OEM identifiers before the `fw_cfg` branch (e.g. `memcpy(oem_id, OEM_ID, 6); memcpy(oem_table_id, ACPI_TABLE_CREATOR, 8);` right after the declarations), or set them inside the QEMU branch before `acpi_write_rsdp()`/SSDT construction.","locations":[{"ref":"src/acpi/acpi.c:1504","code":" 1504 | \tchar oem_id[6], oem_table_id[8];","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/acpi/acpi.c#L1504"},{"ref":"src/acpi/acpi.c:1571-1572","code":" 1571 | \t\t\tacpi_write_rsdp(rsdp, existing_rsdt, xsdt, oem_id);\n 1572 | \t\t\tacpi_write_xsdt(xsdt, oem_id, oem_table_id);","lines":2,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/acpi/acpi.c#L1571-L1572"},{"ref":"src/acpi/acpi.c:1591-1592","code":" 1591 | \t\tmemcpy(&ssdt->oem_table_id, oem_table_id, 8);\n 1592 | \t\tssdt->oem_revision = 42;","lines":2,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/acpi/acpi.c#L1591-L1592"}]},
{"id":"LOGIC_97","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"easy","description":"DPTF passive-policy usage is gated inconsistently. `dptf_write_enabled_policies()` decides `is_passive_used = (passive_count && passive_policies[0].target != DPTF_NONE)` (checks `.target`), but the actual emitters `write_thermal_relationship_table()` and `write_all_PSV()` start/stop on `policies[i].source == DPTF_NONE` (check `.source`). If a board configures a passive policy whose `source` is set but whose `target` is `DPTF_NONE`, `_TRT`/`_PSV` objects are still emitted while the `IDSP` package advertised to the OS omits the passive-policy UUID (or vice-versa), so the advertised capability set and the generated methods can disagree.","fix":"Use the same field for the enabled-policy gate as the emitters use for termination. Change the `is_passive_used` check to test `passive_policies[0].source != DPTF_NONE` (matching `write_thermal_relationship_table()`/`write_all_PSV()`), or make all three consistently test the same field.","locations":[{"ref":"src/acpi/acpigen_dptf.c:528","code":" 528 | \tis_passive_used = (passive_count && passive_policies[0].target != DPTF_NONE);","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/acpi/acpigen_dptf.c#L528"},{"ref":"src/acpi/acpigen_dptf.c:255-258","code":" 255 | \tfor (i = 0; i < max_count; ++i) {\n 256 | \t\t/* Stop writing the table once an entry is empty */\n 257 | \t\tif (policies[i].source == DPTF_NONE)\n 258 | \t\t\tbreak;","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/acpi/acpigen_dptf.c#L255-L258"},{"ref":"src/acpi/acpigen_dptf.c:290-292","code":" 290 | \tfor (i = 0; i < max_count; ++i) {\n 291 | \t\tif (policies[i].source == DPTF_NONE)\n 292 | \t\t\tbreak;","lines":3,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/acpi/acpigen_dptf.c#L290-L292"}]},
{"id":"LOGIC_98","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"very_easy","description":"In `multilane_slave_link_names[]` the eighth entry (index 7) is `SDW_PFX \"slave-link-I\"`, but SoundWire slave links are labeled A through H, so index 7 should be `slave-link-H`. The letter `H` is skipped and `I` used instead, so `soundwire_gen_multilane()` emits an incorrect MIPI SoundWire property name (`mipi-sdw-slave-link-I`) for the last lane, which will not match what the OS driver expects.","fix":"Change the last array element from `SDW_PFX \"slave-link-I\"` to `SDW_PFX \"slave-link-H\"`.\n\nBefore:\n```c\n\tSDW_PFX \"slave-link-G\",\n\tSDW_PFX \"slave-link-I\"\n```\nAfter:\n```c\n\tSDW_PFX \"slave-link-G\",\n\tSDW_PFX \"slave-link-H\"\n```","locations":[{"ref":"src/acpi/soundwire.c:112-121","code":" 112 | static const char * const multilane_slave_link_names[] = {\n 113 | \tSDW_PFX \"slave-link-A\",\n 114 | \tSDW_PFX \"slave-link-B\",\n 115 | \tSDW_PFX \"slave-link-C\",\n 116 | \tSDW_PFX \"slave-link-D\",\n 117 | \tSDW_PFX \"slave-link-E\",\n 118 | \tSDW_PFX \"slave-link-F\",\n 119 | \tSDW_PFX \"slave-link-G\",\n 120 | \tSDW_PFX \"slave-link-I\"\n 121 | };","lines":10,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/acpi/soundwire.c#L112-L121"}]},
{"id":"LOGIC_99","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"medium","description":"`transition_to_el2()` declares a local `struct exc_state exc_state;` that is NOT zero-initialized and only fills three members (`regs->x[X0_INDEX]`, `elx->elr`, `elx->spsr`). It then executes `raw_write_sp_el2(elx->sp_elx)`, reading the uninitialized `sp_elx` field and programming garbage stack memory into `SP_EL2`. The transition library contract (see `transition.h`) states the target program must set up its own stack, so in practice the payload overwrites `SP` before use, but this is a genuine read of uninitialized stack memory driving an architectural register write (undefined behavior, and a latent hang if a future EL2 entry path relies on the incoming `SP`). The remaining unset members (`regs->sp`, `regs->x[1..30]`) are also consumed by `exc_exit`/`trans_switch` from uninitialized memory.","fix":"Zero-initialize the state (`struct exc_state exc_state = {0};`) so `sp_elx` and the unused `x[]`/`sp` fields are deterministic, or drop the `raw_write_sp_el2(elx->sp_elx)` line entirely since the contract makes the payload responsible for its own stack.","locations":[{"ref":"src/arch/arm64/transition.c:33-77","code":" 33 | void transition_to_el2(void *entry, void *arg, uint64_t spsr)\n 34 | {\n 35 | \tstruct exc_state exc_state;\n 36 | \tstruct elx_state *elx = &exc_state.elx;\n 37 | \tstruct regs *regs = &exc_state.regs;\n 38 | \n 39 | \tregs->x[X0_INDEX] = (uint64_t)arg;\n 40 | \telx->elr = (uint64_t)entry;\n 41 | \telx->spsr = spsr;\n 42 | \n 43 | \t/*\n 44 | \t * Policies enforced:\n 45 | \t * 1. We support only transitions to EL2\n 46 | \t * 2. We support transitions to Aarch64 mode only\n 47 | \t *\n 48 | \t * If any of the above conditions holds false, then we need a proper way\n 49 | \t * to update SCR/HCR before removing the checks below\n 50 | \t */\n 51 | \tassert(get_el_from_spsr(spsr) == EL2 && !(spsr & SPSR_ERET_32));\n 52 | \n 53 | \t/* Initialize SCR with defaults for running without secure monitor\n 54 | \t (disable all traps, enable all instructions, run NS at AArch64). */\n 55 | \traw_write_scr_el3(SCR_FIEN | SCR_API | SCR_APK | SCR_ST | SCR_RW |\n 56 | \t\t\t SCR_HCE | SCR_SMD | SCR_RES1 | SCR_NS);\n 57 | \n 58 | \t/* Initialize CPTR to not trap anything to EL3. */\n 59 | \traw_write_cptr_el3(CPTR_EL3_TCPAC_DISABLE | CPTR_EL3_TTA_DISABLE |\n 60 | \t\t\t CPTR_EL3_TFP_DISABLE);\n 61 | \n 62 | \t/* ELR/SPSR: Write entry point and processor state of program */\n 63 | \traw_write_elr_el3(elx->elr);\n 64 | \traw_write_spsr_el3(elx->spsr);\n 65 | \n 66 | \t/* SCTLR: Initialize EL with everything disabled */\n 67 | \traw_write_sctlr_el2(SCTLR_RES1);\n 68 | \n 69 | \t/* SP_ELx: Initialize stack pointer */\n 70 | \traw_write_sp_el2(elx->sp_elx);\n 71 | \n 72 | \t/* Payloads expect to be entered with MMU disabled. Includes an ISB. */\n 73 | \tmmu_disable();\n 74 | \n 75 | \t/* Eret to the entry point */\n 76 | \ttrans_switch(regs);\n 77 | }","lines":45,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/arch/arm64/transition.c#L33-L77"}]},
{"id":"LOGIC_100","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"medium","description":"`generate_pmp_napot()` receives `base` and `size` already converted to word units (`b = base >> PMP_SHIFT`, `s = size >> PMP_SHIFT` in `generate_pmp()`), but then decides `NA4` vs `NAPOT` with `size > GRANULE` where `GRANULE` is `1 << PMP_SHIFT` (== `4`, a byte granule). This is a unit mismatch: the threshold should be `1` word (only an exactly-4-byte region is `NA4`). As written, word-count regions of `2` (8 bytes) and `4` (16 bytes) are wrongly encoded as `PMP_NA4`, which protects only 4 bytes -> silent under-protection of the upper part of the range. Additionally the address formula `base + (size / 2 - 1)` is only correct for `NAPOT`; for a true `NA4` region (`size == 1`) it evaluates to `base + (0 - 1) == base - 1`, an incorrect `pmpaddr`. Real callers (`setup_pmp` for `_text.._estack`, SoC ranges) use large regions so impact is latent, but the encoding is wrong for any region of 16 bytes or smaller.","fix":"Use word-unit thresholds and a correct NA4 address, e.g.:\n```\np->cfg = flags | (size > 1 ? PMP_NAPOT : PMP_NA4);\np->previous_address = 0;\np->address = (size > 1) ? (base + (size / 2 - 1)) : base;\n```","locations":[{"ref":"src/arch/riscv/pmp.c:238-244","code":" 238 | static void generate_pmp_napot(struct pmpcfg *p, uintptr_t base, uintptr_t size, u8 flags)\n 239 | {\n 240 | \tflags = flags & (PMP_R | PMP_W | PMP_X | PMP_L);\n 241 | \tp->cfg = flags | (size > GRANULE ? PMP_NAPOT : PMP_NA4);\n 242 | \tp->previous_address = 0;\n 243 | \tp->address = (base + (size / 2 - 1));\n 244 | }","lines":7,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/arch/riscv/pmp.c#L238-L244"}]},
{"id":"LOGIC_101","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"easy","description":"In `print_pmp_regions()` the PMP address-matching mode is decoded with overlapping single-bit tests instead of masking the 2-bit `A` field. The constants overlap: `PMP_TOR` is `0x08`, `PMP_NA4` is `0x10`, and `PMP_NAPOT` is `0x18` (which contains BOTH `PMP_TOR` and `PMP_NA4` bits). The code does `if (pmpcfg & PMP_NA4)` then `else if (pmpcfg & PMP_NAPOT)` then `else if (pmpcfg & PMP_TOR)`. Consequently a real `PMP_NAPOT` entry (`0x18`) matches `pmpcfg & PMP_NA4` first and is printed as an `NA4` region with `size = 4`, and a `PMP_TOR` entry (`0x08`) matches `pmpcfg & PMP_NAPOT` (`0x08 & 0x18 != 0`) and is printed as `NAPOT`. Every non-`NA4` PMP region is mislabelled with a wrong size in the debug dump. Impact is limited to diagnostic output, but the dump is precisely the tool used to verify PMP protection, so it is actively misleading.","fix":"Mask the `A` field and compare against whole values, e.g. `uintptr_t a = pmpcfg & PMP_A;` then `if (a == 0) continue; else if (a == PMP_NA4) {...} else if (a == PMP_NAPOT) {...} else if (a == PMP_TOR) {...}`. This tests the 2-bit field exactly instead of relying on non-exclusive bit masks.","locations":[{"ref":"src/arch/riscv/pmp.c:327-349","code":" 327 | \t\tif ((pmpcfg & PMP_A) == 0) {\n 328 | \t\t\tcontinue; // PMP entry is disabled\n 329 | \t\t} else if (pmpcfg & PMP_NA4) {\n 330 | \t\t\tbase = pmpaddr << PMP_SHIFT;\n 331 | \t\t\tsize = 4;\n 332 | \t\t\tmode = \"NA4\";\n 333 | \t\t} else if (pmpcfg & PMP_NAPOT) {\n 334 | \t\t\tunsigned int count_trailing_ones = 0;\n 335 | \t\t\tbase = pmpaddr;\n 336 | \t\t\twhile (base) {\n 337 | \t\t\t\tif ((base & 1) == 0)\n 338 | \t\t\t\t\tbreak; // we got a zero\n 339 | \t\t\t\tcount_trailing_ones++;\n 340 | \t\t\t\tbase >>= 1;\n 341 | \t\t\t}\n 342 | \t\t\tsize = 8 << count_trailing_ones;\n 343 | \t\t\tbase = (pmpaddr & ~((2 << count_trailing_ones) - 1)) >> PMP_SHIFT;\n 344 | \t\t\tmode = \"NAPOT\";\n 345 | \t\t} else if (pmpcfg & PMP_TOR) {\n 346 | \t\t\tbase = pmpaddr;\n 347 | \t\t\tsize = pmpaddr - prev_pmpaddr;\n 348 | \t\t\tmode = \"TOR\";\n 349 | \t\t}","lines":23,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/arch/riscv/pmp.c#L327-L349"}]},
{"id":"LOGIC_102","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"medium","description":"`arch_prepare_thread()` sets up the new thread stack so the argument is delivered to `thread_entry` by pushing it onto the stack (`push_stack(stack, (uintptr_t)arg)`) and then reserving `struct saved_regs` with its comment `Ignore initial values`. This is only correct for 32-bit, where `asmlinkage` (`__attribute__((regparm(0)))`) forces stack argument passing. On `ENV_X86_64` the `regparm` attribute has no effect and the SysV AMD64 ABI passes the first argument in `%rdi`; `thread_switch_64.S` pops `%rdi` from the uninitialized `saved_regs` area, so `thread_entry` receives a garbage argument. Today this is masked (`call_wrapper` ignores its argument and the only consumer `call_wrapper_block_state` is reached solely via `thread_run_until()`, which currently has no callers), but any future 64-bit user of `thread_run_until()` would dereference a garbage `bbs` pointer. `COOP_MULTITASKING` is selectable on all `ARCH_X86` including x86_64.","fix":"On `ENV_X86_64`, initialize the `rdi` slot of the reserved `struct saved_regs` with `arg` (and any other argument registers the ABI needs) instead of relying on the pushed stack value, so the argument reaches `thread_entry` in `%rdi`.","locations":[{"ref":"src/arch/x86/thread.c:47-61","code":" 47 | void arch_prepare_thread(struct thread *t,\n 48 | \t\t\tasmlinkage void (*thread_entry)(void *), void *arg)\n 49 | {\n 50 | \tuintptr_t stack = t->stack_current;\n 51 | \n 52 | \t/* Imitate thread_entry(t) with return address of 0. thread_entry()\n 53 | \t * is assumed to never return. */\n 54 | \tstack = push_stack(stack, (uintptr_t)arg);\n 55 | \tstack = push_stack(stack, (uintptr_t)0);\n 56 | \tstack = push_stack(stack, (uintptr_t)thread_entry);\n 57 | \t/* Make room for the registers. Ignore initial values. */\n 58 | \tstack -= sizeof(struct saved_regs);\n 59 | \n 60 | \tt->stack_current = stack;\n 61 | }","lines":15,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/arch/x86/thread.c#L47-L61"}]},
{"id":"LOGIC_103","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"very_easy","description":"In `get_save_state_reg()` and `set_save_state_reg()` the bounds check on the CPU `node` index is off-by-one: valid node indices are `0 .. CONFIG_MAX_CPUS-1`, but the guard `if (node > CONFIG_MAX_CPUS)` accepts `node == CONFIG_MAX_CPUS`, one past the end. The downstream `smm_get_save_state()` re-validates with `cpu >= smm_runtime.num_cpus`, so the invalid index is caught there and impact is limited, but the guard itself is wrong and would allow an out-of-range index if a save-state op indexed an array of size `CONFIG_MAX_CPUS` directly.","fix":"Use a `>=` comparison. Before:\n```c\nif (node > CONFIG_MAX_CPUS)\n\treturn -1;\n```\nAfter:\n```c\nif (node >= CONFIG_MAX_CPUS)\n\treturn -1;\n```\nApply to both `get_save_state_reg()` and `set_save_state_reg()`.","locations":[{"ref":"src/cpu/x86/smm/save_state.c:64","code":" 64 | \tif (node > CONFIG_MAX_CPUS)","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/cpu/x86/smm/save_state.c#L64"},{"ref":"src/cpu/x86/smm/save_state.c:75","code":" 75 | \tif (node > CONFIG_MAX_CPUS)","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/cpu/x86/smm/save_state.c#L75"}]},
{"id":"LOGIC_104","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"very_easy","description":"In `swizzle_irq_pins()` / `get_pci_irq_pins()`, after calling `target_pin = swizzle_irq_pins(dev, parent_bdg)` the code checks `if (parent_bdg == NULL)`. `parent_bdg` is the `struct device **` parameter supplied by the caller and is essentially never NULL, so this check never fires. The comment says \"Make sure the swizzle returned valid structures\", meaning it intends to detect that `swizzle_irq_pins()` failed to set the output bridge pointer (`*parent_bridge` is only assigned when the root bus is found). The check should dereference the pointer to test the value that was written back.","fix":"Test the pointed-to value, not the pointer itself:\n\nBefore:\n```c\ntarget_pin = swizzle_irq_pins(dev, parent_bdg);\n\n/* Make sure the swizzle returned valid structures */\nif (parent_bdg == NULL) {\n```\nAfter:\n```c\ntarget_pin = swizzle_irq_pins(dev, parent_bdg);\n\n/* Make sure the swizzle returned valid structures */\nif (*parent_bdg == NULL) {\n```","locations":[{"ref":"src/device/pci_device.c:1751-1757","code":" 1751 | \t\ttarget_pin = swizzle_irq_pins(dev, parent_bdg);\n 1752 | \n 1753 | \t\t/* Make sure the swizzle returned valid structures */\n 1754 | \t\tif (parent_bdg == NULL) {\n 1755 | \t\t\tprintk(BIOS_WARNING, \"Could not find parent bridge for this device!\\n\");\n 1756 | \t\t\treturn -2;\n 1757 | \t\t}","lines":7,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/device/pci_device.c#L1751-L1757"}]},
{"id":"LOGIC_105","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"easy","description":"`ast_init_analog()` performs a read-modify of SCU2C (`data = ast_read32(ast, 0x1202c); data &= 0xfffcffff;`) but then writes the result to offset `0` instead of back to `0x1202c` (`ast_write32(ast, 0, data)`), contradicting the comment \"clear bits [17:16] of SCU2c\". The sibling `ast_init_dvo()` correctly writes the modified value back to `0x1202c`. The write to offset 0 targets an unrelated P2A register while the intended SCU2C update never happens, so DAC-source-to-VGA selection on the analog-only 3rd-Tx path is not applied. Note this matches the upstream Linux ast driver, so it is likely an inherited defect.","fix":"Write the modified value back to the register it was read from: `ast_write32(ast, 0x1202c, data);` (matching the pattern in `ast_init_dvo()`).","locations":[{"ref":"src/drivers/aspeed/common/ast_dp501.c:352-354","code":" 352 | \tdata = ast_read32(ast, 0x1202c);\n 353 | \tdata &= 0xfffcffff;\n 354 | \tast_write32(ast, 0, data);","lines":3,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/aspeed/common/ast_dp501.c#L352-L354"}]},
{"id":"LOGIC_106","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"very_easy","description":"In `cs42l42_fill_ssdt()` the range clamp for `config->btn_det_init_dbnce` and `config->btn_det_event_dbnce` runs AFTER the value has already been emitted into the `_DSD` via `acpi_dp_add_integer()`. The corrected value assigned in the `if` block (`= 100` / `= 10`) is therefore never written to the ACPI table, so an out-of-range devicetree value is passed through to the OS unchanged while the error message misleadingly claims a default was used. Contrast with `cs35l53_fill_ssdt()`, which validates `boost_peak_milliamp` before calling `acpi_dp_add_integer()`.","fix":"Move each validation/clamp ahead of the corresponding `acpi_dp_add_integer()` call, e.g.:\n```c\nif (config->btn_det_init_dbnce > 200) {\n\tprintk(BIOS_ERR, \"%s: Incorrect btn_det_init_dbnce(%d). Using default of 100ms\\n\",\n\t\t__func__, config->btn_det_init_dbnce);\n\tconfig->btn_det_init_dbnce = 100;\n}\nacpi_dp_add_integer(dsd, \"cirrus,btn-det-init-dbnce\", config->btn_det_init_dbnce);\n```\nand likewise for `btn_det_event_dbnce`.","locations":[{"ref":"src/drivers/i2c/cs42l42/cs42l42.c:70-84","code":" 70 | \tacpi_dp_add_integer(dsd, \"cirrus,btn-det-init-dbnce\", config->btn_det_init_dbnce);\n 71 | \n 72 | \tif (config->btn_det_init_dbnce > 200) {\n 73 | \t\tprintk(BIOS_ERR, \"%s: Incorrect btn_det_init_dbnce(%d). Using default of 100ms\\n\",\n 74 | \t\t\t\t__func__, config->btn_det_init_dbnce);\n 75 | \t\tconfig->btn_det_init_dbnce = 100;\n 76 | \t}\n 77 | \n 78 | \tacpi_dp_add_integer(dsd, \"cirrus,btn-det-event-dbnce\", config->btn_det_event_dbnce);\n 79 | \n 80 | \tif (config->btn_det_event_dbnce > 100) {\n 81 | \t\tprintk(BIOS_ERR, \"%s: Incorrect btn_det_event_dbnce(%d). Using default of 10ms\\n\",\n 82 | \t\t\t\t__func__, config->btn_det_event_dbnce);\n 83 | \t\tconfig->btn_det_event_dbnce = 10;\n 84 | \t}","lines":15,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/i2c/cs42l42/cs42l42.c#L70-L84"}]},
{"id":"LOGIC_107","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"medium","description":"In `cr50_i2c_tis_send()` the inner transmit loop `while (len > 0)` has no overall timeout; it relies solely on `cr50_i2c_wait_burststs()` succeeding each iteration. When the reported `burstcnt` is 1, `limit = MIN(burstcnt - 1, len)` becomes 0, `cr50_i2c_write()` sends only the address byte, and `sent`/`len` do not advance. If the TPM keeps reporting `burstcnt == 1` (with `TPM_STS_DATA_EXPECT` set), `wait_burststs` returns success repeatedly (`*burst > 0` passes) and the loop spins forever, hanging boot. The `sw` stopwatch initialized at the top only guards the preceding command-ready wait, not this loop.","fix":"Guard the transmit loop against zero forward progress, e.g. bail out (goto `out_err`) if `limit == 0`, or re-check `stopwatch_expired(&sw)` inside the `while (len > 0)` loop so a stuck `burstcnt == 1` cannot spin indefinitely.","locations":[{"ref":"src/drivers/i2c/tpm/cr50.c:369-391","code":" 369 | \twhile (len > 0) {\n 370 | \t\tuint8_t mask = TPM_STS_VALID;\n 371 | \n 372 | \t\t/* Wait for data if this is not the first chunk */\n 373 | \t\tif (sent > 0)\n 374 | \t\t\tmask |= TPM_STS_DATA_EXPECT;\n 375 | \n 376 | \t\t/* Read burst count and check status */\n 377 | \t\tif (cr50_i2c_wait_burststs(mask, &burstcnt, &status))\n 378 | \t\t\tgoto out_err;\n 379 | \n 380 | \t\t/* Use burstcnt - 1 to account for the address byte\n 381 | \t\t * that is inserted by cr50_i2c_write() */\n 382 | \t\tlimit = MIN(burstcnt - 1, len);\n 383 | \t\trc = cr50_i2c_write(TPM_DATA_FIFO(tpm_dev.locality), &buf[sent], limit);\n 384 | \t\tif (rc) {\n 385 | \t\t\tprintk(BIOS_ERR, \"%s: Write failed with error %#x\\n\", __func__, rc);\n 386 | \t\t\tgoto out_err;\n 387 | \t\t}\n 388 | \n 389 | \t\tsent += limit;\n 390 | \t\tlen -= limit;\n 391 | \t}","lines":23,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/i2c/tpm/cr50.c#L369-L391"}]},
{"id":"LOGIC_108","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"easy","description":"`fsp_display_timestamp()` walks FSP-provided performance records advancing by `rec->header.length` (a `uint8_t` taken directly from the HOB). If a record has `header.length == 0`, `i += rec->header.length` never increases and `rec` never advances, producing an infinite loop / hang. Additionally, `print_string_record()` computes `str_len = rec->header.length - offsetof(struct generic_event_record, string)` (offset is 34); when `header.length < 34` the subtraction underflows to a huge `size_t`, and `%.*s` with `(int)str_len` reads far past the record (out-of-bounds read). Both are reachable only from the debug timestamp-dump path but are driven by unvalidated external HOB data.","fix":"Before dereferencing/advancing, validate the record: bail out (or break) if `rec->header.length < offsetof(struct generic_event_record, string)` (which also guarantees length is non-zero) and if `i + rec->header.length` would exceed `hdr->table_size`. Only then compute `str_len` and advance `rec`.","locations":[{"ref":"src/drivers/intel/fsp2_0/fsp_timestamp.c:100-106","code":" 100 | \tfor (size_t i = 0; i < hdr->table_size;) {\n 101 | \t\tprint_fsp_perf_timestamp(rec);\n 102 | \n 103 | \t\ti += rec->header.length;\n 104 | \t\trec = (const struct generic_event_record *)((uint8_t *)rec +\n 105 | \t\t\t\t rec->header.length);\n 106 | \t}","lines":7,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/intel/fsp2_0/fsp_timestamp.c#L100-L106"},{"ref":"src/drivers/intel/fsp2_0/fsp_timestamp.c:53-55","code":" 53 | \tsize_t str_len = rec->header.length - offsetof(struct generic_event_record, string);\n 54 | \tprintk(BIOS_INFO, \"%5x\\t%16llu\\t\\t%.*s/\",\n 55 | \t rec->progress_id, TIMESTAMP_TO_MICRO(rec->timestamp), (int)str_len, rec->string);","lines":3,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/intel/fsp2_0/fsp_timestamp.c#L53-L55"}]},
{"id":"LOGIC_109","type":"logic_error","type_label":"Logic Error","severity":"low","difficulty":"easy","description":"`multi_phase_get_number` is declared uninitialized at line 99. Its `number_of_phases` field is filled by the FSP `GET_NUMBER_OF_PHASES` call at line 200, but the return value of that call is passed to `fsps_return_value_handler(FSP_MULTI_PHASE_SI_INIT_GET_NUMBER_OF_PHASES_API, status)` which, for that phase, only logs at `BIOS_SPEW` and never calls `die()` (see lines 67-69). If the FSP call fails and leaves the struct unwritten, `number_of_phases` is an uninitialized stack value, and the loop `for (uint32_t i = 1; i <= multi_phase_get_number.number_of_phases; i++)` at line 204 can iterate an arbitrarily large number of times, invoking `multi_phase_si_init()` with garbage `phase_index` values.","fix":"Zero-initialize the struct: change `struct fsp_multi_phase_get_number_of_phases_params multi_phase_get_number;` to `struct fsp_multi_phase_get_number_of_phases_params multi_phase_get_number = {0};`, and/or skip the loop when the `GET_NUMBER_OF_PHASES` `status` is not `FSP_SUCCESS`.","locations":[{"ref":"src/drivers/intel/fsp2_0/silicon_init.c:99","code":" 99 | \tstruct fsp_multi_phase_get_number_of_phases_params multi_phase_get_number;","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/intel/fsp2_0/silicon_init.c#L99"},{"ref":"src/drivers/intel/fsp2_0/silicon_init.c:196-219","code":" 196 | \t/* Get NumberOfPhases Value */\n 197 | \tmulti_phase_params.multi_phase_action = GET_NUMBER_OF_PHASES;\n 198 | \tmulti_phase_params.phase_index = 0;\n 199 | \tmulti_phase_params.multi_phase_param_ptr = &multi_phase_get_number;\n 200 | \tstatus = multi_phase_si_init(&multi_phase_params);\n 201 | \tfsps_return_value_handler(FSP_MULTI_PHASE_SI_INIT_GET_NUMBER_OF_PHASES_API, status);\n 202 | \n 203 | \t/* Execute Multi Phase Execution */\n 204 | \tfor (uint32_t i = 1; i <= multi_phase_get_number.number_of_phases; i++) {\n 205 | \t\tprintk(BIOS_SPEW, \"Executing Phase %u of FspMultiPhaseSiInit\\n\", i);\n 206 | \t\t/*\n 207 | \t\t * Give SoC/mainboard a chance to perform any operation before\n 208 | \t\t * Multi Phase Execution\n 209 | \t\t */\n 210 | \t\tplatform_fsp_silicon_multi_phase_init_cb(i);\n 211 | \n 212 | \t\tmulti_phase_params.multi_phase_action = EXECUTE_PHASE;\n 213 | \t\tmulti_phase_params.phase_index = i;\n 214 | \t\tmulti_phase_params.multi_phase_param_ptr = NULL;\n 215 | \t\tstatus = multi_phase_si_init(&multi_phase_params);\n 216 | \t\tif (CONFIG(FSP_MULTIPHASE_SI_INIT_RETURN_BROKEN))\n 217 | \t\t\tstatus = fsp_get_pch_reset_status();\n 218 | \t\tfsps_return_value_handler(FSP_MULTI_PHASE_SI_INIT_EXECUTE_PHASE_API, status);\n 219 | \t}","lines":24,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/intel/fsp2_0/silicon_init.c#L196-L219"}]},
{"id":"LOGIC_110","type":"logic_error","type_label":"Logic Error","severity":"negligible","difficulty":"easy","description":"`fit_place_mem()` accepts a placement only when `start + region->size < range_entry_end(r)`. Since `range_entry_end(r)` is the exclusive end (base + size) of the RAM range, a region that fits exactly (its last byte at `end - 1`, i.e. `start + region->size == range_entry_end(r)`) should be accepted, but the strict `<` rejects it. This is an off-by-one that can needlessly reject an exact-fit placement of the kernel/FDT/initrd. Real impact is minimal because `get_kernel_size()` already pads the kernel region by `MAX_KERNEL_SIZE` (64 MiB), making an exact fit unlikely.","fix":"Use `if (start + region->size <= range_entry_end(r))` so a region that ends exactly at the range end is considered a valid placement.","locations":[{"ref":"src/arch/riscv/fit_payload.c:43-60","code":" 43 | static bool fit_place_mem(const struct range_entry *r, void *arg)\n 44 | {\n 45 | \tstruct region *region = arg;\n 46 | \tresource_t start;\n 47 | \n 48 | \tif (range_entry_tag(r) != BM_MEM_RAM)\n 49 | \t\treturn true;\n 50 | \n 51 | \t/* Section must be aligned at page boundary */\n 52 | \tstart = ALIGN_UP(MAX(region->offset, range_entry_base(r)), SECTION_ALIGN);\n 53 | \n 54 | \tif (start + region->size < range_entry_end(r)) {\n 55 | \t\tregion->offset = (size_t)start;\n 56 | \t\treturn false;\n 57 | \t}\n 58 | \n 59 | \treturn true;\n 60 | }","lines":18,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/arch/riscv/fit_payload.c#L43-L60"}]},
{"id":"LOGIC_111","type":"logic_error","type_label":"Logic Error","severity":"negligible","difficulty":"medium","description":"`display_capacity` computes `capacity / (decimal_divisor / 1000)` and `(capacity / (hex_divisor / 1024))`. When the selected units are bytes (`\"B\"`, `bytes == 1`), `decimal_divisor / 1000` and `hex_divisor / 1024` both evaluate to 0, causing a division by zero. This only occurs if a displayed partition capacity is in the range 1..999 bytes, which is effectively impossible for real MMC/SD capacities, and the code is compiled only with `SDHC_DEBUG`, so real-world impact is essentially nil.","fix":"Guard the sub-1000/1024 case (e.g. skip the fractional term when `decimal_divisor < 1000` / `hex_divisor < 1024`), or special-case the `B`/`B` units entry so the divisor is never reduced to 0.","locations":[{"ref":"src/commonlib/storage/storage.c:77-82","code":" 77 | \tsdhc_debug(\"%3lld.%03lld %sytes (%3lld.%03lld %sytes)%s%s\\n\",\n 78 | \t\tcapacity / decimal_divisor,\n 79 | \t\t(capacity / (decimal_divisor / 1000)) % 1000,\n 80 | \t\tdecimal_units,\n 81 | \t\tcapacity / hex_divisor,\n 82 | \t\t((capacity / (hex_divisor / 1024)) * 1000 / 1024) % 1000,","lines":6,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/commonlib/storage/storage.c#L77-L82"}]},
{"id":"CPY_1","type":"copy_paste","type_label":"Copy Paste","severity":"medium","difficulty":"very_easy","description":"In apic.c, apic_set_task_priority() reads the TPR register, correctly clears the priority field bits and ORs in the new priority into the local variable tpr, but then writes the raw priority byte directly instead of the modified tpr value. The read-modify-write to preserve upper bits is therefore useless, and the written value always has the upper bits cleared.","fix":"Change apic_write32(APIC_TASK_PRIORITY, priority) to apic_write32(APIC_TASK_PRIORITY, tpr) to use the value computed on the lines above.","locations":[{"ref":"payloads/libpayload/arch/x86/apic.c:226-235","code":" 226 | static void apic_set_task_priority(uint8_t priority)\n 227 | {\n 228 | \tdie_if(!apic_bar, \"APIC is not initialized\");\n 229 | \n 230 | \tuint32_t tpr = apic_read32(APIC_TASK_PRIORITY);\n 231 | \ttpr &= ~APIC_TASK_PRIORITY_MASK;\n 232 | \ttpr |= priority;\n 233 | \n 234 | \tapic_write32(APIC_TASK_PRIORITY, priority);\n 235 | }","lines":10,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/arch/x86/apic.c#L226-L235"}]},
{"id":"CPY_2","type":"copy_paste","type_label":"Copy Paste","severity":"low","difficulty":"very_easy","description":"Multiple PDC_LOG messages in addstr.c report wrong function names due to copy-paste. mvaddwstr() at line 200 logs 'mvaddstr() - called' instead of 'mvaddwstr() - called'. mvaddnwstr() at line 210 logs 'mvaddnstr() - called' instead of 'mvaddnwstr() - called'. mvwaddwstr() at line 220 logs 'mvwaddstr() - called' and mvwaddnwstr() at line 230 logs 'mvwaddnstr() - called' -- these should log their own wide-character function names.","fix":"Update each PDC_LOG string to use the actual function name being called.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/addstr.c:200","code":" 200 | PDC_LOG((\"mvaddstr() - called\\n\"));","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/addstr.c#L200"},{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/addstr.c:210","code":" 210 | PDC_LOG((\"mvaddnstr() - called\\n\"));","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/addstr.c#L210"},{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/addstr.c:220","code":" 220 | PDC_LOG((\"mvwaddstr() - called\\n\"));","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/addstr.c#L220"},{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/addstr.c:230","code":" 230 | PDC_LOG((\"mvwaddnstr() - called\\n\"));","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/addstr.c#L230"}]},
{"id":"CPY_3","type":"copy_paste","type_label":"Copy Paste","severity":"low","difficulty":"very_easy","description":"`sdhci_display_clock` reconstructs the clock divisor incorrectly. The high divisor bits are programmed by `sdhci_set_clock` at bit position `SDHCI_DIVIDER_HI_SHIFT` (6), but the display code shifts `clk_ctrl` by `SDHCI_DIVIDER_SHIFT` (8) again when extracting the high bits, reading the wrong register bits. This yields a wrong reported bus clock whenever the divisor exceeds 255 (SPEC_300 large divisors). Debug-only (`SDHC_DEBUG`), so impact is limited to misleading log output.","fix":"Use `SDHCI_DIVIDER_HI_SHIFT` for the high bits.\nBefore:\n```c\ndivisor = (clk_ctrl >> SDHCI_DIVIDER_SHIFT)\n\t& SDHCI_DIV_MASK;\ndivisor |= ((clk_ctrl >> SDHCI_DIVIDER_SHIFT)\n\t<< SDHCI_DIV_MASK_LEN) & SDHCI_DIV_HI_MASK;\n```\nAfter:\n```c\ndivisor = (clk_ctrl >> SDHCI_DIVIDER_SHIFT)\n\t& SDHCI_DIV_MASK;\ndivisor |= ((clk_ctrl >> SDHCI_DIVIDER_HI_SHIFT)\n\t<< SDHCI_DIV_MASK_LEN) & SDHCI_DIV_HI_MASK;\n```","locations":[{"ref":"src/commonlib/storage/sdhci_display.c:51-55","code":" 51 | \t\t\tdivisor = (clk_ctrl >> SDHCI_DIVIDER_SHIFT)\n 52 | \t\t\t\t& SDHCI_DIV_MASK;\n 53 | \t\t\tdivisor |= ((clk_ctrl >> SDHCI_DIVIDER_SHIFT)\n 54 | \t\t\t\t<< SDHCI_DIV_MASK_LEN) & SDHCI_DIV_HI_MASK;\n 55 | \t\t\tdivisor <<= 1;","lines":5,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/commonlib/storage/sdhci_display.c#L51-L55"}]},
{"id":"CPY_4","type":"copy_paste","type_label":"Copy Paste","severity":"low","difficulty":"very_easy","description":"The `dev_map[]` entry for the Intel iTPM has its `vid`/`did` fields swapped. `crb_tpm_get_info()` fills `info->vendor_id` from bits [63:48] and `info->device_id` from bits [47:32] of the interface-ID register, and Intel's PCI vendor ID is `0x8086` (device `0xa13a`). The table stores `{0xa13a, 0x8086, \"Intel iTPM\"}`, so the comparison `dev_map[i].vid == info->vendor_id && dev_map[i].did == info->device_id` never matches an Intel TPM and it is always reported as `\"Unknown\"`. The `CR50` entry `{0x1ae0, 0x0028, ...}` uses the correct `{vid, did}` order, confirming the intended layout.","fix":"Swap the two fields to `{vid, did}` order.\n```c\n// before\n{0xa13a, 0x8086, \"Intel iTPM\"}\n// after\n{0x8086, 0xa13a, \"Intel iTPM\"}\n```","locations":[{"ref":"src/drivers/crb/tis.c:22-24","code":" 22 | \t{0x1ae0, 0x0028, \"CR50\"},\n 23 | \t{0xa13a, 0x8086, \"Intel iTPM\"}\n 24 | };","lines":3,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/crb/tis.c#L22-L24"}]},
{"id":"ERR_1","type":"incorrect_error_handling","type_label":"Incorrect Error Handling","severity":"medium","difficulty":"medium","description":"In demos/worm.c, the outer `ref` pointer array is allocated with malloc() at line 249 but the\nreturn value is never checked for NULL. If the allocation fails, the immediately following\n`ref[y] = malloc(...)` loop dereferences a NULL pointer, causing a crash. The inner\nrow allocations (ref[y]) are also unchecked. Additionally during KEY_RESIZE handling (line 341),\n`realloc(ref, ...)` is assigned directly back to `ref` without a NULL check; a failed realloc\nreturns NULL, destroying the only pointer to all existing row buffers and leaking them.","fix":"Check the return values of malloc() and realloc() and handle failures gracefully. For realloc(), save the return value to a temporary pointer before assigning to ref to avoid losing the original pointer on failure.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/demos/worm.c:249-257","code":" 249 | ref = malloc(sizeof(short *) * LINES);\n 250 | \n 251 | for (y = 0; y < LINES; y++)\n 252 | {\n 253 | ref[y] = malloc(sizeof(short) * COLS);\n 254 | \n 255 | for (x = 0; x < COLS; x++)\n 256 | ref[y][x] = 0;\n 257 | }","lines":9,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/demos/worm.c#L249-L257"},{"ref":"payloads/libpayload/curses/PDCurses/demos/worm.c:325-352","code":" 325 | for (y = 0; y <= bottom; y++)\n 326 | {\n 327 | ref[y] = realloc(ref[y], sizeof(short) * COLS);\n 328 | \n 329 | for (x = last + 1; x < COLS; x++)\n 330 | ref[y][x] = 0;\n 331 | }\n 332 | \n 333 | last = COLS - 1;\n 334 | }\n 335 | \n 336 | if (bottom != LINES - 1)\n 337 | {\n 338 | for (y = LINES; y <= bottom; y++)\n 339 | free(ref[y]);\n 340 | \n 341 | ref = realloc(ref, sizeof(short *) * LINES);\n 342 | \n 343 | for (y = bottom + 1; y < LINES; y++)\n 344 | {\n 345 | ref[y] = malloc(sizeof(short) * COLS);\n 346 | \n 347 | for (x = 0; x < COLS; x++)\n 348 | ref[y][x] = 0;\n 349 | }\n 350 | \n 351 | bottom = LINES - 1;\n 352 | }","lines":28,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/demos/worm.c#L325-L352"}]},
{"id":"ERR_2","type":"incorrect_error_handling","type_label":"Incorrect Error Handling","severity":"medium","difficulty":"medium","description":"In doc/manext.c main(), `argv[1]` is dereferenced via strcmp() at line 52 before checking\nwhether argc > 1. When the program is invoked with no arguments (argc == 1), argv[1] is NULL,\nand passing NULL to strcmp() is undefined behavior (typically a segfault). The program has no\nusage message path that can be triggered safely without at least one argument.","fix":"Add `if (argc < 2) { display_info(); exit(1); }` before the strcmp call, or change the check to `if (argc > 1 && strcmp(argv[1], \"-h\") == 0)`.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/doc/manext.c:52-55","code":" 52 | if (strcmp(argv[1], \"-h\") == 0)\n 53 | {\n 54 | display_info();\n 55 | exit(1);","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/doc/manext.c#L52-L55"}]},
{"id":"ERR_3","type":"incorrect_error_handling","type_label":"Incorrect Error Handling","severity":"medium","difficulty":"very_easy","description":"In win32/pdcgetsc.c all three functions (PDC_get_cursor_mode, PDC_get_rows,\nPDC_get_buffer_rows, PDC_get_columns) call the Win32 API (GetConsoleCursorInfo,\nGetConsoleScreenBufferInfo) but completely ignore the BOOL return value. A failure leaves the\nlocal struct uninitialised, so the function returns garbage. Callers then use these values to\nsize the terminal, which can corrupt internal state or trigger out-of-bounds accesses.","fix":"Check the return value of each Win32 call; on failure return -1 (or ERR) and\nlet callers propagate the error rather than proceeding with an uninitialised struct.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/win32/pdcgetsc.c:9-57","code":" 9 | int PDC_get_cursor_mode(void)\n 10 | {\n 11 | CONSOLE_CURSOR_INFO ci;\n 12 | \n 13 | PDC_LOG((\"PDC_get_cursor_mode() - called\\n\"));\n 14 | \n 15 | GetConsoleCursorInfo(pdc_con_out, &ci);\n 16 | \n 17 | return ci.dwSize;\n 18 | }\n 19 | \n 20 | /* return number of screen rows */\n 21 | \n 22 | int PDC_get_rows(void)\n 23 | {\n 24 | CONSOLE_SCREEN_BUFFER_INFO scr;\n 25 | \n 26 | PDC_LOG((\"PDC_get_rows() - called\\n\"));\n 27 | \n 28 | GetConsoleScreenBufferInfo(pdc_con_out, &scr);\n 29 | \n 30 | return scr.srWindow.Bottom - scr.srWindow.Top + 1;\n 31 | }\n 32 | \n 33 | /* return number of buffer rows */\n 34 | \n 35 | int PDC_get_buffer_rows(void)\n 36 | {\n 37 | CONSOLE_SCREEN_BUFFER_INFO scr;\n 38 | \n 39 | PDC_LOG((\"PDC_get_buffer_rows() - called\\n\"));\n 40 | \n 41 | GetConsoleScreenBufferInfo(pdc_con_out, &scr);\n 42 | \n 43 | return scr.dwSize.Y;\n 44 | }\n 45 | \n 46 | /* return width of screen/viewport */\n 47 | \n 48 | int PDC_get_columns(void)\n 49 | {\n 50 | CONSOLE_SCREEN_BUFFER_INFO scr;\n 51 | \n 52 | PDC_LOG((\"PDC_get_columns() - called\\n\"));\n 53 | \n 54 | GetConsoleScreenBufferInfo(pdc_con_out, &scr);\n 55 | \n 56 | return scr.srWindow.Right - scr.srWindow.Left + 1;\n 57 | }","lines":49,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/win32/pdcgetsc.c#L9-L57"}]},
{"id":"ERR_4","type":"incorrect_error_handling","type_label":"Incorrect Error Handling","severity":"medium","difficulty":"medium","description":"In set_option_from_string() case 'h', malloc(sizeof(u64)) result is not checked for NULL before immediately dereferencing it with *(u64*)raw = strtoull(...). Only case 's' has a NULL check. If malloc returns NULL (impossible in libpayload's default allocator but still undefined behavior), this faults.","fix":"Add a null check after malloc for case 'h': if (!raw) return 1;","locations":[{"ref":"payloads/libpayload/drivers/options.c:358-360","code":" 358 | \t\t\traw = malloc(sizeof(u64));\n 359 | \t\t\t*(u64*)raw = strtoull(value, NULL, 0);\n 360 | \t\t\tbreak;","lines":3,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/options.c#L358-L360"}]},
{"id":"ERR_5","type":"incorrect_error_handling","type_label":"Incorrect Error Handling","severity":"medium","difficulty":"very_easy","description":"`crb_tpm_process_command()` is declared to return `size_t` and returns `-1` (i.e. `SIZE_MAX`) on every error path. `crb_tpm_sendrecv()` stores the result in `int len` and only tests `if (len == 0)`. When the command fails, `crb_tpm_process_command()` returns `(size_t)-1`, which truncates to `int -1`; `len == 0` is false, so the function skips the error return, sets `*rbuf_len = len` (which converts `-1` back to `SIZE_MAX` in the caller's `size_t`), and returns `TPM_SUCCESS`. A failed TPM command is thus reported to the TSS layer as success with an enormous response length, so the caller parses an unfilled/garbage response buffer.","fix":"Use a matching unsigned type and treat the sentinel as failure.\n```c\n// before\nint len = crb_tpm_process_command(sendbuf, sbuf_size, recvbuf, *rbuf_len);\nif (len == 0)\n return TPM_CB_FAIL;\n// after\nsize_t len = crb_tpm_process_command(sendbuf, sbuf_size, recvbuf, *rbuf_len);\nif (len == 0 || len == (size_t)-1)\n return TPM_CB_FAIL;\n```","locations":[{"ref":"src/drivers/crb/tis.c:36-47","code":" 36 | static tpm_result_t crb_tpm_sendrecv(const uint8_t *sendbuf, size_t sbuf_size, uint8_t *recvbuf,\n 37 | \t\t\t\t size_t *rbuf_len)\n 38 | {\n 39 | \tint len = crb_tpm_process_command(sendbuf, sbuf_size, recvbuf, *rbuf_len);\n 40 | \n 41 | \tif (len == 0)\n 42 | \t\treturn TPM_CB_FAIL;\n 43 | \n 44 | \t*rbuf_len = len;\n 45 | \n 46 | \treturn TPM_SUCCESS;\n 47 | }","lines":12,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/crb/tis.c#L36-L47"}]},
{"id":"ERR_6","type":"incorrect_error_handling","type_label":"Incorrect Error Handling","severity":"low","difficulty":"medium","description":"In win32/pdcclip.c PDC_clearclipboard() calls EmptyClipboard() at line 142\nwithout first calling OpenClipboard(). EmptyClipboard() requires the clipboard to be open and\nowned by the calling window; calling it without OpenClipboard() will fail silently on Windows,\nleaving the clipboard contents intact while returning PDC_CLIP_SUCCESS.","fix":"Wrap EmptyClipboard() with OpenClipboard(NULL)/CloseClipboard(), checking the\nreturn value of OpenClipboard and returning PDC_CLIP_ACCESS_ERROR on failure.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/win32/pdcclip.c:138-145","code":" 138 | int PDC_clearclipboard(void)\n 139 | {\n 140 | PDC_LOG((\"PDC_clearclipboard() - called\\n\"));\n 141 | \n 142 | EmptyClipboard();\n 143 | \n 144 | return PDC_CLIP_SUCCESS;\n 145 | }","lines":8,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/win32/pdcclip.c#L138-L145"}]},
{"id":"ERR_7","type":"incorrect_error_handling","type_label":"Incorrect Error Handling","severity":"low","difficulty":"medium","description":"set_form_fields (frm_def.c) calls Disconnect_Fields then on failure calls Connect_Fields(form, old) to restore the previous field array. However, Connect_Fields can itself fail (e.g. E_SYSTEM_ERROR from malloc), and that failure is silently ignored. The form ends up with a NULL field array (from Disconnect_Fields) rather than the restored old array, leaving it in a broken half-disconnected state while the function has already returned the original error code.","fix":"Check the return value of the fallback Connect_Fields call and handle failure, e.g. by logging or returning a combined error. At minimum, document that the form may be left with no fields after a failed set_form_fields.","locations":[{"ref":"payloads/libpayload/curses/form/frm_def.c:408-412","code":" 408 | old = form->field;\n 409 | Disconnect_Fields(form);\n 410 | \n 411 | if ((res = Associate_Fields(form, fields)) != E_OK)\n 412 | Connect_Fields(form, old);","lines":5,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/frm_def.c#L408-L412"}]},
{"id":"ERR_8","type":"incorrect_error_handling","type_label":"Incorrect Error Handling","severity":"low","difficulty":"medium","description":"rtc_read_clock() and rtc_write_clock() return void, so when the nvram_updating() timeout fires after 10001 iterations, the caller receives no indication of failure. rtc_read_clock() returns with time partially zeroed (only memset was done), giving the caller a struct tm with year=0, month=0, etc. that looks valid.","fix":"Change the return type to int and return a nonzero error code on timeout, or at minimum document that callers must check some out-of-range field to detect the timeout case.","locations":[{"ref":"payloads/libpayload/drivers/nvram.c:111-121","code":" 111 | void rtc_read_clock(struct tm *time)\n 112 | {\n 113 | \tu16 timeout = 10000;\n 114 | \tu8 statusB;\n 115 | \tu8 reg8;\n 116 | \n 117 | \tmemset(time, 0, sizeof(*time));\n 118 | \n 119 | \twhile (nvram_updating())\n 120 | \t\tif (!timeout--)\n 121 | \t\t\treturn;","lines":11,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/nvram.c#L111-L121"},{"ref":"payloads/libpayload/drivers/nvram.c:169-177","code":" 169 | void rtc_write_clock(const struct tm *time)\n 170 | {\n 171 | \tu16 timeout = 10000;\n 172 | \tu8 statusB;\n 173 | \tu8 reg8, year;\n 174 | \n 175 | \twhile (nvram_updating())\n 176 | \t\tif (!timeout--)\n 177 | \t\t\treturn;","lines":9,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/nvram.c#L169-L177"}]},
{"id":"ERR_9","type":"incorrect_error_handling","type_label":"Incorrect Error Handling","severity":"low","difficulty":"very_easy","description":"In `relocate_remaining_items()` the FSP info-header signature check prints an error but does not return, so relocation continues using a header whose `Signature` did not match `FSP_SIG`. This is inconsistent with the sibling function `fsp_get_info_hdr()`, which returns `NULL` on the identical signature mismatch (the preceding GUID and section-type checks in `relocate_remaining_items()` also both `return -1`). This looks like a dropped `return -1;` where one branch of a copy-paste pair was updated and the other was not.","fix":"Before:\n```c\n\tif (read_le32(&fih->Signature) != FSP_SIG) {\n\t\tprintk(BIOS_ERR, \"Unexpected FIH signature: %08x\\n\",\n\t\t\tread_le32(&fih->Signature));\n\t}\n```\nAfter:\n```c\n\tif (read_le32(&fih->Signature) != FSP_SIG) {\n\t\tprintk(BIOS_ERR, \"Unexpected FIH signature: %08x\\n\",\n\t\t\tread_le32(&fih->Signature));\n\t\treturn -1;\n\t}\n```","locations":[{"ref":"src/commonlib/fsp_relocate.c:516-519","code":" 516 | \tif (read_le32(&fih->Signature) != FSP_SIG) {\n 517 | \t\tprintk(BIOS_ERR, \"Unexpected FIH signature: %08x\\n\",\n 518 | \t\t\tread_le32(&fih->Signature));\n 519 | \t}","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/commonlib/fsp_relocate.c#L516-L519"}]},
{"id":"ERR_10","type":"incorrect_error_handling","type_label":"Incorrect Error Handling","severity":"low","difficulty":"very_easy","description":"`thermal_zone_acpi_name()` calls `malloc(ACPI_NAME_BUFFER_SIZE)` and immediately passes the result to `snprintf(name, ACPI_NAME_BUFFER_SIZE, ...)` without checking for `NULL`. If the allocation fails, `snprintf()` dereferences a `NULL` pointer and crashes. Other coreboot `acpi_name` implementations exhibit the same pattern, but the missing check is still a real (if unlikely) null-pointer dereference.","fix":"Check the allocation before use:\n```c\nname = malloc(ACPI_NAME_BUFFER_SIZE);\nif (!name)\n\treturn NULL;\nsnprintf(name, ACPI_NAME_BUFFER_SIZE, \"TM%02X\", dev->path.generic.id);\n```","locations":[{"ref":"src/drivers/acpi/thermal_zone/thermal_zone.c:29-32","code":" 29 | \tname = malloc(ACPI_NAME_BUFFER_SIZE);\n 30 | \tsnprintf(name, ACPI_NAME_BUFFER_SIZE, \"TM%02X\", dev->path.generic.id);\n 31 | \n 32 | \treturn name;","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/acpi/thermal_zone/thermal_zone.c#L29-L32"}]},
{"id":"ERR_11","type":"incorrect_error_handling","type_label":"Incorrect Error Handling","severity":"low","difficulty":"medium","description":"`fw_cfg_acpi_tables()` calls `malloc(f.size)` and `malloc(max * sizeof(*addrs))` without checking for `NULL`. `f.size` comes from QEMU fw_cfg (external to coreboot); on allocation failure the subsequent `fw_cfg_get(f.select, s, f.size)` and `s[i].command` dereference a NULL pointer. Similarly `fw_cfg_smbios_init()` does `malloc(entry.length - sizeof(entry))` where `entry.length` is a `uint16_t` read from fw_cfg: because `sizeof(entry)` is `size_t` (unsigned), any `entry.length < 6` makes the subtraction wrap to a huge value, causing a giant `malloc` that returns NULL, after which `fw_cfg_read(buf, entry.length - sizeof(entry))` writes via `insb` to NULL with a huge length. The malloc results there are also unchecked.","fix":"Check every `malloc()` result and bail out (`goto err` / return) on NULL. In `fw_cfg_smbios_init()` also validate `entry.length >= sizeof(entry)` before computing the payload size, e.g. `if (entry.length < sizeof(entry)) break;`, to avoid the unsigned underflow.","locations":[{"ref":"src/drivers/emulation/qemu/fw_cfg.c:252-255","code":" 252 | \tmax = f.size / sizeof(*s);\n 253 | \ts = malloc(f.size);\n 254 | \taddrs = malloc(max * sizeof(*addrs));\n 255 | \tfw_cfg_get(f.select, s, f.size);","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/emulation/qemu/fw_cfg.c#L252-L255"},{"ref":"src/drivers/emulation/qemu/fw_cfg.c:384-386","code":" 384 | \t\tfw_cfg_read(&entry, sizeof(entry));\n 385 | \t\tbuf = malloc(entry.length - sizeof(entry));\n 386 | \t\tfw_cfg_read(buf, entry.length - sizeof(entry));","lines":3,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/emulation/qemu/fw_cfg.c#L384-L386"}]},
{"id":"ERR_12","type":"incorrect_error_handling","type_label":"Incorrect Error Handling","severity":"negligible","difficulty":"very_easy","description":"`dev_finalize()` calls `final_link(dev_root.downstream)` without first checking that `dev_root.downstream` is non-NULL, whereas the analogous `dev_initialize()` and `dev_enable()` both guard with `if (dev_root.downstream)`. If `dev_root` has no downstream bus, `final_link()` dereferences `link->children` on a NULL pointer. In practice the root always has a downstream bus by finalize time, so this is a latent inconsistency rather than a reachable crash.","fix":"Guard the call to match the other iterators. Before:\n```c\nfinal_link(dev_root.downstream);\n```\nAfter:\n```c\nif (dev_root.downstream)\n\tfinal_link(dev_root.downstream);\n```","locations":[{"ref":"src/device/device.c:651-662","code":" 651 | void dev_finalize(void)\n 652 | {\n 653 | \tprintk(BIOS_INFO, \"Finalize devices...\\n\");\n 654 | \n 655 | \t/* First call the mainboard finalize. */\n 656 | \tfinal_dev(&dev_root);\n 657 | \n 658 | \t/* Now finalize everything. */\n 659 | \tfinal_link(dev_root.downstream);\n 660 | \n 661 | \tprintk(BIOS_INFO, \"Devices finalized\\n\");\n 662 | }","lines":12,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/device/device.c#L651-L662"},{"ref":"src/device/device.c:605-606","code":" 605 | \tif (dev_root.downstream)\n 606 | \t\tinit_link(dev_root.downstream);","lines":2,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/device/device.c#L605-L606"}]},
{"id":"API_1","type":"api_misuse","type_label":"Api Misuse","severity":"high","difficulty":"medium","description":"In win32/pdcclip.c PDC_setclipboard(), after SetClipboardData() succeeds the\ncode calls GlobalFree(ptr1) at line 125. This is wrong: once SetClipboardData() succeeds,\nownership of the memory transfers to the system clipboard; the application must NOT free it.\nFreeing it here results in a use-after-free whenever the system or another application later\nreads the clipboard data.","fix":"Remove the `GlobalFree(ptr1);` call after the successful SetClipboardData path\n(line 125). Only free ptr1 in the failure branch (already done at line 120).","locations":[{"ref":"payloads/libpayload/curses/PDCurses/win32/pdcclip.c:118-127","code":" 118 | if (!SetClipboardData(PDC_TEXT, ptr1))\n 119 | {\n 120 | GlobalFree(ptr1);\n 121 | return PDC_CLIP_ACCESS_ERROR;\n 122 | }\n 123 | \n 124 | CloseClipboard();\n 125 | GlobalFree(ptr1);\n 126 | \n 127 | return PDC_CLIP_SUCCESS;","lines":10,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/win32/pdcclip.c#L118-L127"}]},
{"id":"API_2","type":"api_misuse","type_label":"Api Misuse","severity":"medium","difficulty":"medium","description":"In os2/pdcclip.c, PDC_setclipboard() and PDC_clearclipboard() do not call WinCreateMsgQueue()\nafter WinInitialize(), unlike PDC_getclipboard() which does. On OS/2, clipboard operations\nthrough the WinPM API require a message queue to be associated with the calling thread. Without\none, WinOpenClipbrd() and clipboard operations may fail or behave unpredictably. Only\nPDC_getclipboard creates and destroys a message queue (lines 63 and 92 respectively);\nthe other two functions skip this step.","fix":"Add `HMQ hmq = WinCreateMsgQueue(hab, 0);` after WinInitialize() in both PDC_setclipboard and PDC_clearclipboard, and add `WinDestroyMsgQueue(hmq);` before WinTerminate().","locations":[{"ref":"payloads/libpayload/curses/PDCurses/os2/pdcclip.c:101-149","code":" 101 | int PDC_setclipboard(const char *contents, long length)\n 102 | {\n 103 | #ifndef EMXVIDEO\n 104 | HAB hab;\n 105 | PTIB ptib;\n 106 | PPIB ppib;\n 107 | ULONG ulRC;\n 108 | PSZ szTextOut = NULL;\n 109 | int rc;\n 110 | #endif\n 111 | PDC_LOG((\"PDC_setclipboard() - called\\n\"));\n 112 | \n 113 | #ifndef EMXVIDEO\n 114 | DosGetInfoBlocks(&ptib, &ppib);\n 115 | ppib->pib_ultype = 3;\n 116 | hab = WinInitialize(0);\n 117 | \n 118 | if (!WinOpenClipbrd(hab))\n 119 | {\n 120 | WinTerminate(hab);\n 121 | return PDC_CLIP_ACCESS_ERROR;\n 122 | }\n 123 | \n 124 | rc = PDC_CLIP_MEMORY_ERROR;\n 125 | \n 126 | ulRC = DosAllocSharedMem((PVOID)&szTextOut, NULL, length + 1,\n 127 | PAG_WRITE | PAG_COMMIT | OBJ_GIVEABLE);\n 128 | \n 129 | if (ulRC == 0)\n 130 | {\n 131 | strcpy(szTextOut, contents);\n 132 | WinEmptyClipbrd(hab);\n 133 | \n 134 | if (WinSetClipbrdData(hab, (ULONG)szTextOut, CF_TEXT, CFI_POINTER))\n 135 | rc = PDC_CLIP_SUCCESS;\n 136 | else\n 137 | {\n 138 | DosFreeMem(szTextOut);\n 139 | rc = PDC_CLIP_ACCESS_ERROR;\n 140 | }\n 141 | }\n 142 | \n 143 | WinCloseClipbrd(hab);\n 144 | WinTerminate(hab);\n 145 | \n 146 | return rc;\n 147 | #else\n 148 | return PDC_CLIP_ACCESS_ERROR;\n 149 | #endif","lines":49,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/os2/pdcclip.c#L101-L149"},{"ref":"payloads/libpayload/curses/PDCurses/os2/pdcclip.c:162-185","code":" 162 | int PDC_clearclipboard(void)\n 163 | {\n 164 | #ifndef EMXVIDEO\n 165 | HAB hab;\n 166 | PTIB ptib;\n 167 | PPIB ppib;\n 168 | #endif\n 169 | PDC_LOG((\"PDC_clearclipboard() - called\\n\"));\n 170 | \n 171 | #ifndef EMXVIDEO\n 172 | DosGetInfoBlocks(&ptib, &ppib);\n 173 | ppib->pib_ultype = 3;\n 174 | hab = WinInitialize(0);\n 175 | \n 176 | WinEmptyClipbrd(hab);\n 177 | \n 178 | WinCloseClipbrd(hab);\n 179 | WinTerminate(hab);\n 180 | \n 181 | return PDC_CLIP_SUCCESS;\n 182 | #else\n 183 | return PDC_CLIP_ACCESS_ERROR;\n 184 | #endif\n 185 | }","lines":24,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/os2/pdcclip.c#L162-L185"}]},
{"id":"API_3","type":"api_misuse","type_label":"Api Misuse","severity":"medium","difficulty":"medium","description":"`fsp_load_and_convert_bmp_to_gop_blt()` declares `logo_size` as `uint32_t *` but casts it to `(size_t *)` before passing it to `load_and_convert_bmp_to_blt()`, whose implementation does `*logo_size = bmp_size;` writing a full `size_t`. On x86_64 ramstage builds `size_t` is 8 bytes while the caller's storage is a 4-byte `uint32_t` (e.g. `uint32_t logo_size;` in `soc/intel/pantherlake/fsp_params.c`, or the `UINT32 LogoSize` UPD field in meteorlake), so the call performs an 8-byte write into a 4-byte object, corrupting the adjacent stack variable / UPD field. The same reinterpreting-cast pattern is applied to `blt_ptr`/`blt_size` (`efi_uintn_t *` cast to `uintptr_t *`/`size_t *`), which is only safe as long as those widths happen to coincide.","fix":"Make the wrapper's parameter types match the underlying `load_and_convert_bmp_to_blt()` types (use `size_t *logo_size`, `size_t *blt_size` and `uintptr_t *` for the pointers) and update the header and callers accordingly, instead of casting pointers of different widths. Alternatively, marshal through local `size_t`/`uintptr_t` variables and copy back into the narrower caller fields with an explicit range check.","locations":[{"ref":"src/drivers/intel/fsp2_0/fsp_gop_blt.c:11-17","code":" 11 | void fsp_load_and_convert_bmp_to_gop_blt(efi_uintn_t *logo, uint32_t *logo_size,\n 12 | \tefi_uintn_t *blt_ptr, efi_uintn_t *blt_size, uint32_t *pixel_height, uint32_t *pixel_width,\n 13 | \tenum lb_fb_orientation orientation)\n 14 | {\n 15 | \tload_and_convert_bmp_to_blt((uintptr_t *)logo, (size_t *)logo_size, (uintptr_t *)blt_ptr,\n 16 | \t\t\t\t(size_t *)blt_size, pixel_height, pixel_width, orientation);\n 17 | }","lines":7,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/intel/fsp2_0/fsp_gop_blt.c#L11-L17"},{"ref":"src/drivers/intel/fsp2_0/fsp_gop_blt.c:20-26","code":" 20 | void fsp_convert_bmp_to_gop_blt(uintptr_t logo_ptr, size_t logo_ptr_size,\n 21 | \tefi_uintn_t *blt_ptr, efi_uintn_t *blt_size, uint32_t *pixel_height, uint32_t *pixel_width,\n 22 | \tenum lb_fb_orientation orientation)\n 23 | {\n 24 | \tconvert_bmp_to_blt(logo_ptr, logo_ptr_size, (uintptr_t *)blt_ptr, (size_t *)blt_size,\n 25 | \t\t\t\tpixel_height, pixel_width, orientation);\n 26 | }","lines":7,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/intel/fsp2_0/fsp_gop_blt.c#L20-L26"}]},
{"id":"API_4","type":"api_misuse","type_label":"Api Misuse","severity":"low","difficulty":"medium","description":"The `FreeAndNull` macro in curses.priv.h (line 848) is defined as:\n #define FreeAndNull(p) free(p); p = 0\nThis is two semicolon-separated statements without a `do{}while(0)` wrapper.\nUsing it in a single-branch `if` or `else` without braces will only apply the\n`free(p)` to the conditional branch while `p = 0` executes unconditionally,\ncreating a use-after-free if the pointer is later accessed after an apparent\n\"not-freed\" path.","fix":"Wrap the macro body in `do { free(p); (p) = 0; } while (0)` to make it safe in all contexts.","locations":[{"ref":"payloads/libpayload/curses/curses.priv.h:848","code":" 848 | #define FreeAndNull(p) free(p); p = 0","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/curses.priv.h#L848"}]},
{"id":"API_5","type":"api_misuse","type_label":"Api Misuse","severity":"low","difficulty":"easy","description":"`pcix_scan_bridge()` calls `pos = pci_find_capability(dev, PCI_CAP_ID_PCIX)` and then unconditionally does `sstatus = pci_read_config16(dev, pos + PCI_X_SEC_STATUS)` without checking whether `pos` is non-zero. `pci_find_capability()` returns `0` when the capability is absent, so on a bridge without a PCI-X capability the code reads config offset `PCI_X_SEC_STATUS` (a low, unrelated register), then feeds the garbage value into `PCI_X_SSTATUS_MFREQ(sstatus)` and may erroneously invoke `pcix_tune_bus()` on the secondary bus. The sibling function `pcix_tune_dev()` correctly guards with `if (!cap) return;`, so this is an inconsistency and a latent misconfiguration.","fix":"After `pos = pci_find_capability(dev, PCI_CAP_ID_PCIX);` add a guard, e.g. `if (!pos) return;` (or skip the `sstatus` read and tuning when `pos == 0`), mirroring the check already present in `pcix_tune_dev()`.","locations":[{"ref":"src/device/pcix_device.c:106-110","code":" 106 | \tpos = pci_find_capability(dev, PCI_CAP_ID_PCIX);\n 107 | \tsstatus = pci_read_config16(dev, pos + PCI_X_SEC_STATUS);\n 108 | \n 109 | \tif (PCI_X_SSTATUS_MFREQ(sstatus) != PCI_X_SSTATUS_CONVENTIONAL_PCI)\n 110 | \t\tpcix_tune_bus(dev->downstream);","lines":5,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/device/pcix_device.c#L106-L110"}]},
{"id":"PERF_1","type":"performance","type_label":"Performance","severity":"low","difficulty":"medium","description":"In sha1.c SHA1Pad(), the padding loop calls SHA1Update() one byte at a time (with a single '\\0' byte) until the bit count reaches 448 mod 512. This can call SHA1Update up to 63 times individually rather than building the padding in a single buffer and calling it once. For a hash-heavy boot environment this is needlessly slow.","fix":"Pre-build a zero-padding buffer of the required size and pass it to SHA1Update in a single call.","locations":[{"ref":"payloads/libpayload/crypto/sha1.c:169-172","code":" 169 | \tSHA1Update(context, (u_int8_t *)\"\\200\", 1);\n 170 | \twhile ((context->count & 504) != 448)\n 171 | \t\tSHA1Update(context, (u_int8_t *)\"\\0\", 1);\n 172 | \tSHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/crypto/sha1.c#L169-L172"}]},
{"id":"DEAD_1","type":"dead_code","type_label":"Dead Code","severity":"low","difficulty":"medium","description":"APIC_SPURIOUS is defined twice in apic.c (lines 54 and 57) with the same value 0x0F0. The second definition is a redundant leftover, likely from a copy-paste between the register offset block and the bit-field block. Modern compilers emit a macro redefinition warning.","fix":"Remove the duplicate #define APIC_SPURIOUS 0x0F0 on line 57.","locations":[{"ref":"payloads/libpayload/arch/x86/apic.c:54-57","code":" 54 | #define APIC_SPURIOUS\t\t\t0x0F0\n 55 | #define APIC_SW_ENABLED_BIT\t\t(1 << 8)\n 56 | #define APIC_SPURIOUS_VECTOR_MASK\t0xFFUL\n 57 | #define APIC_SPURIOUS\t\t\t0x0F0","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/arch/x86/apic.c#L54-L57"}]},
{"id":"DEAD_2","type":"dead_code","type_label":"Dead Code","severity":"low","difficulty":"medium","description":"In demos/xmas.c reindeer(), the check `if (x_pos < 62) y_pos = 1;` at line 821 is inside a\n`for (x_pos = 70; x_pos > 62; x_pos--)` loop. Because the loop condition is `x_pos > 62`,\nx_pos is never less than 62 inside the loop body. The condition is always false and the\ny_pos assignment never executes.","fix":"Remove the dead `if (x_pos < 62) y_pos = 1;` check, or adjust the loop bounds if the intent was to handle x_pos reaching values below 62.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/demos/xmas.c:819-822","code":" 819 | for (x_pos = 70; x_pos > 62; x_pos--)\n 820 | {\n 821 | if (x_pos < 62)\n 822 | y_pos = 1;","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/demos/xmas.c#L819-L822"}]},
{"id":"DEAD_3","type":"dead_code","type_label":"Dead Code","severity":"low","difficulty":"very_easy","description":"PDC_restore_screen_mode() and PDC_save_screen_mode() in sdl1/pdcscrn.c\n(lines 247-253) have empty bodies. They accept an int parameter that is never used. These are\neffectively no-ops and serve no function in the SDL backend.","fix":"No fix needed for functionality; consider adding a (void)i; to silence compiler warnings about unused parameters.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/sdl1/pdcscrn.c:247-253","code":" 247 | void PDC_restore_screen_mode(int i)\n 248 | {\n 249 | }\n 250 | \n 251 | void PDC_save_screen_mode(int i)\n 252 | {\n 253 | }","lines":7,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/sdl1/pdcscrn.c#L247-L253"}]},
{"id":"DEAD_4","type":"dead_code","type_label":"Dead Code","severity":"low","difficulty":"very_easy","description":"PDC_beep() in sdl1/pdcutil.c (lines 7-10) does nothing except log. The SDL\nbackend provides no actual audio beep functionality, making the function a silent no-op for the\nentire SDL port.","fix":"Document that the SDL backend has no beep support, or implement SDL_mixer beep.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/sdl1/pdcutil.c:7-10","code":" 7 | void PDC_beep(void)\n 8 | {\n 9 | PDC_LOG((\"PDC_beep() - called\\n\"));\n 10 | }","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/sdl1/pdcutil.c#L7-L10"}]},
{"id":"DEAD_5","type":"dead_code","type_label":"Dead Code","severity":"low","difficulty":"very_easy","description":"In set_form_opts, form_opts_on, and form_opts_off the pattern 'opts &= ALL_FORM_OPTS; if (opts & ~ALL_FORM_OPTS)' is used to validate the option bits. After ANDing with ALL_FORM_OPTS, the expression 'opts & ~ALL_FORM_OPTS' is always zero because all bits outside ALL_FORM_OPTS have already been cleared. The E_BAD_ARGUMENT branch is therefore unreachable dead code. The validation never fires regardless of what the caller passes.","fix":"To actually catch invalid option bits, check before masking: 'if (opts & ~ALL_FORM_OPTS) RETURN(E_BAD_ARGUMENT);' then apply the mask, or drop the mask entirely and just validate.","locations":[{"ref":"payloads/libpayload/curses/form/frm_opts.c:52-54","code":" 52 | opts &= ALL_FORM_OPTS;\n 53 | if (opts & ~ALL_FORM_OPTS)\n 54 | RETURN(E_BAD_ARGUMENT);","lines":3,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/frm_opts.c#L52-L54"},{"ref":"payloads/libpayload/curses/form/frm_opts.c:92-94","code":" 92 | opts &= ALL_FORM_OPTS;\n 93 | if (opts & ~ALL_FORM_OPTS)\n 94 | RETURN(E_BAD_ARGUMENT);","lines":3,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/frm_opts.c#L92-L94"},{"ref":"payloads/libpayload/curses/form/frm_opts.c:117-119","code":" 117 | opts &= ALL_FORM_OPTS;\n 118 | if (opts & ~ALL_FORM_OPTS)\n 119 | RETURN(E_BAD_ARGUMENT);","lines":3,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/frm_opts.c#L117-L119"}]},
{"id":"DEAD_6","type":"dead_code","type_label":"Dead Code","severity":"low","difficulty":"very_easy","description":"In set_item_opts (m_item_opt.c), 'opts &= ALL_ITEM_OPTS' is applied at line 58,\nmasking all bits not in ALL_ITEM_OPTS. The immediately following check\n'if (opts & ~ALL_ITEM_OPTS)' at line 60 can therefore never be true - after the\nmask operation, opts cannot have any bits outside ALL_ITEM_OPTS. The E_BAD_ARGUMENT\nbranch is unreachable dead code.","fix":"Either remove the mask-then-check pattern and validate before masking:\n if (opts & ~ALL_ITEM_OPTS)\n RETURN(E_BAD_ARGUMENT);\n opts &= ALL_ITEM_OPTS;\nOr remove the dead check entirely if the mask is the intended behavior.","locations":[{"ref":"payloads/libpayload/curses/menu/m_item_opt.c:58-61","code":" 58 | opts &= ALL_ITEM_OPTS;\n 59 | \n 60 | if (opts & ~ALL_ITEM_OPTS)\n 61 | RETURN(E_BAD_ARGUMENT);","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/menu/m_item_opt.c#L58-L61"}]},
{"id":"DEAD_7","type":"dead_code","type_label":"Dead Code","severity":"low","difficulty":"medium","description":"In tinycurses.c, delwin() is a stub that always returns OK without freeing anything (lines 220-232). window_list, ldat_list, and linebuf_list are statically allocated and window_count/ldat_count/linebuf_count never decrease. This means after unpost_menu() deletes sub and win windows, those allocation slots are permanently consumed. With MAX_WINDOWS=3 and the menu library creating 2 windows per post_menu() call, a second post_menu() call after unpost_menu() will fail to allocate windows.","fix":"Implement delwin() to decrement window_count and return the slot, or use a free-list pattern to recycle window slots.","locations":[{"ref":"payloads/libpayload/curses/tinycurses.c:220-232","code":" 220 | int delwin(WINDOW *win)\n 221 | {\n 222 | \t/* TODO: Don't try to delete stdscr. */\n 223 | \t/* TODO: Don't delete parent windows before subwindows. */\n 224 | \n 225 | \t// if (win->_flags & _SUBWIN)\n 226 | \t// \ttouchwin(win->_parent);\n 227 | \t// else if (curscr != 0)\n 228 | \t// \ttouchwin(curscr);\n 229 | \n 230 | \t// return _nc_freewin(win);\n 231 | \treturn OK;\n 232 | }","lines":13,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/tinycurses.c#L220-L232"}]},
{"id":"DEAD_8","type":"dead_code","type_label":"Dead Code","severity":"low","difficulty":"medium","description":"In tinycurses.c, wattr_on() and wattr_off() both have their core logic commented out (lines 574 and 581: `toggle_attr_on`/`toggle_attr_off`). Only the color-pair side effect is implemented. The functions return OK but do not actually apply bold, underline, or reverse attributes to the window's attr field (WINDOW_ATTRS), causing wattron()/wattroff() calls from the menu library (m_post.c) to be no-ops for attributes other than color. This means items in the menu will not display foreground/background/grey attribute differences on the tinycurses backend.","fix":"Implement toggle_attr_on/toggle_attr_off: `WINDOW_ATTRS(win) |= at;` in wattr_on and `WINDOW_ATTRS(win) &= ~at;` in wattr_off, guarding color separately.","locations":[{"ref":"payloads/libpayload/curses/tinycurses.c:570-583","code":" 570 | int wattr_on(WINDOW *win, attr_t at, void *opts GCC_UNUSED)\n 571 | {\n 572 | \tif (at & A_COLOR)\n 573 | \t\twin->_color = PAIR_NUMBER(at);\n 574 | \t// toggle_attr_on(WINDOW_ATTRS(win), at);\n 575 | \treturn OK;\n 576 | }\n 577 | int wattr_off(WINDOW *win, attr_t at, void *opts GCC_UNUSED)\n 578 | {\n 579 | \tif (at & A_COLOR)\n 580 | \t\twin->_color = 0;\n 581 | \t// toggle_attr_off(WINDOW_ATTRS(win), at);\n 582 | \treturn 0;\n 583 | }","lines":14,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/tinycurses.c#L570-L583"}]},
{"id":"DEAD_9","type":"dead_code","type_label":"Dead Code","severity":"low","difficulty":"medium","description":"In pci_qcom.c the macro lower_32_bits and upper_32_bits (lines 32-33) are local redefinitions that shadow any globally-defined versions. These could cause silent behavior divergence if the global definitions change. Furthermore, upper_32_bits uses the double-shift idiom ((n >> 16) >> 16) to avoid undefined behavior with shift-by-32 on 32-bit types, but since n is always uint64_t at the call sites, a plain (u32)((n) >> 32) would be clearer and equally correct.","fix":"Remove the local macro definitions and include or use whatever header already defines these macros project-wide, to avoid duplicate definitions.","locations":[{"ref":"payloads/libpayload/drivers/pci_qcom.c:32-33","code":" 32 | #define lower_32_bits(n)\t\t((u32)(n))\n 33 | #define upper_32_bits(n)\t\t((u32)(((n) >> 16) >> 16))","lines":2,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/pci_qcom.c#L32-L33"}]},
{"id":"DEAD_10","type":"dead_code","type_label":"Dead Code","severity":"low","difficulty":"very_easy","description":"In `mp_get_processor_info()` the check `if (!info)` can never be true because `info = &cpu_infos[processor_number]` is the address of an array element and is always non-NULL, so it is dead code. The value that actually can be NULL is `dev = info->cpu`, which is dereferenced unchecked at `dev->path.apic.apic_id` (line 70). If a slot within `get_cpu_count()` has an unpopulated `cpu` pointer this is a NULL dereference.","fix":"Check the pointer that can actually be NULL:\n\nBefore:\n```c\n\tstruct cpu_info *info = &cpu_infos[processor_number];\n\tif (!info)\n\t\treturn FSP_DEVICE_ERROR;\n\n\tstruct device *dev = info->cpu;\n```\nAfter:\n```c\n\tstruct cpu_info *info = &cpu_infos[processor_number];\n\tstruct device *dev = info->cpu;\n\tif (!dev)\n\t\treturn FSP_DEVICE_ERROR;\n```","locations":[{"ref":"src/drivers/intel/fsp2_0/ppi/mp_service_ppi.c:55-70","code":" 55 | \tstruct cpu_info *info = &cpu_infos[processor_number];\n 56 | \tif (!info)\n 57 | \t\treturn FSP_DEVICE_ERROR;\n 58 | \n 59 | \tstruct device *dev = info->cpu;\n 60 | \n 61 | \tif (processor_info_buffer == NULL)\n 62 | \t\treturn FSP_INVALID_PARAMETER;\n 63 | \n 64 | \tprocessor_info_buffer->StatusFlag = PROCESSOR_HEALTH_STATUS_BIT\n 65 | \t\t\t| PROCESSOR_ENABLED_BIT;\n 66 | \n 67 | \tif (processor_number == BSP_CPU_SLOT)\n 68 | \t\tprocessor_info_buffer->StatusFlag |= PROCESSOR_AS_BSP_BIT;\n 69 | \n 70 | \tprocessor_info_buffer->ProcessorId = dev->path.apic.apic_id;","lines":16,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/intel/fsp2_0/ppi/mp_service_ppi.c#L55-L70"}]},
{"id":"DEAD_11","type":"dead_code","type_label":"Dead Code","severity":"negligible","difficulty":"medium","description":"In virtual.c, the file defines only getpagesize() returning 4096 and the virtual_offset global. The function is the standard POSIX getpagesize() but libpayload targets bare metal where no virtual-memory page size concept applies. The stub is inert but may mislead callers into thinking paging is configured.","fix":"Consider marking the function with a comment explaining it is a stub for compatibility, or adding a static_assert / build-time check that it is only referenced where appropriate.","locations":[{"ref":"payloads/libpayload/arch/x86/virtual.c:33-36","code":" 33 | int getpagesize(void)\n 34 | {\n 35 | \treturn 4096;\n 36 | }","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/arch/x86/virtual.c#L33-L36"}]},
{"id":"DEAD_12","type":"dead_code","type_label":"Dead Code","severity":"negligible","difficulty":"very_easy","description":"dupwin() in window.c assigns 'new->_flags = win->_flags' twice (lines 408 and 421) with no other code between that could modify _flags. The second assignment is dead code.","fix":"Remove the duplicate assignment at line 421.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/window.c:408-421","code":" 408 | new->_flags = win->_flags;\n 409 | new->_attrs = win->_attrs;\n 410 | new->_clear = win->_clear;\n 411 | new->_leaveit = win->_leaveit;\n 412 | new->_scroll = win->_scroll;\n 413 | new->_nodelay = win->_nodelay;\n 414 | new->_use_keypad = win->_use_keypad;\n 415 | new->_tmarg = win->_tmarg;\n 416 | new->_bmarg = win->_bmarg;\n 417 | new->_parx = win->_parx;\n 418 | new->_pary = win->_pary;\n 419 | new->_parent = win->_parent;\n 420 | new->_bkgd = win->_bkgd;\n 421 | new->_flags = win->_flags;","lines":14,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/window.c#L408-L421"}]},
{"id":"DEAD_13","type":"dead_code","type_label":"Dead Code","severity":"negligible","difficulty":"medium","description":"`f_trace.c` provides five tracing wrappers (`_nc_retrace_field_ptr`,\n`_nc_retrace_field`, `_nc_retrace_field_type`, `_nc_retrace_form`,\n`_nc_retrace_form_hook`) that are guarded by `#ifdef TRACE` at their\ncall sites (via the `returnField`, `returnForm`, etc. macros in form.priv.h\nlines 219-229). When compiled without TRACE, the call-site macros expand to\nplain `return code` and these five functions are never called. The translation\nunit itself is always compiled, producing five unreferenced symbols that bloat\nthe object file and confuse linkers that warn about unused functions.","fix":"Wrap the entire file body (or each function individually) with `#ifdef TRACE ... #endif` to match the conditional use at call sites.","locations":[{"ref":"payloads/libpayload/curses/form/f_trace.c:37-70","code":" 37 | NCURSES_EXPORT(FIELD **)\n 38 | _nc_retrace_field_ptr(FIELD **code)\n 39 | {\n 40 | T((T_RETURN(\"%p\"), (void *)code));\n 41 | return code;\n 42 | }\n 43 | \n 44 | NCURSES_EXPORT(FIELD *)\n 45 | _nc_retrace_field(FIELD *code)\n 46 | {\n 47 | T((T_RETURN(\"%p\"), (void *)code));\n 48 | return code;\n 49 | }\n 50 | \n 51 | NCURSES_EXPORT(FIELDTYPE *)\n 52 | _nc_retrace_field_type(FIELDTYPE *code)\n 53 | {\n 54 | T((T_RETURN(\"%p\"), (void *)code));\n 55 | return code;\n 56 | }\n 57 | \n 58 | NCURSES_EXPORT(FORM *)\n 59 | _nc_retrace_form(FORM *code)\n 60 | {\n 61 | T((T_RETURN(\"%p\"), (void *)code));\n 62 | return code;\n 63 | }\n 64 | \n 65 | NCURSES_EXPORT(Form_Hook)\n 66 | _nc_retrace_form_hook(Form_Hook code)\n 67 | {\n 68 | T((T_RETURN(\"%p\"), code));\n 69 | return code;\n 70 | }","lines":34,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/f_trace.c#L37-L70"}]},
{"id":"CMT_1","type":"invalid_comment","type_label":"Invalid Comment","severity":"low","difficulty":"very_easy","description":"In attr.c, the PDC_LOG call inside wattr_on() (line 260) logs 'wattr_off() - called' instead of 'wattr_on() - called'. This is a copy-paste error in the debug message only.","fix":"Change the string from '\"wattr_off() - called\\n\"' to '\"wattr_on() - called\\n\"'.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/attr.c:260","code":" 260 | PDC_LOG((\"wattr_off() - called\\n\"));","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/attr.c#L260"}]},
{"id":"CMT_2","type":"invalid_comment","type_label":"Invalid Comment","severity":"low","difficulty":"very_easy","description":"In attr.c, the PDC_LOG call inside attr_set() (line 286) logs 'attr_get() - called' instead of 'attr_set() - called'. This is a copy-paste error in the debug message.","fix":"Change the log string to '\"attr_set() - called\\n\"'.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/attr.c:286","code":" 286 | PDC_LOG((\"attr_get() - called\\n\"));","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/attr.c#L286"}]},
{"id":"CMT_3","type":"invalid_comment","type_label":"Invalid Comment","severity":"low","difficulty":"easy","description":"In `smbios_write_type4()` the comment claims core count is computed by \"dividing SMT_ID (logical processors in a core) by Core_ID (number of cores)\", and the locals are named `leaf_b_cores`/`leaf_b_threads`, but the code actually does the opposite: `leaf_b_cores = cpuid_ext(0xb, 1).ebx` is the total logical processors at the core level (package-wide threads) and `leaf_b_threads = cpuid_ext(0xb, 0).ebx` is logical processors per core (threads-per-core). `core_count2 = leaf_b_cores / leaf_b_threads` = total_threads / threads_per_core = number of cores, which is correct, but the comment and variable names are inverted/misleading and will misguide future maintainers.","fix":"Rename the locals to reflect reality (e.g. `total_logical_procs` for leaf 0xb sub-leaf 1 ebx and `threads_per_core` for sub-leaf 0 ebx) and correct the comment to describe `core_count = total_logical_procs / threads_per_core`.","locations":[{"ref":"src/arch/x86/smbios.c:142-161","code":" 142 | \t/*\n 143 | \t * If CPUID leaf 11 is available, calculate \"core count\" by dividing\n 144 | \t * SMT_ID (logical processors in a core) by Core_ID (number of cores).\n 145 | \t * This seems to be the way to arrive to a number of cores mentioned on\n 146 | \t * ark.intel.com.\n 147 | \t */\n 148 | \tif (cpu_have_cpuid() && cpuid_get_max_func() >= 0xb) {\n 149 | \t\tuint32_t leaf_b_cores = 0, leaf_b_threads = 0;\n 150 | \t\tres = cpuid_ext(0xb, 1);\n 151 | \t\tleaf_b_cores = res.ebx;\n 152 | \t\tres = cpuid_ext(0xb, 0);\n 153 | \t\tleaf_b_threads = res.ebx;\n 154 | \t\t/* if hyperthreading is not available, pretend this is 1 */\n 155 | \t\tif (leaf_b_threads == 0)\n 156 | \t\t\tleaf_b_threads = 1;\n 157 | \n 158 | \t\tt->core_count2 = leaf_b_cores / leaf_b_threads;\n 159 | \t\tt->core_count = t->core_count2 > 0xff ? 0xff : t->core_count2;\n 160 | \t\tt->thread_count2 = leaf_b_cores;\n 161 | \t\tt->thread_count = t->thread_count2 > 0xff ? 0xff : t->thread_count2;","lines":20,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/arch/x86/smbios.c#L142-L161"}]},
{"id":"CMT_4","type":"invalid_comment","type_label":"Invalid Comment","severity":"negligible","difficulty":"very_easy","description":"In arm/virtual.c, the comment for lpae_map_phys_addr has a typo: 'Data chache policy' should be 'Data cache policy'.","fix":"Change 'chache' to 'cache' in the comment.","locations":[{"ref":"payloads/libpayload/arch/arm/virtual.c:118","code":" 118 | * policy: Data chache policy","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/arch/arm/virtual.c#L118"}]},
{"id":"CMT_5","type":"invalid_comment","type_label":"Invalid Comment","severity":"negligible","difficulty":"very_easy","description":"In arm64/cache.c, the file header comment says 'Cache maintenance routines for ARM64-A and ARM64-R' and references 'ARM64 Architecture Reference Manual, ARM64-A and ARM64-R edition'. These are not valid ARM architecture names. The ARM 64-bit ISA is ARMv8-A (or AArch64). The equivalent arm/cache.c correctly says 'ARMv7-A and ARMv7-R'. This comment was copy-pasted and incorrectly updated.","fix":"Change to: 'Cache maintenance routines for ARMv8-A' and reference 'ARM Architecture Reference Manual, ARMv8, for ARMv8-A architecture profile'.","locations":[{"ref":"payloads/libpayload/arch/arm64/cache.c:28-30","code":" 28 | * cache.c: Cache maintenance routines for ARM64-A and ARM64-R\n 29 | *\n 30 | * Reference: ARM64 Architecture Reference Manual, ARM64-A and ARM64-R edition","lines":3,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/arch/arm64/cache.c#L28-L30"}]},
{"id":"CMT_6","type":"invalid_comment","type_label":"Invalid Comment","severity":"negligible","difficulty":"very_easy","description":"In arm64/cache.c, dcache_line_bytes() has a comment 'bytes per word' on the multiply-by-sizeof(unsigned int) line. The correct comment (used in the equivalent arm/cache.c) is 'bytes per line', because the line variable at that point counts words per cache line, and the multiplication converts it to bytes per cache line.","fix":"Change the comment to: /* bytes per line */","locations":[{"ref":"payloads/libpayload/arch/arm64/cache.c:66","code":" 66 | \tline_bytes *= sizeof(unsigned int);\t/* bytes per word */","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/arch/arm64/cache.c#L66"}]},
{"id":"CMT_7","type":"invalid_comment","type_label":"Invalid Comment","severity":"negligible","difficulty":"very_easy","description":"In `spd_decode_ddr3()` the debug strings printed for the two DDR3 thermal/refresh option bits are swapped relative to the flags they set. `reg8 & 0x02` correctly sets `dimm->flags.ext_temp_range` but prints `\" ext_temp_refresh\"`, while `reg8 & 0x01` correctly sets `dimm->flags.ext_temp_refresh` but prints `\" ext_temp_range\"`. The stored flags are correct; only the human-readable `printram` output is misleading.","fix":"Swap the two printed strings to match the flags being set.\n\nBefore:\n```c\nif (reg8 & 0x02) {\n\tdimm->flags.ext_temp_range = 1;\n\tprintram(\" ext_temp_refresh\");\n}\nif (reg8 & 0x01) {\n\tdimm->flags.ext_temp_refresh = 1;\n\tprintram(\" ext_temp_range\");\n}\n```\nAfter:\n```c\nif (reg8 & 0x02) {\n\tdimm->flags.ext_temp_range = 1;\n\tprintram(\" ext_temp_range\");\n}\nif (reg8 & 0x01) {\n\tdimm->flags.ext_temp_refresh = 1;\n\tprintram(\" ext_temp_refresh\");\n}\n```","locations":[{"ref":"src/device/dram/ddr3.c:343-350","code":" 343 | \tif (reg8 & 0x02) {\n 344 | \t\tdimm->flags.ext_temp_range = 1;\n 345 | \t\tprintram(\" ext_temp_refresh\");\n 346 | \t}\n 347 | \tif (reg8 & 0x01) {\n 348 | \t\tdimm->flags.ext_temp_refresh = 1;\n 349 | \t\tprintram(\" ext_temp_range\");\n 350 | \t}","lines":8,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/device/dram/ddr3.c#L343-L350"}]},
{"id":"CMT_8","type":"invalid_comment","type_label":"Invalid Comment","severity":"negligible","difficulty":"very_easy","description":"The comment above the `_DSD`/SSCN package generation in `dw_i2c_acpi_write_speed_config()` documents the package order as `{ scl_lcnt, scl_hcnt, sda_hold }`, but the code writes `scl_hcnt` first, then `scl_lcnt` (`acpigen_write_word(config->scl_hcnt); acpigen_write_word(config->scl_lcnt);`). The code order (hcnt, lcnt) is the one the Linux i2c-designware driver expects, so the code is correct and the comment is misleading.","fix":"Update the comment to match the emitted order:\n```c\n/* Package () { scl_hcnt, scl_lcnt, sda_hold } */\n```\n(before: `/* Package () { scl_lcnt, scl_hcnt, sda_hold } */`)","locations":[{"ref":"src/drivers/i2c/designware/dw_i2c.c:782-786","code":" 782 | \t/* Package () { scl_lcnt, scl_hcnt, sda_hold } */\n 783 | \tacpigen_write_package(3);\n 784 | \tacpigen_write_word(config->scl_hcnt);\n 785 | \tacpigen_write_word(config->scl_lcnt);\n 786 | \tacpigen_write_dword(config->sda_hold);","lines":5,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/drivers/i2c/designware/dw_i2c.c#L782-L786"}]},
{"id":"DUP_1","type":"duplicate_code","type_label":"Duplicate Code","severity":"low","difficulty":"medium","description":"The acs_map[128] array definition is duplicated verbatim between dos/pdcdisp.c (lines 14-42)\nand os2/pdcdisp.c (lines 14-42), under identical preprocessor guards. Any correction to the\nACS character mappings must be applied in both places.","fix":"Define acs_map in a single shared source file (e.g. pdcurses/acs.c) and remove the duplicate definitions.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/dos/pdcdisp.c:14-42","code":" 14 | chtype acs_map[128] =\n 15 | {\n 16 | A(0), A(1), A(2), A(3), A(4), A(5), A(6), A(7), A(8), A(9), A(10),\n 17 | A(11), A(12), A(13), A(14), A(15), A(16), A(17), A(18), A(19),\n 18 | A(20), A(21), A(22), A(23), A(24), A(25), A(26), A(27), A(28),\n 19 | A(29), A(30), A(31), ' ', '!', '\"', '#', '$', '%', '&', '\\'', '(',\n 20 | ')', '*',\n 21 | \n 22 | A(0x1a), A(0x1b), A(0x18), A(0x19),\n 23 | \n 24 | '/',\n 25 | \n 26 | 0xdb,\n 27 | \n 28 | '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=',\n 29 | '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',\n 30 | 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',\n 31 | 'X', 'Y', 'Z', '[', '\\\\', ']', '^', '_',\n 32 | \n 33 | A(0x04), 0xb1,\n 34 | \n 35 | 'b', 'c', 'd', 'e',\n 36 | \n 37 | 0xf8, 0xf1, 0xb0, A(0x0f), 0xd9, 0xbf, 0xda, 0xc0, 0xc5, 0x2d, 0x2d,\n 38 | 0xc4, 0x2d, 0x5f, 0xc3, 0xb4, 0xc1, 0xc2, 0xb3, 0xf3, 0xf2, 0xe3,\n 39 | 0xd8, 0x9c, 0xf9,\n 40 | \n 41 | A(127)\n 42 | };","lines":29,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/dos/pdcdisp.c#L14-L42"},{"ref":"payloads/libpayload/curses/PDCurses/os2/pdcdisp.c:14-42","code":" 14 | chtype acs_map[128] =\n 15 | {\n 16 | A(0), A(1), A(2), A(3), A(4), A(5), A(6), A(7), A(8), A(9),\n 17 | A(10), A(11), A(12), A(13), A(14), A(15), A(16), A(17), A(18),\n 18 | A(19), A(20), A(21), A(22), A(23), A(24), A(25), A(26), A(27),\n 19 | A(28), A(29), A(30), A(31), ' ', '!', '\"', '#', '$', '%', '&',\n 20 | '\\'', '(', ')', '*',\n 21 | \n 22 | A(0x1a), A(0x1b), A(0x18), A(0x19),\n 23 | \n 24 | '/',\n 25 | \n 26 | 0xdb,\n 27 | \n 28 | '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=',\n 29 | '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',\n 30 | 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',\n 31 | 'X', 'Y', 'Z', '[', '\\\\', ']', '^', '_',\n 32 | \n 33 | A(0x04), 0xb1,\n 34 | \n 35 | 'b', 'c', 'd', 'e',\n 36 | \n 37 | 0xf8, 0xf1, 0xb0, A(0x0f), 0xd9, 0xbf, 0xda, 0xc0, 0xc5, 0x2d,\n 38 | 0x2d, 0xc4, 0x2d, 0x5f, 0xc3, 0xb4, 0xc1, 0xc2, 0xb3, 0xf3,\n 39 | 0xf2, 0xe3, 0xd8, 0x9c, 0xf9,\n 40 | \n 41 | A(127)\n 42 | };","lines":29,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/os2/pdcdisp.c#L14-L42"}]},
{"id":"DUP_2","type":"duplicate_code","type_label":"Duplicate Code","severity":"low","difficulty":"medium","description":"The key_table[] array and the shift-key remapping switch block (KEY_HOME -> KEY_SHOME, etc.)\nare duplicated verbatim between dos/pdckbd.c (lines 43-87, 413-446) and os2/pdckbd.c\n(lines 46-90, 422-453). Any bug fix or key mapping change in one file must be manually\nreplicated in the other, creating a maintenance hazard.","fix":"Extract key_table and the shift-remapping logic into a shared pdckbd_common.c or pdckbd_common.h that both dos/ and os2/ can include.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/dos/pdckbd.c:43-87","code":" 43 | static short key_table[] =\n 44 | {\n 45 | -1, ALT_ESC, -1, 0,\n 46 | -1, -1, -1, -1,\n 47 | -1, -1, -1, -1,\n 48 | -1, -1, ALT_BKSP, KEY_BTAB,\n 49 | ALT_Q, ALT_W, ALT_E, ALT_R,\n 50 | ALT_T, ALT_Y, ALT_U, ALT_I,\n 51 | ALT_O, ALT_P, ALT_LBRACKET, ALT_RBRACKET,\n 52 | ALT_ENTER, -1, ALT_A, ALT_S,\n 53 | ALT_D, ALT_F, ALT_G, ALT_H,\n 54 | ALT_J, ALT_K, ALT_L, ALT_SEMICOLON,\n 55 | ALT_FQUOTE, ALT_BQUOTE, -1, ALT_BSLASH,\n 56 | ALT_Z, ALT_X, ALT_C, ALT_V,\n 57 | ALT_B, ALT_N, ALT_M, ALT_COMMA,\n 58 | ALT_STOP, ALT_FSLASH, -1, ALT_PADSTAR,\n 59 | -1, -1, -1, KEY_F(1),\n 60 | KEY_F(2), KEY_F(3), KEY_F(4), KEY_F(5),\n 61 | KEY_F(6), KEY_F(7), KEY_F(8), KEY_F(9),\n 62 | KEY_F(10), -1, -1, KEY_HOME,\n 63 | KEY_UP, KEY_PPAGE, ALT_PADMINUS, KEY_LEFT,\n 64 | KEY_B2, KEY_RIGHT, ALT_PADPLUS, KEY_END,\n 65 | KEY_DOWN, KEY_NPAGE, KEY_IC, KEY_DC,\n 66 | KEY_F(13), KEY_F(14), KEY_F(15), KEY_F(16),\n 67 | KEY_F(17), KEY_F(18), KEY_F(19), KEY_F(20),\n 68 | KEY_F(21), KEY_F(22), KEY_F(25), KEY_F(26),\n 69 | KEY_F(27), KEY_F(28), KEY_F(29), KEY_F(30),\n 70 | KEY_F(31), KEY_F(32), KEY_F(33), KEY_F(34),\n 71 | KEY_F(37), KEY_F(38), KEY_F(39), KEY_F(40),\n 72 | KEY_F(41), KEY_F(42), KEY_F(43), KEY_F(44),\n 73 | KEY_F(45), KEY_F(46), -1, CTL_LEFT,\n 74 | CTL_RIGHT, CTL_END, CTL_PGDN, CTL_HOME,\n 75 | ALT_1, ALT_2, ALT_3, ALT_4,\n 76 | ALT_5, ALT_6, ALT_7, ALT_8,\n 77 | ALT_9, ALT_0, ALT_MINUS, ALT_EQUAL,\n 78 | CTL_PGUP, KEY_F(11), KEY_F(12), KEY_F(23),\n 79 | KEY_F(24), KEY_F(35), KEY_F(36), KEY_F(47),\n 80 | KEY_F(48), CTL_UP, CTL_PADMINUS, CTL_PADCENTER,\n 81 | CTL_PADPLUS, CTL_DOWN, CTL_INS, CTL_DEL,\n 82 | CTL_TAB, CTL_PADSLASH, CTL_PADSTAR, ALT_HOME,\n 83 | ALT_UP, ALT_PGUP, -1, ALT_LEFT,\n 84 | -1, ALT_RIGHT, -1, ALT_END,\n 85 | ALT_DOWN, ALT_PGDN, ALT_INS, ALT_DEL,\n 86 | ALT_PADSLASH, ALT_TAB, ALT_PADENTER, -1\n 87 | };","lines":45,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/dos/pdckbd.c#L43-L87"},{"ref":"payloads/libpayload/curses/PDCurses/os2/pdckbd.c:46-90","code":" 46 | static short key_table[] =\n 47 | {\n 48 | -1, ALT_ESC, -1, 0,\n 49 | -1, -1, -1, -1,\n 50 | -1, -1, -1, -1,\n 51 | -1, -1, ALT_BKSP, KEY_BTAB,\n 52 | ALT_Q, ALT_W, ALT_E, ALT_R,\n 53 | ALT_T, ALT_Y, ALT_U, ALT_I,\n 54 | ALT_O, ALT_P, ALT_LBRACKET, ALT_RBRACKET,\n 55 | ALT_ENTER, -1, ALT_A, ALT_S,\n 56 | ALT_D, ALT_F, ALT_G, ALT_H,\n 57 | ALT_J, ALT_K, ALT_L, ALT_SEMICOLON,\n 58 | ALT_FQUOTE, ALT_BQUOTE, -1, ALT_BSLASH,\n 59 | ALT_Z, ALT_X, ALT_C, ALT_V,\n 60 | ALT_B, ALT_N, ALT_M, ALT_COMMA,\n 61 | ALT_STOP, ALT_FSLASH, -1, ALT_PADSTAR,\n 62 | -1, -1, -1, KEY_F(1),\n 63 | KEY_F(2), KEY_F(3), KEY_F(4), KEY_F(5),\n 64 | KEY_F(6), KEY_F(7), KEY_F(8), KEY_F(9),\n 65 | KEY_F(10), -1, -1, KEY_HOME,\n 66 | KEY_UP, KEY_PPAGE, ALT_PADMINUS, KEY_LEFT,\n 67 | KEY_B2, KEY_RIGHT, ALT_PADPLUS, KEY_END,\n 68 | KEY_DOWN, KEY_NPAGE, KEY_IC, KEY_DC,\n 69 | KEY_F(13), KEY_F(14), KEY_F(15), KEY_F(16),\n 70 | KEY_F(17), KEY_F(18), KEY_F(19), KEY_F(20),\n 71 | KEY_F(21), KEY_F(22), KEY_F(25), KEY_F(26),\n 72 | KEY_F(27), KEY_F(28), KEY_F(29), KEY_F(30),\n 73 | KEY_F(31), KEY_F(32), KEY_F(33), KEY_F(34),\n 74 | KEY_F(37), KEY_F(38), KEY_F(39), KEY_F(40),\n 75 | KEY_F(41), KEY_F(42), KEY_F(43), KEY_F(44),\n 76 | KEY_F(45), KEY_F(46), -1, CTL_LEFT,\n 77 | CTL_RIGHT, CTL_END, CTL_PGDN, CTL_HOME,\n 78 | ALT_1, ALT_2, ALT_3, ALT_4,\n 79 | ALT_5, ALT_6, ALT_7, ALT_8,\n 80 | ALT_9, ALT_0, ALT_MINUS, ALT_EQUAL,\n 81 | CTL_PGUP, KEY_F(11), KEY_F(12), KEY_F(23),\n 82 | KEY_F(24), KEY_F(35), KEY_F(36), KEY_F(47),\n 83 | KEY_F(48), CTL_UP, CTL_PADMINUS, CTL_PADCENTER,\n 84 | CTL_PADPLUS, CTL_DOWN, CTL_INS, CTL_DEL,\n 85 | CTL_TAB, CTL_PADSLASH, CTL_PADSTAR, ALT_HOME,\n 86 | ALT_UP, ALT_PGUP, -1, ALT_LEFT,\n 87 | -1, ALT_RIGHT, -1, ALT_END,\n 88 | ALT_DOWN, ALT_PGDN, ALT_INS, ALT_DEL,\n 89 | ALT_PADSLASH, ALT_TAB, ALT_PADENTER, -1\n 90 | };","lines":45,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/os2/pdckbd.c#L46-L90"}]},
{"id":"DUP_3","type":"duplicate_code","type_label":"Duplicate Code","severity":"low","difficulty":"medium","description":"`INFINITY` is defined twice in curses.priv.h (lines 1109 and 1125) with the same\nvalue. This compiles without error only because both definitions are identical,\nbut it is misleading and would become a hard error if the values ever diverged.\nThe second definition is also paired with `BAUDBYTE`, suggesting a copy-paste of\nan entire comment block.","fix":"Remove the duplicate `#define INFINITY 1000000` at line 1125.","locations":[{"ref":"payloads/libpayload/curses/curses.priv.h:1109-1126","code":" 1109 | #define INFINITY\t1000000\t/* cost: too high to use */\n 1110 | \n 1111 | extern NCURSES_EXPORT(void) _nc_mvcur_init (void);\n 1112 | extern NCURSES_EXPORT(void) _nc_mvcur_resume (void);\n 1113 | extern NCURSES_EXPORT(void) _nc_mvcur_wrap (void);\n 1114 | \n 1115 | extern NCURSES_EXPORT(int) _nc_scrolln (int, int, int, int);\n 1116 | \n 1117 | extern NCURSES_EXPORT(void) _nc_screen_init (void);\n 1118 | extern NCURSES_EXPORT(void) _nc_screen_resume (void);\n 1119 | extern NCURSES_EXPORT(void) _nc_screen_wrap (void);\n 1120 | \n 1121 | /* lib_mouse.c */\n 1122 | extern NCURSES_EXPORT(int) _nc_has_mouse (void);\n 1123 | \n 1124 | /* lib_mvcur.c */\n 1125 | #define INFINITY\t1000000\t/* cost: too high to use */\n 1126 | #define BAUDBYTE\t9\t/* 9 = 7 bits + 1 parity + 1 stop */","lines":18,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/curses.priv.h#L1109-L1126"}]},
{"id":"DUP_4","type":"duplicate_code","type_label":"Duplicate Code","severity":"low","difficulty":"very_easy","description":"In set_menu_mark (m_item_new.c), after strdup succeeds, strcpy is called\nimmediately on the same source string into the already-duplicated memory.\nstrdup already produces a correct null-terminated copy; the strcpy on line 225\nis entirely redundant and wastes time copying the string a second time.","fix":"Remove the redundant strcpy call. The code should be:\n menu->mark = strdup(mark);\n if (menu->mark)\n {\n if (menu != &_nc_Default_Menu)\n menu->status |= _MARK_ALLOCATED;\n }","locations":[{"ref":"payloads/libpayload/curses/menu/m_item_new.c:222-226","code":" 222 | \t menu->mark = strdup(mark);\n 223 | \t if (menu->mark)\n 224 | \t {\n 225 | \t strcpy(menu->mark, mark);\n 226 | \t if (menu != &_nc_Default_Menu)","lines":5,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/menu/m_item_new.c#L222-L226"}]},
{"id":"OTHER_1","type":"other","type_label":"Other","severity":"high","difficulty":"medium","description":"PDC_scr_open() in sdl1/pdcscrn.c reads environment variables PDC_LINES and\nPDC_COLS via getenv() and passes them directly to atoi() (lines 144-148). The results are\nused without any validation as multipliers to compute screen pixel dimensions\n(pdc_sheight, pdc_swidth). A value of 0, negative, or extremely large can result in\nSDL_SetVideoMode being called with 0x0 or enormous dimensions, or the subsequent\nPDC_get_rows()/PDC_get_columns() returning 0 causing division-by-zero or corrupted\nSP->lines/SP->cols, which propagate into all curses window sizing.","fix":"Validate the values from PDC_LINES and PDC_COLS: clamp to a reasonable range\n(e.g. 1-500) and fall back to the default (25/80) if out of range.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/sdl1/pdcscrn.c:143-151","code":" 143 | {\n 144 | const char *env = getenv(\"PDC_LINES\");\n 145 | pdc_sheight = (env ? atoi(env) : 25) * pdc_fheight;\n 146 | \n 147 | env = getenv(\"PDC_COLS\");\n 148 | pdc_swidth = (env ? atoi(env) : 80) * pdc_fwidth;\n 149 | \n 150 | pdc_screen = SDL_SetVideoMode(pdc_swidth, pdc_sheight, 0,\n 151 | SDL_SWSURFACE|SDL_ANYFORMAT|SDL_RESIZABLE);","lines":9,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/sdl1/pdcscrn.c#L143-L151"}]},
{"id":"OTHER_2","type":"other","type_label":"Other","severity":"high","difficulty":"very_easy","description":"`init_pair()` in colors.c checks `index == 0 || index > 255` but does NOT check\nfor negative values. A `short` argument can be negative (e.g. -1), which passes\nthe guard and is then used as an array index into `color_pairs[256]`, producing\nan out-of-bounds write before the array. This is an arbitrary memory corruption\nvulnerability for any caller that passes a negative color pair index.","fix":"Change the guard to: `if (index <= 0 || index > 255) return ERR;` (note <=0 to also keep the existing rejection of index==0).","locations":[{"ref":"payloads/libpayload/curses/colors.c:45-48","code":" 45 | \tif (index == 0 || index > 255)\n 46 | \t\treturn ERR;\n 47 | \n 48 | \tcolor_pairs[index] = ((bg & 0xF) << 4) | (fg & 0xF);","lines":4,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/colors.c#L45-L48"}]},
{"id":"OTHER_3","type":"other","type_label":"Other","severity":"high","difficulty":"medium","description":"lookup_cmos_enum_core() calls strnlen(text, CB_CMOS_MAX_TEXT_LENGTH) unconditionally at line 240, but text is documented as optionally NULL (the comment says 'Either value or text must be NULL'). When called via lookup_cmos_enum_by_value(), text is NULL. Passing NULL to strnlen() is undefined behavior and will fault on most implementations.","fix":"Guard the strnlen call: int len = text ? strnlen(text, CB_CMOS_MAX_TEXT_LENGTH) : 0; The subsequent memcmp is already guarded by (text == NULL) so no other change is needed.","locations":[{"ref":"payloads/libpayload/drivers/options.c:238-254","code":" 238 | static struct cb_cmos_enums *lookup_cmos_enum_core(struct cb_cmos_option_table *option_table, int config_id, const u8 *value, const char *text)\n 239 | {\n 240 | \tint len = strnlen(text, CB_CMOS_MAX_TEXT_LENGTH);\n 241 | \n 242 | \t/* CMOS enums are located after CMOS entries. */\n 243 | \tstruct cb_cmos_enums *cmos_enum;\n 244 | \tfor ( cmos_enum = first_cmos_enum_of_id(option_table, config_id);\n 245 | \t\tcmos_enum;\n 246 | \t\tcmos_enum = next_cmos_enum_of_id(cmos_enum, config_id)) {\n 247 | \t\tif (((value == NULL) || (cmos_enum->value == *value)) &&\n 248 | \t\t ((text == NULL) || (memcmp((const char*)cmos_enum->text, text, len) == 0))) {\n 249 | \t\t\treturn cmos_enum;\n 250 | \t\t}\n 251 | \t}\n 252 | \n 253 | \treturn NULL;\n 254 | }","lines":17,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/options.c#L238-L254"}]},
{"id":"OTHER_4","type":"other","type_label":"Other","severity":"high","difficulty":"medium","description":"In get_option_as_string(), after lookup_cmos_enum_by_value() is called for config type 'e', the return value cmos_enum is never checked for NULL before dereferencing cmos_enum->text on the next line. If no enum entry matches the stored value, this is a null pointer dereference.","fix":"Add a null check: if (!cmos_enum) { ret = 1; break; } before the strdup call.","locations":[{"ref":"payloads/libpayload/drivers/options.c:337-338","code":" 337 | \t\t\tcmos_enum = lookup_cmos_enum_by_value(option_table, cmos_entry->config_id, (u8*)raw);\n 338 | \t\t\t*dest = strdup((const char*)cmos_enum->text);","lines":2,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/options.c#L337-L338"}]},
{"id":"OTHER_5","type":"other","type_label":"Other","severity":"high","difficulty":"medium","description":"In set_option_from_string(), for config type 'e', lookup_cmos_enum_by_label() return value is not checked for NULL before cmos_enum->value is dereferenced on line 371. If the label does not exist, this is a null pointer dereference. Additionally, malloc(sizeof(u32)) on line 370 is not checked for NULL before the dereference.","fix":"Check cmos_enum for NULL after the lookup and return an error. Also check that malloc returned non-NULL before dereferencing raw.","locations":[{"ref":"payloads/libpayload/drivers/options.c:369-371","code":" 369 | \t\t\tcmos_enum = lookup_cmos_enum_by_label(option_table, cmos_entry->config_id, value);\n 370 | \t\t\traw = malloc(sizeof(u32));\n 371 | \t\t\t*(u32*)raw = cmos_enum->value;","lines":3,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/drivers/options.c#L369-L371"}]},
{"id":"OTHER_6","type":"other","type_label":"Other","severity":"medium","difficulty":"medium","description":"In boot_media.c, boot_device_read() checks that boot_media_size - offset <= 16 MiB (to restrict reads to the top 16MB of flash mapping), but does not validate that offset + size <= boot_media_size. A caller providing a large size value causes memcpy to read beyond the end of the flash memory region, potentially exposing adjacent memory contents.","fix":"Add a bounds check before the memcpy: if (size > lib_sysinfo.boot_media_size - offset) return CB_ERR_ARG;","locations":[{"ref":"payloads/libpayload/arch/x86/boot_media.c:6-13","code":" 6 | __attribute__((weak)) ssize_t boot_device_read(void *buf, size_t offset, size_t size)\n 7 | {\n 8 | \t/* Memory-mapping usually only works for the top 16MB. */\n 9 | \tif (!lib_sysinfo.boot_media_size || lib_sysinfo.boot_media_size - offset > 16 * MiB)\n 10 | \t\treturn CB_ERR_ARG;\n 11 | \tconst void *const ptr = phys_to_virt(0 - lib_sysinfo.boot_media_size + offset);\n 12 | \tmemcpy(buf, ptr, size);\n 13 | \treturn size;","lines":8,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/arch/x86/boot_media.c#L6-L13"}]},
{"id":"OTHER_7","type":"other","type_label":"Other","severity":"medium","difficulty":"medium","description":"In dos/pdcclip.c PDC_freeclipboard(), the function writes the string \"PDCURSES\" into the\nbuffer being freed, then calls free() on it. Writing to memory that is about to be freed is\nundefined behavior and the write happens before free() so the scrubbing is pointless. Worse,\nthe length check `strlen(contents) >= strlen(\"PDCURSES\")` means shorter clipboard contents\nare freed without any scrubbing at all. The comment acknowledges the caller's pointer is not\nnulled, making a use-after-free trivial for any caller that reuses the pointer.","fix":"Remove the write-before-free pattern. To guard against accidental reuse, use memset(contents, 0, strlen(contents)) before free(), or document that callers must set their pointer to NULL after calling PDC_freeclipboard.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/dos/pdcclip.c:92-116","code":" 92 | int PDC_freeclipboard(char *contents)\n 93 | {\n 94 | PDC_LOG((\"PDC_freeclipboard() - called\\n\"));\n 95 | \n 96 | /* should we also free empty the system clipboard? probably not */\n 97 | \n 98 | if (contents)\n 99 | {\n 100 | \n 101 | /* NOTE: We free the memory, but we can not set caller's pointer\n 102 | to NULL, so if caller calls again then will try to access\n 103 | free'd memory. We 1st overwrite memory with a string so if\n 104 | caller tries to use free memory they won't get what they\n 105 | expect & hopefully notice. */\n 106 | \n 107 | /* memset(contents, 0xFD, strlen(contents)); */\n 108 | \n 109 | if (strlen(contents) >= strlen(\"PDCURSES\"))\n 110 | strcpy(contents, \"PDCURSES\");\n 111 | \n 112 | free(contents);\n 113 | }\n 114 | \n 115 | return PDC_CLIP_SUCCESS;\n 116 | }","lines":25,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/dos/pdcclip.c#L92-L116"}]},
{"id":"OTHER_8","type":"other","type_label":"Other","severity":"medium","difficulty":"medium","description":"getwin() in scr_dump.c reads a raw WINDOW struct from a file with fread() and then uses the embedded _maxy and _maxx fields directly as allocation sizes without any validation. A crafted dump file can set these to negative values (they are signed int), causing malloc() calls like 'malloc(nlines * sizeof(chtype *))' to receive a very large size (due to sign extension on most platforms), likely failing and returning NULL (which is handled), but also to zero (malloc(0) implementation-defined) or to an apparently valid small size that causes a write overflow later in the fread loop.","fix":"After reading the WINDOW struct, validate that nlines and ncols are positive and within reasonable bounds (e.g., 1 <= nlines <= LINES_MAX and 1 <= ncols <= COLS_MAX) before using them as malloc sizes.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/scr_dump.c:91-158","code":" 91 | WINDOW *getwin(FILE *filep)\n 92 | {\n 93 | WINDOW *win;\n 94 | char marker[4];\n 95 | int i, nlines, ncols;\n 96 | \n 97 | PDC_LOG((\"getwin() - called\\n\"));\n 98 | \n 99 | if ( !(win = malloc(sizeof(WINDOW))) )\n 100 | return (WINDOW *)NULL;\n 101 | \n 102 | /* check for the marker, and load the WINDOW struct */\n 103 | \n 104 | if (!filep || !fread(marker, 4, 1, filep) || strncmp(marker, \"PDC\", 3)\n 105 | || marker[3] != DUMPVER || !fread(win, sizeof(WINDOW), 1, filep))\n 106 | {\n 107 | free(win);\n 108 | return (WINDOW *)NULL;\n 109 | }\n 110 | \n 111 | nlines = win->_maxy;\n 112 | ncols = win->_maxx;\n 113 | \n 114 | /* allocate the line pointer array */\n 115 | \n 116 | if ( !(win->_y = malloc(nlines * sizeof(chtype *))) )\n 117 | {\n 118 | free(win);\n 119 | return (WINDOW *)NULL;\n 120 | }\n 121 | \n 122 | /* allocate the minchng and maxchng arrays */\n 123 | \n 124 | if ( !(win->_firstch = malloc(nlines * sizeof(int))) )\n 125 | {\n 126 | free(win->_y);\n 127 | free(win);\n 128 | return (WINDOW *)NULL;\n 129 | }\n 130 | \n 131 | if ( !(win->_lastch = malloc(nlines * sizeof(int))) )\n 132 | {\n 133 | free(win->_firstch);\n 134 | free(win->_y);\n 135 | free(win);\n 136 | return (WINDOW *)NULL;\n 137 | }\n 138 | \n 139 | /* allocate the lines */\n 140 | \n 141 | if ( !(win = PDC_makelines(win)) )\n 142 | return (WINDOW *)NULL;\n 143 | \n 144 | /* read them */\n 145 | \n 146 | for (i = 0; i < nlines; i++)\n 147 | {\n 148 | if (!fread(win->_y[i], ncols * sizeof(chtype), 1, filep))\n 149 | {\n 150 | delwin(win);\n 151 | return (WINDOW *)NULL;\n 152 | }\n 153 | }\n 154 | \n 155 | touchwin(win);\n 156 | \n 157 | return win;\n 158 | }","lines":68,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/scr_dump.c#L91-L158"}]},
{"id":"OTHER_9","type":"other","type_label":"Other","severity":"medium","difficulty":"medium","description":"In the wide-character branch of form_driver (USE_WIDEC_SUPPORT), character validation via Check_Char is skipped entirely. The comment at line 4348 acknowledges this as a known gap ('FIXME: the wide-character branch should also use Check_Char()'). As a result, any non-control wide character is inserted without field-type validation, bypassing application-defined character filters. In a firmware context where form fields may guard configuration values, this allows invalid data to be entered.","fix":"Apply Check_Char validation in the wide-character path before calling Data_Entry, analogous to the narrow-character path at lines 4353-4355.","locations":[{"ref":"payloads/libpayload/curses/form/frm_driver.c:4340-4357","code":" 4340 | else if (!(c & (~(int)MAX_REGULAR_CHARACTER)))\n 4341 | {\n 4342 | /*\n 4343 | * If we're using 8-bit characters, iscntrl+isprint cover the whole set.\n 4344 | * But with multibyte characters, there is a third possibility, i.e.,\n 4345 | * parts of characters that build up into printable characters which are\n 4346 | * not considered printable.\n 4347 | *\n 4348 | * FIXME: the wide-character branch should also use Check_Char().\n 4349 | */\n 4350 | #if USE_WIDEC_SUPPORT\n 4351 | if (!iscntrl(UChar(c)))\n 4352 | #else\n 4353 | if (isprint(UChar(c)) &&\n 4354 | \t Check_Char(form, form->current, form->current->type, c,\n 4355 | \t\t (TypeArgument *)(form->current->arg)))\n 4356 | #endif\n 4357 | \tres = Data_Entry(form, c);","lines":18,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/form/frm_driver.c#L4340-L4357"}]},
{"id":"OTHER_10","type":"other","type_label":"Other","severity":"low","difficulty":"medium","description":"In Documentation/conf.py, subprocess.check_output(('git', 'describe')) is called without error handling. If git is not available, the working directory is not a git repository, or there are no tags (CalledProcessError), the Sphinx build fails with an uncaught exception and an unhelpful Python traceback instead of a useful error message. The decode().split() on line 17 also assumes UTF-8 output but does not handle potential codec errors.","fix":"Wrap the subprocess call in a try/except: try: release = subprocess.check_output(('git', 'describe'), stderr=subprocess.DEVNULL).decode('utf-8', errors='replace').strip() except Exception: release = 'unknown'","locations":[{"ref":"Documentation/conf.py:15-17","code":" 15 | release = subprocess.check_output(('git', 'describe')).decode(\"utf-8\")\n 16 | # The short X.Y version.\n 17 | version = release.split(\"-\")[0]","lines":3,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/Documentation/conf.py#L15-L17"}]},
{"id":"OTHER_11","type":"other","type_label":"Other","severity":"low","difficulty":"medium","description":"In apic_sw_disable(), a debug printf statement was left in production code: printf(\"%s: writing %#x to %#x\\n\", __func__, reg, APIC_SPURIOUS). This prints diagnostic output to the console every time the APIC software-disable path is taken (once during apic_init), cluttering the firmware output. No equivalent debug print exists in the symmetric apic_sw_enable() or apic_setup_spurious() functions.","fix":"Remove the printf call from apic_sw_disable().","locations":[{"ref":"payloads/libpayload/arch/x86/apic.c:271","code":" 271 | \tprintf(\"%s: writing %#x to %#x\\n\", __func__, reg, APIC_SPURIOUS);","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/arch/x86/apic.c#L271"}]},
{"id":"OTHER_12","type":"other","type_label":"Other","severity":"low","difficulty":"medium","description":"PDC_setclipboard() allocates 'malloc(length + 1)' bytes based on the caller-supplied 'length' parameter, but then copies the string with 'strcpy(pdc_SDL_clipboard, contents)', which copies until the null terminator in 'contents'. If the caller passes a 'length' value smaller than 'strlen(contents)', the strcpy writes beyond the allocated buffer. This is an API misuse hazard: the contract says length is the byte count of contents, but there is no enforcement.","fix":"Replace strcpy with 'memcpy(pdc_SDL_clipboard, contents, length); pdc_SDL_clipboard[length] = 0;' to honor the length parameter and prevent overflow when the caller passes a length smaller than the string.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/sdl1/pdcclip.c:71-89","code":" 71 | int PDC_setclipboard(const char *contents, long length)\n 72 | {\n 73 | PDC_LOG((\"PDC_setclipboard() - called\\n\"));\n 74 | \n 75 | if (pdc_SDL_clipboard)\n 76 | {\n 77 | free(pdc_SDL_clipboard);\n 78 | pdc_SDL_clipboard = NULL;\n 79 | }\n 80 | \n 81 | if (contents)\n 82 | {\n 83 | if ((pdc_SDL_clipboard = malloc(length + 1)) == NULL)\n 84 | return PDC_CLIP_MEMORY_ERROR;\n 85 | \n 86 | strcpy(pdc_SDL_clipboard, contents);\n 87 | }\n 88 | \n 89 | return PDC_CLIP_SUCCESS;","lines":19,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/sdl1/pdcclip.c#L71-L89"}]},
{"id":"OTHER_13","type":"other","type_label":"Other","severity":"low","difficulty":"medium","description":"In the GDB stub, the `m` (memory read) command does `copy_to_hex(out_buffer, (void *)addr, length)` where `length` is parsed directly from the remote GDB packet via `parse_ulong`. `copy_to_hex` writes `2 * length + 1` bytes into the fixed `static char out_buffer[BUFMAX]` (`BUFMAX == 400`), so any request with `length > 199` overflows `out_buffer` into adjacent globals. `parse_ulong` performs no bound check. This is behind `CONFIG(GDB_STUB)` (debug-only, trusted debugger), so severity is low, but it is an unbounded global-buffer write.","fix":"Clamp the requested length before converting, e.g. reject or truncate when `length > (BUFMAX - 1) / 2`, returning an `E01`-style error packet for oversized reads.","locations":[{"ref":"src/arch/x86/exception.c:427-436","code":" 427 | \t\tcase 'm':\n 428 | \t\t\t/* mAA..AA,LLLL Read LLLL bytes at address AA..AA */\n 429 | \t\t\tptr = &in_buffer[1];\n 430 | \t\t\tif (parse_ulong(&ptr, &addr) &&\n 431 | \t\t\t\t(*ptr++ == ',') &&\n 432 | \t\t\t\tparse_ulong(&ptr, &length)) {\n 433 | \t\t\t\tcopy_to_hex(out_buffer, (void *)addr, length);\n 434 | \t\t\t} else\n 435 | \t\t\t\tmemcpy(out_buffer, \"E01\", 4);\n 436 | \t\t\tbreak;","lines":10,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/src/arch/x86/exception.c#L427-L436"}]},
{"id":"OTHER_14","type":"other","type_label":"Other","severity":"negligible","difficulty":"very_easy","description":"In apic.c, the spurious interrupt handler function name has a typo: 'suprious_interrupt_handler' should be 'spurious_interrupt_handler'. The function is registered with set_interrupt_handler and the typo is consistent so it doesn't break functionality, but it reduces code readability.","fix":"Rename suprious_interrupt_handler to spurious_interrupt_handler in both the definition and the set_interrupt_handler call.","locations":[{"ref":"payloads/libpayload/arch/x86/apic.c:162","code":" 162 | static void suprious_interrupt_handler(u8 vector) {}","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/arch/x86/apic.c#L162"},{"ref":"payloads/libpayload/arch/x86/apic.c:327","code":" 327 | \t\t\t &suprious_interrupt_handler);","lines":1,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/arch/x86/apic.c#L327"}]},
{"id":"OTHER_15","type":"other","type_label":"Other","severity":"negligible","difficulty":"very_easy","description":"PDC_makelines() passes uninitialized local variables 'nlines' and 'ncols' to PDC_LOG at line 194, before they are assigned from win->_maxy and win->_maxx at lines 199-200. When PDC_LOG is compiled as a no-op this is harmless, but with logging enabled it invokes undefined behavior by reading uninitialised integers.","fix":"Move the PDC_LOG call to after the assignments of nlines and ncols (after lines 199-200), or change it to read from 'win->_maxy' and 'win->_maxx' directly.","locations":[{"ref":"payloads/libpayload/curses/PDCurses/pdcurses/window.c:190-200","code":" 190 | WINDOW *PDC_makelines(WINDOW *win)\n 191 | {\n 192 | int i, j, nlines, ncols;\n 193 | \n 194 | PDC_LOG((\"PDC_makelines() - called: lines %d cols %d\\n\", nlines, ncols));\n 195 | \n 196 | if (!win)\n 197 | return (WINDOW *)NULL;\n 198 | \n 199 | nlines = win->_maxy;\n 200 | ncols = win->_maxx;","lines":11,"link":"https://github.com/coreboot/coreboot/blob/85915142b1a96564145160d28766c3bd3e2ca5a0/payloads/libpayload/curses/PDCurses/pdcurses/window.c#L190-L200"}]}
]</script>
<script type="application/json" id="PI">{"name":"coreboot","git_url":"https://github.com/coreboot/coreboot.git","repo_url":"https://github.com/coreboot/coreboot","commit":"85915142b1a96564145160d28766c3bd3e2ca5a0","commit_short":"85915142b1","commit_url":"https://github.com/coreboot/coreboot/commit/85915142b1a96564145160d28766c3bd3e2ca5a0","branch":"main","generated":"2026-09-09","description":"Read-only mirror of https://review.coreboot.org/coreboot.git. Synced every hour. We don't handle Pull Requests."}</script>
<div class="app">
<div class="top">
<div>
<h1>Code Audit Report — coreboot</h1>
<div class="meta">Generated 2026-09-09 — <span id="stats"></span></div>
</div>
<div class="top-right">
<div class="search-wrap">
<input type="search" id="search" placeholder="Search findings…" autocomplete="off">
<button id="search-scope" class="action-btn" type="button" title="Toggle: search everywhere vs. only in file/folder paths">📁 Paths only</button>
</div>
<label class="group-label">Group by
<select id="groupby">
<option value="type">Type</option>
<option value="severity">Severity</option>
<option value="file">File</option>
<option value="none">None (flat)</option>
</select>
</label>
</div>
</div>
<div class="project-info"><div class="pi-name">coreboot</div><div class="pi-desc">Read-only mirror of https://review.coreboot.org/coreboot.git. Synced every hour. We don't handle Pull Requests.</div><div class="pi-row"><a class="pi-repo-btn" href="https://github.com/coreboot/coreboot" target="_blank" rel="noopener">🔗 https://github.com/coreboot/coreboot</a><span><span class="pi-k">branch</span> <code>main</code></span><span><span class="pi-k">commit</span> <a href="https://github.com/coreboot/coreboot/commit/85915142b1a96564145160d28766c3bd3e2ca5a0" target="_blank" rel="noopener"><code>85915142b1</code></a></span></div></div>
<div class="filterbar" id="filterbar"></div>
<div id="progress-wrap">
<div id="progress-bar"><div id="progress-fill"></div></div>
<span id="progress-text"></span>
<button class="action-btn" data-action="collapse-all">⊟ Collapse all</button>
<button class="action-btn" data-action="expand-all">⊞ Expand all</button>
<button class="action-btn" data-action="download-md">⬇ MD</button>
<button class="action-btn" data-action="download-md-short">⬇ MD Short</button>
<button class="action-btn" data-action="download-toml">⬇ TOML</button>
</div>
<div id="hidden-bar"></div>
<div id="content"></div>
</div>
<script>
(function(){
'use strict';
// Data
const FINDINGS = JSON.parse(document.getElementById('D').textContent);
const PROJECT_INFO = JSON.parse(document.getElementById('PI').textContent);
const SEVS = ['critical','high','medium','low','negligible'];
const STATUSES = ['open','fixed','wont-fix','false-positive'];
const ALL_TYPES = [...new Set(FINDINGS.map(f => f.type))].sort();
// Build lookup map for O(1) access
const FINDING_BY_ID = new Map(FINDINGS.map(f => [f.id, f]));
// Only severities actually present in the report — the filter bar hides the rest.
const PRESENT_SEVS = SEVS.filter(s => FINDINGS.some(f => (f.severity || 'medium') === s));
// Whether any finding is flagged easy — the "Quick wins" filter row is hidden otherwise.
const DIFFICULTY_ORDER = ['very_easy','easy','medium','hard'];
const DIFFICULTY_LBL = {very_easy:'⚡ Very Easy', easy:'Easy', medium:'Medium', hard:'Hard'};
const DIFFICULTY_CLS = {very_easy:'diff-very-easy', easy:'diff-easy', medium:'diff-medium', hard:'diff-hard'};
const PRESENT_DIFFS = DIFFICULTY_ORDER.filter(d => FINDINGS.some(f => f.difficulty === d));
const HAS_DIFFICULTY = PRESENT_DIFFS.length > 0;
const STORE_KEY = 'audit-status:' + location.href;
const STATUS_CLS = {open:'st-open', fixed:'st-fixed', 'wont-fix':'st-wont', 'false-positive':'st-fp'};
const STATUS_LBL = {open:'● open', fixed:'✓ fixed', 'wont-fix':"— won't fix", 'false-positive':'✗ false pos.'};
const STATUS_NEXT = {open:'fixed', fixed:'wont-fix', 'wont-fix':'false-positive', 'false-positive':'open'};
// State
const S = {
search: '', searchScope: 'all', groupBy: 'type',
hiddenSev: new Set(), hiddenType: new Set(),
hiddenStatus: new Set(), hiddenDiff: new Set(), hiddenGroup: new Set(),
collapsed: new Set(),
};
const statuses = {};
// Persistence
function loadStatuses() {
try { Object.assign(statuses, JSON.parse(localStorage.getItem(STORE_KEY) || '{}')); } catch {}
}
function saveStatuses() {
try { localStorage.setItem(STORE_KEY, JSON.stringify(statuses)); } catch {}
}
function statusOf(id) { return statuses[id] || 'open'; }
// URL hash — debounced so rapid filter changes don't thrash history
let _hashTimer = 0;
function syncHash() {
clearTimeout(_hashTimer);
_hashTimer = setTimeout(() => {
const p = new URLSearchParams();
if (S.groupBy !== 'type') p.set('g', S.groupBy);
if (S.hiddenSev.size) p.set('hs', [...S.hiddenSev].join(','));
if (S.hiddenType.size) p.set('ht', [...S.hiddenType].join(','));
if (S.hiddenStatus.size) p.set('hst', [...S.hiddenStatus].join(','));
if (S.hiddenDiff.size) p.set('hd', [...S.hiddenDiff].join(','));
if (S.search) p.set('q', S.search);
if (S.searchScope !== 'all') p.set('qs', S.searchScope);
const str = p.toString();
history.replaceState(null, '', str ? '#' + str : location.pathname + location.search);
}, 300);
}
function loadHash() {
const p = new URLSearchParams(location.hash.slice(1));
if (p.has('g')) { S.groupBy = p.get('g'); document.getElementById('groupby').value = S.groupBy; }
if (p.has('q')) { S.search = p.get('q'); document.getElementById('search').value = S.search; }
if (p.has('qs')) { S.searchScope = p.get('qs'); }
document.getElementById('search-scope').classList.toggle('active', S.searchScope === 'locations');
if (p.has('hs')) p.get('hs') .split(',').filter(Boolean).forEach(v => S.hiddenSev.add(v));
if (p.has('ht')) p.get('ht') .split(',').filter(Boolean).forEach(v => S.hiddenType.add(v));
if (p.has('hst')) p.get('hst').split(',').filter(Boolean).forEach(v => S.hiddenStatus.add(v));
if (p.has('hd')) p.get('hd') .split(',').filter(Boolean).forEach(v => S.hiddenDiff.add(v));
}
// Helpers
function esc(s) {
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
}
function highlight(text, q) {
if (!q) return esc(text);
const re = new RegExp('(' + q.replace(/[.*+?^${}()|[\]\\]/g,'\\$&') + ')', 'gi');
return String(text).split(re).map((p, i) => i % 2 ? '<mark>' + esc(p) + '</mark>' : esc(p)).join('');
}
// Render text with ```fenced``` code blocks and `inline` backticks,
// also paragraph-splitting on blank lines. Output is HTML-safe.
function renderMd(text, q) {
const src = String(text == null ? '' : text);
const FENCE = /```[ \t]*([A-Za-z0-9_+-]*)[ \t]*\r?\n([\s\S]*?)```/g;
const parts = [];
let last = 0, m;
while ((m = FENCE.exec(src)) !== null) {
parts.push({kind: 'text', value: src.slice(last, m.index)});
parts.push({kind: 'code', value: m[2].replace(/\r?\n$/, '')});
last = m.index + m[0].length;
}
parts.push({kind: 'text', value: src.slice(last)});
function renderInline(s) {
const re = /`([^`\n]+)`/g;
let out = '', i = 0, mm;
while ((mm = re.exec(s)) !== null) {
out += highlight(s.slice(i, mm.index), q);
out += '<code class="md-inline">' + highlight(mm[1], q) + '</code>';
i = mm.index + mm[0].length;
}
out += highlight(s.slice(i), q);
return out;
}
function renderTextChunk(s) {
const paragraphs = s.split(/\r?\n[ \t]*\r?\n/);
const out = [];
for (const p of paragraphs) {
const trimmed = p.replace(/^\s+|\s+$/g, '');
if (!trimmed) continue;
out.push('<p>' + renderInline(trimmed).replace(/\r?\n/g, '<br>') + '</p>');
}
return out.join('');
}
let html = '';
for (const p of parts) {
if (p.kind === 'text') html += renderTextChunk(p.value);
else html += '<pre class="md-code">' + highlight(p.value, q) + '</pre>';
}
return html;
}
function sevCls(s) { return SEVS.includes(s) ? 'sev-'+s : 'sev-negligible'; }
function sevLbl(s) { return s ? s[0].toUpperCase()+s.slice(1) : 'Medium'; }
function typeLabel(t) { return t.replace(/_/g,' ').replace(/\b\w/g, c => c.toUpperCase()); }
// First sentence of text (up to and including the first period).
function firstSentence(text) {
const m = String(text).match(/\.(\s|$)/);
return m ? text.slice(0, text.indexOf(m[0]) + 1) : text;
}
// Render a "**Label:** value" line. GitHub/CommonMark only recognizes a
// ``` fence when it starts at the beginning of a line, so if the value
// itself opens with a fence, put it on its own line instead of right
// after the inline label - otherwise the fence marker gets glued onto
// "**Label:** ```" and never renders as a code block.
function mdField(label, value) {
value = value || '';
return /^```/.test(value.trim())
? '**' + label + ':**\n\n' + value
: '**' + label + ':** ' + value;
}
// Render the "**Locations:**" block. When a location has a commit-pinned
// permalink (GitHub/GitLab), just paste the bare URL on its own line -
// GitHub/GitLab auto-expand a line-range blob link into an inline code
// preview, so re-pasting the snippet as a fenced block would duplicate it.
// Forges without a detected permalink shape (or missing commit/repo info)
// fall back to the fenced ref+snippet so the location is still readable.
function mdLocations(locations) {
if (!locations.length) return '';
let out = '**Locations:**\n\n';
for (const l of locations) {
if (l.link) out += l.link + '\n\n';
else if (l.code != null) out += '```\n' + l.ref + '\n' + l.code + '\n```\n\n';
else out += '- `' + l.ref + '` _(file not found)_\n\n';
}
return out;
}
// Build full MD report for in-browser download.
function buildMdFull() {
const SORD = ['critical','high','medium','low','negligible'];
const today = new Date().toISOString().slice(0, 10);
let out = '# Code Audit Report\n\n_Generated ' + today + '_\n\n';
const typeMap = new Map();
for (const f of FINDINGS) {
if (!typeMap.has(f.type)) typeMap.set(f.type, []);
typeMap.get(f.type).push(f);
}
const sortedTypes = [...typeMap.keys()].sort();
out += '## Summary\n\n| Type | Severity | Count |\n|------|----------|-------|\n';
for (const t of sortedTypes) {
const items = typeMap.get(t);
const topSev = items.map(f => f.severity || 'medium').reduce((best, s) => {
const bi = SORD.indexOf(best), si = SORD.indexOf(s);
return (si >= 0 && (bi < 0 || si < bi)) ? s : best;
}, 'negligible');
out += '| ' + typeLabel(t) + ' | ' + topSev + ' | ' + items.length + ' |\n';
}
for (const t of sortedTypes) {
out += '\n## ' + typeLabel(t) + '\n\n';
for (const f of typeMap.get(t)) {
const sevStr = f.severity ? ' `' + f.severity.toUpperCase() + '`' : '';
out += '### ' + f.id + sevStr + '\n\n';
out += mdField('Description', f.description) + '\n\n';
out += mdLocations(f.locations);
out += mdField('Fix', f.fix) + '\n\n---\n\n';
}
}
return out;
}
// Build MD short checklist for in-browser download.
function buildMdShort() {
const lines = [];
const ids = [];
const sorted = [...FINDINGS].sort((a, b) => a.type.localeCompare(b.type));
for (const f of sorted) {
ids.push(f.id);
const desc = firstSentence(f.description || '');
const locs = f.locations.map(l => l.ref);
const locStr = locs.length ? ' - [' + locs.join(', ') + ']' : '';
const sevStr = f.severity ? ' [' + f.severity.toUpperCase() + ']' : '';
lines.push('- [ ] - ' + f.id + sevStr + ' - ' + desc + locStr);
}
lines.push('', '## IDs only', '');
ids.forEach(id => lines.push('- [ ] - ' + id));
return lines.join('\n') + '\n';
}
function downloadText(text, filename, mime) {
const a = document.createElement('a');
a.href = URL.createObjectURL(new Blob([text], {type: mime || 'text/plain'}));
a.download = filename;
a.click();
setTimeout(() => URL.revokeObjectURL(a.href), 1000);
}
function tomlStr(s) {
return '"' + String(s == null ? '' : s)
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t') + '"';
}
// Build a [project] header block from embedded PROJECT_INFO, so the exported
// findings.toml is self-contained (doesn't depend on a sibling project-info.toml
// that a second, separately-triggered download could silently fail to save).
// '' when there is no project info at all.
function buildProjectToml() {
const ORDER = ['name','description','git_url','repo_url','commit','commit_short','commit_url','branch','generated'];
const pi = PROJECT_INFO || {};
if (!Object.keys(pi).length) return '';
const lines = ['[project]'];
for (const k of ORDER) {
if (k in pi) lines.push(k + ' = ' + tomlStr(pi[k]));
}
return lines.join('\n') + '\n\n';
}
// Build TOML matching the original findings.toml format (re-parseable by toml_to_report.py).
function buildFindingsToml() {
function pad(key) { return (key + ' ').slice(0, 12); }
const chunks = [];
for (const f of FINDINGS) {
const lns = ['[[finding]]'];
lns.push(pad('type') + '= ' + tomlStr(f.type));
lns.push(pad('severity') + '= ' + tomlStr(f.severity || 'medium'));
if (f.difficulty) lns.push(pad('difficulty') + '= ' + tomlStr(f.difficulty));
lns.push(pad('description') + '= ' + tomlStr(f.description || ''));
const refs = f.locations.map(l => tomlStr(l.ref));
lns.push(pad('locations') + '= [' + refs.join(', ') + ']');
lns.push(pad('fix') + '= ' + tomlStr(f.fix || ''));
chunks.push(lns.join('\n'));
}
return buildProjectToml() + chunks.join('\n\n') + '\n';
}
// Build the GitHub-ready Markdown for one finding (mirrors findings.md).
function findingToMd(f) {
let out = '### ' + f.id + (f.severity ? ' `' + f.severity.toUpperCase() + '`' : '') + '\n\n';
out += mdField('Description', f.description) + '\n\n';
out += mdLocations(f.locations);
out += mdField('Fix', f.fix) + '\n';
return out;
}
function flashBtn(btn, msg) {
btn.textContent = msg;
setTimeout(() => { btn.textContent = '⧉ Copy'; }, 1200);
}
function copyText(text, btn) {
const ok = () => flashBtn(btn, '✓ Copied');
const no = () => flashBtn(btn, '✗ Failed');
const fallback = () => {
try {
const ta = document.createElement('textarea');
ta.value = text; ta.style.position = 'fixed'; ta.style.top = '-9999px';
document.body.appendChild(ta); ta.select();
const r = document.execCommand('copy');
document.body.removeChild(ta);
r ? ok() : no();
} catch { no(); }
};
if (navigator.clipboard && navigator.clipboard.writeText)
navigator.clipboard.writeText(text).then(ok, fallback);
else fallback();
}
// Filter logic
function getKey(f) {
if (S.groupBy === 'severity') return f.severity || 'medium';
if (S.groupBy === 'file') return f.locations.length ? f.locations[0].ref.replace(/:.*/,'') : '(no location)';
if (S.groupBy === 'none') return '';
return f.type_label;
}
// Pre-compute search haystacks per finding (cleared on full re-render).
// Two variants: everything (default), and file/folder paths only - for when a
// search term (e.g. a directory name like 'dlls/abc') also happens to appear
// inside description/fix prose and you only want path matches.
const _hay = new Map();
const _hayLoc = new Map();
function hayOf(f) {
if (!_hay.has(f.id)) {
_hay.set(f.id, [f.id, f.type_label, f.severity, f.description, f.fix,
f.difficulty ? (DIFFICULTY_LBL[f.difficulty] || f.difficulty) : '',
...f.locations.map(l => l.ref)].join(' ').toLowerCase());
}
return _hay.get(f.id);
}
function hayLocOf(f) {
if (!_hayLoc.has(f.id)) {
_hayLoc.set(f.id, f.locations.map(l => l.ref).join(' ').toLowerCase());
}
return _hayLoc.get(f.id);
}
function matches(f) {
if (S.hiddenDiff.size > 0 && f.difficulty && S.hiddenDiff.has(f.difficulty)) return false;
if (S.hiddenSev.has(f.severity || 'medium')) return false;
if (S.hiddenType.has(f.type)) return false;
const st = statusOf(f.id);
if (S.hiddenStatus.has(st)) return false;
if (S.search) {
const q = S.search.toLowerCase();
if (S.searchScope === 'locations') {
if (!hayLocOf(f).includes(q)) return false;
} else if (!hayOf(f).includes(q) && !st.includes(q)) {
return false;
}
}
return true;
}
function sortKeys(keys) {
if (S.groupBy === 'severity') {
return [...keys].sort((a, b) => {
const ai = SEVS.indexOf(a), bi = SEVS.indexOf(b);
return (ai < 0 ? 99 : ai) - (bi < 0 ? 99 : bi);
});
}
return [...keys].sort();
}
// Build body HTML for an expanded card (called lazily on first expand).
function renderCardBody(f, q) {
let locs = '';
for (const l of f.locations) {
const lineInfo = l.lines != null
? ' <span class="loc-lines">(' + l.lines + ' line' + (l.lines === 1 ? '' : 's') + ')</span>'
: '';
const refHtml = l.link
? '<a class="loc-link" href="' + esc(l.link) + '" target="_blank" rel="noopener" title="Open at this commit">' + highlight(l.ref, q) + ' ↗</a>'
: highlight(l.ref, q);
locs += '<div class="loc-block"><div class="loc-ref">' + refHtml + lineInfo + '</div>';
locs += l.code != null ? '<pre>' + esc(l.code) + '</pre>' : '<div class="not-found">(file not found)</div>';
locs += '</div>';
}
return (
'<div class="field-lbl">Description</div>' +
'<div class="desc">' + renderMd(f.description, q) + '</div>' +
(locs ? '<div class="field-lbl">Locations</div>' + locs : '') +
'<div class="field-lbl">Fix</div>' +
'<div class="fix-txt">' + renderMd(f.fix, q) + '</div>'
);
}
// Render card shell only — header + empty or populated body.
// When collapsed the body is empty (display:none) — avoids renderMd for 1800+ cards.
function renderCard(f, q) {
const sc = sevCls(f.severity || 'medium');
const sl = sevLbl(f.severity || 'medium');
const st = statusOf(f.id);
const sc2 = STATUS_CLS[st] || 'st-open';
const sl2 = STATUS_LBL[st] || '● open';
const isCollapsed = S.collapsed.has(f.id);
const isResolved = st !== 'open';
const locSummary = f.locations.length
? f.locations[0].ref + (f.locations.length > 1 ? ' \u2026' : '')
: '';
const bodyInner = isCollapsed ? '' : renderCardBody(f, q);
return (
'<div class="card' + (isResolved ? ' resolved' : '') + '" data-card-id="' + esc(f.id) + '">' +
'<div class="card-header" data-action="toggle-card" data-id="' + esc(f.id) + '">' +
'<span class="card-arrow">' + (isCollapsed ? '\u25b6' : '\u25bc') + '</span>' +
'<span class="card-id">' + esc(f.id) + '</span>' +
'<span class="badge ' + sc + '" style="cursor:default">' + esc(sl) + '</span>' +
'<span class="badge type-badge" style="cursor:default">' + esc(f.type_label) + '</span>' +
(f.difficulty ? '<span class="badge ' + (DIFFICULTY_CLS[f.difficulty]||'') + '" style="cursor:default" title="Difficulty: ' + esc(DIFFICULTY_LBL[f.difficulty]||f.difficulty) + '">' + esc(DIFFICULTY_LBL[f.difficulty]||f.difficulty) + '</span>' : '') +
(locSummary ? '<span class="card-loc" title="' + esc(locSummary) + '">' + esc(locSummary) + '</span>' : '') +
'<button class="status-btn ' + sc2 + '" data-action="cycle-status" data-id="' + esc(f.id) + '">' + sl2 + '</button>' +
'<button class="copy-btn" data-action="copy-md" data-id="' + esc(f.id) + '" title="Copy as Markdown for GitHub">\u29c9 Copy</button>' +
'</div>' +
'<div class="card-body" data-body-id="' + esc(f.id) + '"' + (isCollapsed ? ' style="display:none"' : '') + '>' +
bodyInner +
'</div></div>'
);
}
function initFilterBar() {
const fb = document.getElementById('filterbar');
let h = '<div class="filter-row"><span class="filter-lbl">Severity:</span>';
for (const s of PRESENT_SEVS)
h += '<button class="badge ' + sevCls(s) + '" data-sev="' + s + '">' + sevLbl(s) + '</button>';
h += '<button class="filter-toggle" data-toggle-all="sev"></button></div>';
h += '<div class="filter-row"><span class="filter-lbl">Type:</span>';
for (const t of ALL_TYPES)
h += '<button class="badge type-badge" data-type="' + esc(t) + '">' + esc(typeLabel(t)) + '</button>';
h += '<button class="filter-toggle" data-toggle-all="type"></button></div>';
h += '<div class="filter-row"><span class="filter-lbl">Status:</span>';
for (const st of STATUSES)
h += '<button class="badge ' + (STATUS_CLS[st]||'st-open') + '" data-status="' + st + '">' + (STATUS_LBL[st]||st) + '</button>';
h += '<button class="filter-toggle" data-toggle-all="status"></button></div>';
if (HAS_DIFFICULTY) {
h += '<div class="filter-row"><span class="filter-lbl">Difficulty:</span>';
for (const d of PRESENT_DIFFS)
h += '<button class="badge ' + (DIFFICULTY_CLS[d]||'') + '" data-diff="' + d + '">' + esc(DIFFICULTY_LBL[d]||d) + '</button>';
h += '<button class="filter-toggle" data-toggle-all="diff"></button></div>';
}
fb.innerHTML = h;
}
function updateToggles() {
for (const b of document.querySelectorAll('[data-sev]')) b.classList.toggle('off', S.hiddenSev.has(b.dataset.sev));
for (const b of document.querySelectorAll('[data-type]')) b.classList.toggle('off', S.hiddenType.has(b.dataset.type));
for (const b of document.querySelectorAll('[data-status]')) b.classList.toggle('off', S.hiddenStatus.has(b.dataset.status));
for (const b of document.querySelectorAll('[data-diff]')) b.classList.toggle('off', S.hiddenDiff.has(b.dataset.diff));
for (const b of document.querySelectorAll('[data-toggle-all]')) {
const k = b.dataset.toggleAll;
const set = k === 'sev' ? S.hiddenSev : k === 'type' ? S.hiddenType : k === 'status' ? S.hiddenStatus : k === 'diff' ? S.hiddenDiff : null;
if (set) b.textContent = set.size === 0 ? 'Hide all' : 'Show all';
}
}
function updateProgress() {
const total = FINDINGS.length;
const done = FINDINGS.filter(f => statusOf(f.id) !== 'open').length;
const pct = total ? Math.round(done / total * 100) : 0;
document.getElementById('progress-fill').style.width = pct + '%';
document.getElementById('progress-text').textContent = done + ' / ' + total + ' resolved (' + pct + '%)';
}
// Surgical toggle: flip one card without touching the rest of the DOM.
function toggleCard(id) {
const wasCollapsed = S.collapsed.has(id);
if (wasCollapsed) S.collapsed.delete(id); else S.collapsed.add(id);
const body = document.querySelector('[data-body-id="' + id + '"]');
const arrow = document.querySelector('[data-card-id="' + id + '"] .card-arrow');
if (!body) { render(); return; }
if (wasCollapsed) {
if (!body.firstChild) {
const f = FINDING_BY_ID.get(id);
if (f) body.innerHTML = renderCardBody(f, S.search);
}
body.style.display = '';
if (arrow) arrow.textContent = '\u25bc';
} else {
body.style.display = 'none';
if (arrow) arrow.textContent = '\u25b6';
}
}
// Surgical status update: update one card's button + resolved class.
function cycleStatus(id) {
statuses[id] = STATUS_NEXT[statusOf(id)] || 'open';
saveStatuses();
const st = statusOf(id);
const btn = document.querySelector('.status-btn[data-id="' + id + '"]');
if (!btn) { render(); return; }
btn.className = 'status-btn ' + (STATUS_CLS[st] || 'st-open');
btn.textContent = STATUS_LBL[st] || '\u25cf open';
const card = document.querySelector('[data-card-id="' + id + '"]');
if (card) card.classList.toggle('resolved', st !== 'open');
updateProgress();
}
// Surgical collapse/expand for a group: toggle all bodies in the group without full re-render.
function collapseGroup(key, collapse) {
FINDINGS.forEach(f => {
if (getKey(f) !== key) return;
if (collapse) S.collapsed.add(f.id); else S.collapsed.delete(f.id);
const body = document.querySelector('[data-body-id="' + f.id + '"]');
const arrow = document.querySelector('[data-card-id="' + f.id + '"] .card-arrow');
if (!body) return;
if (collapse) {
body.style.display = 'none';
if (arrow) arrow.textContent = '\u25b6';
} else {
if (!body.firstChild) {
const ff = FINDING_BY_ID.get(f.id);
if (ff) body.innerHTML = renderCardBody(ff, S.search);
}
body.style.display = '';
if (arrow) arrow.textContent = '\u25bc';
}
});
}
// Full re-render: used for filter/group-by/search changes.
// Batched via requestAnimationFrame.
let _rafPending = false;
function scheduleRender() {
if (_rafPending) return;
_rafPending = true;
requestAnimationFrame(() => { _rafPending = false; render(); });
}
function render() {
_hay.clear();
_hayLoc.clear();
updateToggles();
updateProgress();
const q = S.search;
const visible = FINDINGS.filter(matches);
document.getElementById('stats').textContent = visible.length + ' / ' + FINDINGS.length + ' findings';
// Build groups
const map = new Map();
for (const f of visible) {
const k = getKey(f);
if (!map.has(k)) map.set(k, []);
map.get(k).push(f);
}
// Hidden-groups bar
const hbar = document.getElementById('hidden-bar');
if (S.hiddenGroup.size > 0) {
let hh = '<div class="hidden-inner"><span class="hidden-lbl">Hidden:</span>';
for (const g of [...S.hiddenGroup].sort())
hh += '<button class="show-btn" data-action="show-group" data-key="' + esc(g) + '">' + esc(g || '(all findings)') + '</button>';
hh += '<button class="show-all-btn" data-action="show-all">Show all</button></div>';
hbar.innerHTML = hh;
} else {
hbar.innerHTML = '';
}
// Content
const content = document.getElementById('content');
if (!visible.length) {
content.innerHTML = '<div class="no-results">No findings match the current filters.</div>';
syncHash();
return;
}
let hh = '';
for (const k of sortKeys([...map.keys()])) {
if (S.hiddenGroup.has(k)) continue;
const items = map.get(k);
const title = k || 'All Findings';
const done = items.filter(f => statusOf(f.id) !== 'open').length;
const allDone = done === items.length;
hh += '<div class="group-section">' +
'<div class="group-header">' +
'<span class="group-title' + (allDone ? ' all-done' : '') + '">' + esc(title) + '</span>' +
'<span class="group-count">' + done + '/' + items.length + '</span>' +
'<button class="icon-btn" data-action="collapse-group" data-key="' + esc(k) + '" title="Collapse all">\u229f</button>' +
'<button class="icon-btn" data-action="expand-group" data-key="' + esc(k) + '" title="Expand all">\u229e</button>' +
'<button class="hide-btn" data-action="hide-group" data-key="' + esc(k) + '">Hide</button>' +
'</div>';
for (const f of items) hh += renderCard(f, q);
hh += '</div>';
}
content.innerHTML = hh;
syncHash();
}
// Long-press solo: hold a filter badge 450 ms to isolate that value (hide all others in the group).
let _soloTimer = 0;
let _soloFired = false;
function _applySolo(b) {
_soloFired = true;
const sev = b.dataset.sev, typ = b.dataset.type, sta = b.dataset.status, dif = b.dataset.diff;
if (sev) { S.hiddenSev.clear(); PRESENT_SEVS.filter(v => v !== sev).forEach(v => S.hiddenSev.add(v)); }
else if (typ) { S.hiddenType.clear(); ALL_TYPES.filter(v => v !== typ).forEach(v => S.hiddenType.add(v)); }
else if (sta) { S.hiddenStatus.clear(); STATUSES.filter(v => v !== sta).forEach(v => S.hiddenStatus.add(v)); }
else if (dif) { S.hiddenDiff.clear(); PRESENT_DIFFS.filter(v => v !== dif).forEach(v => S.hiddenDiff.add(v)); }
scheduleRender();
}
const _fb = document.getElementById('filterbar');
_fb.addEventListener('pointerdown', e => {
const b = e.target.closest('[data-sev],[data-type],[data-status],[data-diff]');
if (!b) return;
_soloFired = false;
clearTimeout(_soloTimer);
_soloTimer = setTimeout(() => _applySolo(b), 450);
}, {passive: true});
_fb.addEventListener('pointerup', () => clearTimeout(_soloTimer), {passive: true});
_fb.addEventListener('pointercancel', () => clearTimeout(_soloTimer), {passive: true});
_fb.addEventListener('pointermove', e => {
if (!_soloTimer) return;