-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathopenssl-why-not-rust.html
More file actions
897 lines (865 loc) · 89.3 KB
/
Copy pathopenssl-why-not-rust.html
File metadata and controls
897 lines (865 loc) · 89.3 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
<!doctype html>
<html lang="en" data-theme="dark" data-lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>why-not-rust · openssl/openssl</title>
<style>
:root[data-theme="dark"] {
color-scheme: dark;
--page:#0a0a0b; --raised:#111113; --line:#232327; --line-soft:#1a1a1d;
--ink:#f4f4f5; --ink-2:#a8a8b0; --ink-3:#6e6e78;
--blue:#5b9cf0; --teal:#3fbfa0; --rust:#f0803c; --amber:#e0a93c;
--wash:rgba(255,255,255,.028);
}
:root[data-theme="light"] {
color-scheme: light;
--page:#fcfcfb; --raised:#ffffff; --line:#e4e2dd; --line-soft:#efedE8;
--ink:#16161a; --ink-2:#55555f; --ink-3:#8b8b95;
--blue:#1f6fd0; --teal:#0f8f74; --rust:#c85a15; --amber:#9a6b00;
--wash:rgba(0,0,0,.022);
}
:root {
--mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
--sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Inter,Roboto,Helvetica,Arial,sans-serif;
--accent:var(--blue);
--measure:74ch;
}
body.v-stay { --accent:var(--blue); }
body.v-extract { --accent:var(--teal); }
body.v-partial { --accent:var(--amber); }
body.v-migrate { --accent:var(--rust); }
* { box-sizing:border-box; margin:0; padding:0; }
html { background:var(--page); }
body {
background:var(--page); color:var(--ink); font-family:var(--sans);
font-size:16px; line-height:1.62; -webkit-font-smoothing:antialiased;
padding:0 32px 96px;
/* Evidence is full of long file paths, commit SHAs and URLs. Without this
a single unbreakable token pushes the page wider than a phone screen. */
overflow-wrap:break-word;
}
.wrap { max-width:1080px; margin:0 auto; }
.mono { font-family:var(--mono); font-variant-ligatures:none; }
a { color:inherit; }
/* ---------- masthead ---------- */
.mast { display:flex; align-items:center; gap:14px; padding:26px 0 22px; }
.logo { font-size:.9rem; font-weight:600; letter-spacing:-.02em; }
.logo .q { color:var(--accent); }
.mast .meta { margin-left:auto; display:flex; gap:14px; align-items:center; color:var(--ink-3); font-size:.78rem; }
.btn {
font:inherit; font-size:.72rem; color:var(--ink-2); background:none; cursor:pointer;
border:1px solid var(--line); border-radius:6px; padding:3px 9px; line-height:1.5;
}
.btn:hover { color:var(--ink); border-color:var(--ink-3); }
/* ---------- hero ---------- */
.hero { padding:22px 0 40px; border-top:1px solid var(--line); }
.kicker {
font-size:.7rem; letter-spacing:.13em; text-transform:uppercase;
color:var(--ink-3); font-weight:600;
}
.kicker .project { color:var(--ink-2); text-transform:none; letter-spacing:.02em; }
.hero-grid { display:grid; grid-template-columns:minmax(0,1fr) 330px; gap:56px; margin-top:26px; align-items:start; }
.scope-word {
font-size:clamp(3.2rem,8vw,4.6rem); line-height:.94; font-weight:760;
letter-spacing:-.045em; color:var(--accent);
}
.scope-sub { margin-top:12px; font-size:1.02rem; color:var(--ink); font-weight:500; }
.auth {
display:inline-flex; align-items:center; gap:8px; margin-top:20px;
font-size:.74rem; font-weight:650; letter-spacing:.05em; color:var(--ink-2);
}
.auth i { width:7px; height:7px; border-radius:50%; background:var(--ink-3); flex:none; }
body.a-approve .auth i { background:var(--teal); }
body.a-reject .auth i { background:var(--blue); }
body.a-defer .auth i { background:var(--amber); }
.why { margin-top:18px; font-size:1.02rem; line-height:1.66; color:var(--ink); max-width:var(--measure); }
.trigger {
margin-top:16px; padding-left:15px; border-left:2px solid var(--line);
font-size:.87rem; line-height:1.6; color:var(--ink-2); max-width:var(--measure);
}
.chips { display:flex; flex-wrap:wrap; gap:9px 20px; margin-top:26px; font-size:.75rem; color:var(--ink-3); }
.chips b { color:var(--ink-2); font-weight:600; }
.chips .scope b { color:var(--accent); }
/* ---------- gates ---------- */
.gatebox h2 {
font-size:.68rem; letter-spacing:.13em; text-transform:uppercase;
color:var(--ink-3); font-weight:650; padding-bottom:11px; border-bottom:1px solid var(--line);
}
.gate { display:grid; grid-template-columns:26px 1fr; gap:11px; padding:13px 0; border-bottom:1px solid var(--line-soft); align-items:start; }
.gate:last-child { border-bottom:0; }
.gate .id { color:var(--ink-3); font-size:.7rem; padding-top:3px; }
.gate .head { display:flex; align-items:center; gap:8px; }
.gate .name { font-size:.88rem; font-weight:620; }
.gate .ev { color:var(--ink-3); font-size:.78rem; line-height:1.5; margin-top:3px; }
/* state words: a dot plus a word. No filled pills anywhere. */
.state { display:inline-flex; align-items:center; gap:5px; font-size:.66rem; font-weight:700; letter-spacing:.07em; white-space:nowrap; }
.state::before { content:""; width:6px; height:6px; border-radius:50%; background:currentColor; flex:none; }
.state.pass { color:var(--teal); }
.state.fail { color:var(--blue); }
.state.unknown { color:var(--amber); }
/* ---------- magnitude figures ---------- */
.tiles {
/* Six magnitude tiles is the house style, and 150px + a 24px gutter is what
lets all six sit on one row inside the 1080px measure. At 168/34 the sixth
tile orphaned onto a row of its own on every report. */
display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
gap:30px 24px; padding:32px 0; border-top:1px solid var(--line); border-bottom:1px solid var(--line);
}
.tile .label { font-size:.72rem; color:var(--ink-3); line-height:1.4; }
/* Sized so a nine-character value — which is 117 of the 120 tiles in the
gallery — stays on one line in a six-across row. Longer composite values
("16,350–808,625") wrap at a space, which no usable size prevents. */
.tile .value { margin-top:7px; font-size:1.55rem; font-weight:640; letter-spacing:-.035em; line-height:1.12; }
/* The unit is a single token ("lines", "2016–2021"); never break inside it. */
.tile .unit { font-size:.8rem; font-weight:500; color:var(--ink-3); margin-left:5px; letter-spacing:0; white-space:nowrap; }
.tile .note { margin-top:6px; font-size:.71rem; color:var(--ink-3); line-height:1.45; }
/* ---------- sections ---------- */
section { padding-top:58px; }
.section-title { display:flex; align-items:baseline; gap:12px; font-size:1.18rem; font-weight:640; letter-spacing:-.02em; }
.section-title .no { font-family:var(--mono); font-size:.72rem; color:var(--ink-3); font-weight:500; }
.sub { margin-top:9px; font-size:.85rem; color:var(--ink-3); max-width:var(--measure); line-height:1.6; }
/* ---------- options table ---------- */
.table-wrap { overflow-x:auto; margin-top:22px; }
table { width:100%; border-collapse:collapse; font-size:.79rem; min-width:820px; }
th {
text-align:left; font-size:.66rem; letter-spacing:.1em; text-transform:uppercase;
color:var(--ink-3); font-weight:650; padding:0 14px 10px 0; border-bottom:1px solid var(--line);
}
td { padding:15px 14px 15px 0; border-bottom:1px solid var(--line-soft); vertical-align:top; color:var(--ink-2); line-height:1.5; }
tr:last-child td { border-bottom:0; }
tr.selected td { background:var(--wash); }
tr.selected td:first-child { box-shadow:inset 2px 0 0 var(--accent); padding-left:13px; }
tr.excluded td { opacity:.6; }
.option-name { color:var(--ink); font-weight:620; display:block; }
td .source { color:var(--ink-3); font-size:.72rem; margin-top:3px; display:block; line-height:1.45; }
.scope-tag { font-family:var(--mono); font-size:.66rem; letter-spacing:.05em; color:var(--ink-3); }
.ev-tag { font-size:.66rem; font-weight:700; letter-spacing:.06em; }
.ev-tag.strong { color:var(--teal); } .ev-tag.moderate { color:var(--ink-2); }
.ev-tag.weak { color:var(--amber); } .ev-tag.unknown { color:var(--amber); }
/* ---------- twelve-lens ledger ---------- */
.ledger { margin-top:22px; border-top:1px solid var(--line); }
.lens { display:grid; grid-template-columns:186px minmax(0,1fr); gap:26px; padding:19px 0; border-bottom:1px solid var(--line-soft); align-items:start; }
.lens .name { font-size:.82rem; font-weight:620; color:var(--ink); }
.lens .tags { display:flex; flex-direction:column; gap:5px; margin-top:7px; }
.dir-tag { font-size:.66rem; font-weight:650; letter-spacing:.04em; line-height:1.4; }
.dir-tag.rust { color:var(--rust); } .dir-tag.current { color:var(--blue); }
.dir-tag.neutral { color:var(--ink-3); } .dir-tag.unknown { color:var(--amber); }
.lens .claim { font-size:.87rem; color:var(--ink-2); line-height:1.6; }
.lens .source { margin-top:6px; font-size:.72rem; color:var(--ink-3); word-break:break-word; }
/* ---------- evidence cards ---------- */
.cards { display:grid; grid-template-columns:repeat(auto-fit,minmax(290px,1fr)); gap:26px 34px; margin-top:24px; }
.card { padding-left:15px; border-left:2px solid var(--line); }
.card.rust { border-left-color:var(--rust); }
.card.current { border-left-color:var(--blue); }
.card.unknown { border-left-color:var(--amber); }
.card h3 { font-size:.9rem; font-weight:620; letter-spacing:-.01em; }
.card p { margin-top:7px; font-size:.85rem; color:var(--ink-2); line-height:1.6; }
.card .ref { display:block; margin-top:9px; font-size:.7rem; color:var(--ink-3); word-break:break-word; }
/* ---------- buys / doesn't buy ---------- */
.duo { display:grid; grid-template-columns:1fr 1fr; gap:44px; margin-top:24px; }
.col h3 { display:flex; align-items:center; gap:8px; font-size:.72rem; letter-spacing:.1em; text-transform:uppercase; color:var(--ink-3); font-weight:650; }
.col h3 i { width:7px; height:7px; border-radius:50%; flex:none; }
.col ul { list-style:none; margin-top:14px; }
.col li { padding:11px 0; border-bottom:1px solid var(--line-soft); font-size:.85rem; color:var(--ink-2); line-height:1.58; }
.col li:last-child { border-bottom:0; }
.col li b { color:var(--ink); font-weight:620; }
/* ---------- precedents ---------- */
.precedents { margin-top:22px; border-top:1px solid var(--line); }
.precedent { padding:19px 0; border-bottom:1px solid var(--line-soft); }
.precedent .top { display:flex; align-items:baseline; gap:11px; flex-wrap:wrap; }
.precedent h3 { font-size:.9rem; font-weight:620; }
.precedent .outcome { font-family:var(--mono); font-size:.66rem; letter-spacing:.07em; color:var(--accent); }
.precedent p { margin-top:7px; font-size:.85rem; color:var(--ink-2); line-height:1.6; max-width:var(--measure); }
.precedent .match { margin-top:8px; font-size:.75rem; color:var(--ink-3); line-height:1.55; }
.precedent .ref { display:inline-block; margin-top:8px; font-size:.7rem; color:var(--ink-3); word-break:break-all; }
.precedent .ref:hover { color:var(--accent); }
/* ---------- reversible path ---------- */
.path { margin-top:22px; counter-reset:step; border-top:1px solid var(--line); }
.step { display:grid; grid-template-columns:30px minmax(0,1fr) auto; gap:16px; padding:19px 0; border-bottom:1px solid var(--line-soft); align-items:start; }
.step .number { counter-increment:step; font-family:var(--mono); font-size:.74rem; color:var(--ink-3); padding-top:2px; }
.step .number::before { content:counter(step,decimal-leading-zero); }
.step h3 { font-size:.9rem; font-weight:620; }
.step p { margin-top:6px; font-size:.83rem; color:var(--ink-2); line-height:1.6; }
.step .cost { font-size:.72rem; color:var(--ink-3); white-space:nowrap; padding-top:3px; }
/* ---------- challenge audit ---------- */
.challenge-grid { display:grid; grid-template-columns:1fr 1fr; gap:44px; margin-top:24px; }
.challenge-col h3 { font-size:.72rem; letter-spacing:.1em; text-transform:uppercase; color:var(--ink-3); font-weight:650; padding-bottom:11px; border-bottom:1px solid var(--line); }
.check { padding:13px 0; border-bottom:1px solid var(--line-soft); font-size:.83rem; color:var(--ink-2); line-height:1.58; }
.check:last-child { border-bottom:0; }
.check .state { margin-right:9px; vertical-align:1px; }
.check b { color:var(--ink); font-weight:620; }
/* ---------- method ---------- */
.method { margin-top:22px; padding-top:22px; border-top:1px solid var(--line); }
.method h3 { font-size:.88rem; font-weight:620; }
.method p { margin-top:10px; font-size:.83rem; color:var(--ink-2); line-height:1.68; max-width:var(--measure); }
.method table { margin-top:24px; min-width:0; }
footer {
margin-top:64px; padding-top:20px; border-top:1px solid var(--line);
color:var(--ink-3); font-size:.73rem; display:flex; gap:20px; flex-wrap:wrap;
}
@media (max-width:960px) { .hero-grid { grid-template-columns:1fr; gap:38px; } }
@media (max-width:760px) {
body { padding:0 18px 64px; font-size:15px; }
.mast { flex-wrap:wrap; gap:10px; }
.mast .meta { margin-left:auto; gap:10px; }
.mast .meta span:not(:first-child) { display:none; }
.cards,.duo,.challenge-grid { grid-template-columns:1fr; gap:30px; }
.lens { grid-template-columns:1fr; gap:9px; }
.step { grid-template-columns:24px minmax(0,1fr); }
.step .cost { grid-column:2; }
section { padding-top:44px; }
}
@media print {
body { padding:0 8mm; font-size:11pt; }
.btn { display:none; }
section { padding-top:26px; break-inside:avoid; }
}
/* ---------- bilingual switch ----------
Both languages ship in the document; the root data-lang attribute selects
one. No new script element is involved: the toggle is the same kind of inline handler
as the theme button. */
:root[data-lang="en"] [data-l="zh"] { display:none; }
:root[data-lang="zh"] [data-l="en"] { display:none; }
:root[data-lang="zh"] body {
font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC",var(--sans);
line-height:1.75;
}
:root[data-lang="zh"] .mono,
:root[data-lang="zh"] .scope-word,
:root[data-lang="zh"] .scope-tag,
:root[data-lang="zh"] .outcome,
:root[data-lang="zh"] .step .number { font-family:var(--mono); }
:root[data-lang="zh"] .kicker { letter-spacing:.06em; }
:root[data-lang="zh"] .lens { grid-template-columns:172px minmax(0,1fr); }
</style>
</head>
<body class="v-stay a-defer">
<div class="wrap">
<header class="mast">
<div class="logo mono">why<span class="q">-not-</span>rust</div>
<div class="meta">
<span class="mono">openssl/openssl</span><span>2026-08-01</span><span class="mono">method 2.0</span>
<button class="btn" onclick="document.documentElement.dataset.lang=document.documentElement.dataset.lang==='en'?'zh':'en'">EN / 中文</button>
<button class="btn" onclick="document.documentElement.dataset.theme=document.documentElement.dataset.theme==='dark'?'light':'dark'"><span data-l="en">light / dark</span><span data-l="zh">浅色 / 深色</span></button>
</div>
</header>
<section class="hero">
<div class="kicker"><span data-l="en">Rust adoption decision</span><span data-l="zh">Rust 采用决策</span> · <span class="project"><span data-l="en">C · TLS and crypto library · 808,625 lines of C · 6,357 exported ABI symbols</span><span data-l="zh">C · TLS 与密码学库 · 808,625 行 C · 6,357 个导出 ABI 符号</span></span></div>
<div class="hero-grid">
<div>
<div class="scope-word">STAY</div>
<div class="scope-sub"><span data-l="en">stay while the one missing document gets produced</span><span data-l="zh">先不动,把缺的那份文档做出来</span></div>
<div class="auth"><i></i><span data-l="en">Authorization</span><span data-l="zh">授权</span> · DEFER–MEASURE</div>
<p class="why"><span data-l="en">The requirement holds. The mechanism is the textbook one: C parsing attacker-controlled DER at a network trust boundary. What nobody has published is OpenSSL's advisory history split by component and root cause, so "rewrite the 16,350 lines of crypto/asn1" and "rewrite all 808,625" rest on identical evidence — none. And no proposed scope has a delivery plan for 6,357 exported symbols and a checksum-pinned FIPS module.</span><span data-l="zh">需求成立。机制也是教科书式的:C 代码在网络信任边界上解析攻击者可控的 DER。真正缺的那份东西没有人公开过,就是按组件和根因拆开的 OpenSSL advisory 历史。于是「重写 crypto/asn1 那 16,350 行」和「把 808,625 行全部重写」靠的是同一份证据,也就是没有证据。而且到目前为止,没有哪个提案为 6,357 个导出符号和被 checksum 锁定的 FIPS 模块给出过交付方案。</span></p>
<p class="trigger"><span data-l="en">INDETERMINATE by construction: nobody has produced the evidence that would settle it. Two documents reopen the decision. First, the advisory history split by component and by eliminated-by-construction / downgraded-to-safe-failure / language-independent. Second, a FIPS impact analysis for the candidate component. If the class concentrates in the decoders, the authorized scope becomes EXTRACT behind the existing ABI — never MIGRATE.</span><span data-l="zh">INDETERMINATE 是结构性的:能给出结论的证据还没有人做出来。两份文档可以重开这个决策。第一份,把 advisory 历史按组件、并按 eliminated-by-construction / downgraded-to-safe-failure / language-independent 拆开。第二份,候选组件的 FIPS 影响分析。如果这类缺陷确实集中在解析器上,被授权的范围就是在现有 ABI 后面做 EXTRACT,而不会是 MIGRATE。</span></p>
<div class="chips">
<span class="scope"><span data-l="en">Scope</span><span data-l="zh">范围</span> <b><span data-l="en">no Rust scope authorized until the component attribution exists</span><span data-l="zh">组件归因文档出现之前,不授权任何 Rust 范围</span></b></span>
<span><span data-l="en">Confidence</span><span data-l="zh">置信度</span> <b>LOW</b></span>
<span><span data-l="en">Robustness</span><span data-l="zh">稳健性</span> <b>INDETERMINATE</b></span>
<span><span data-l="en">security-parser · library under a checksum-pinned FIPS validation boundary</span><span data-l="zh">安全解析器 · 处在 checksum 锁定的 FIPS 验证边界内的库</span></span>
</div>
</div>
<aside class="gatebox">
<h2><span data-l="en">Four proof gates</span><span data-l="zh">四道证据门</span></h2>
<div class="gate"><span class="id mono">G1</span><div><div class="head"><span class="name"><span data-l="en">Requirement</span><span data-l="zh">需求</span></span><span class="state pass">PASS</span></div><div class="ev"><span data-l="en">C decodes attacker-controlled DER and TLS records.</span><span data-l="zh">C 代码在解析攻击者可控的 DER 与 TLS 记录。</span></div></div></div>
<div class="gate"><span class="id mono">G2</span><div><div class="head"><span class="name"><span data-l="en">Causality</span><span data-l="zh">因果</span></span><span class="state unknown">UNKNOWN</span></div><div class="ev"><span data-l="en">No published split of advisories by component or root cause.</span><span data-l="zh">没有任何公开资料把 advisory 按组件或根因拆开。</span></div></div></div>
<div class="gate"><span class="id mono">G3</span><div><div class="head"><span class="name"><span data-l="en">Economics</span><span data-l="zh">经济性</span></span><span class="state unknown">UNKNOWN</span></div><div class="ev"><span data-l="en">Candidate scopes span 16,350 to 808,625 lines.</span><span data-l="zh">候选范围从 16,350 行一直到 808,625 行。</span></div></div></div>
<div class="gate"><span class="id mono">G4</span><div><div class="head"><span class="name"><span data-l="en">Delivery</span><span data-l="zh">交付</span></span><span class="state fail">FAIL</span></div><div class="ev"><span data-l="en">6,357 exported symbols; FIPS sources are checksum-pinned.</span><span data-l="zh">6,357 个导出符号;FIPS 源码被 checksum 锁定。</span></div></div></div>
</aside>
</div>
</section>
<div class="tiles">
<div class="tile"><div class="label"><span data-l="en">Exported ABI symbols</span><span data-l="zh">导出 ABI 符号</span></div><div class="value mono">6,357<span class="unit"><span data-l="en">symbols</span><span data-l="zh">个</span></span></div><div class="note"><span data-l="en">util/libcrypto.num 5,729 + util/libssl.num 628</span><span data-l="zh">util/libcrypto.num 5,729 + util/libssl.num 628</span></div></div>
<div class="tile"><div class="label"><span data-l="en">Attacker-facing decoders</span><span data-l="zh">面向攻击者的解析器</span></div><div class="value mono">49,844<span class="unit"><span data-l="en">lines</span><span data-l="zh">行</span></span></div><div class="note"><span data-l="en">crypto/asn1 + crypto/x509 .c · 6.2% of the .c total</span><span data-l="zh">crypto/asn1 + crypto/x509 的 .c · 占 .c 总量 6.2%</span></div></div>
<div class="tile"><div class="label"><span data-l="en">Hand-tuned asm generators</span><span data-l="zh">手写汇编生成器</span></div><div class="value mono">257,523<span class="unit"><span data-l="en">lines</span><span data-l="zh">行</span></span></div><div class="note"><span data-l="en">crypto/**/asm/*.pl · 213 perlasm files</span><span data-l="zh">crypto/**/asm/*.pl · 213 个 perlasm 文件</span></div></div>
<div class="tile"><div class="label"><span data-l="en">FIPS module</span><span data-l="zh">FIPS 模块</span></div><div class="value mono">checksum<span class="unit"><span data-l="en">pinned</span><span data-l="zh">已锁定</span></span></div><div class="note"><span data-l="en">providers/fips-sources.checksums · validation boundary</span><span data-l="zh">providers/fips-sources.checksums · 验证边界</span></div></div>
<div class="tile"><div class="label"><span data-l="en">Candidate scope spread</span><span data-l="zh">候选范围跨度</span></div><div class="value mono">16,350–808,625<span class="unit"><span data-l="en">lines</span><span data-l="zh">行</span></span></div><div class="note"><span data-l="en">why robustness is INDETERMINATE</span><span data-l="zh">robustness 判为 INDETERMINATE 的原因</span></div></div>
<div class="tile"><div class="label"><span data-l="en">In-repo test material</span><span data-l="zh">仓库内测试代码</span></div><div class="value mono">214,064<span class="unit"><span data-l="en">lines</span><span data-l="zh">行</span></span></div><div class="note"><span data-l="en">test/ .c and .h · 342 files</span><span data-l="zh">test/ 的 .c 与 .h · 342 个文件</span></div></div>
</div>
<section>
<h2 class="section-title"><span class="no">01</span><span data-l="en">The options</span><span data-l="zh">备选方案</span></h2>
<p class="sub"><span data-l="en">Same objective for every option: eliminate the memory-unsafety class where OpenSSL decodes attacker-controlled input, without breaking the exported ABI or the FIPS validation.</span><span data-l="zh">所有方案的目标一致:在 OpenSSL 解析攻击者可控输入的地方消除内存不安全这一缺陷类,同时不破坏导出 ABI 与 FIPS 验证。</span></p>
<div class="table-wrap"><table>
<thead><tr><th><span data-l="en">Option</span><span data-l="zh">方案</span></th><th><span data-l="en">Scope</span><span data-l="zh">范围</span></th><th><span data-l="en">Benefit</span><span data-l="zh">收益</span></th><th><span data-l="en">Cost</span><span data-l="zh">成本</span></th><th><span data-l="en">Time to value</span><span data-l="zh">见效时间</span></th><th><span data-l="en">Compatibility / rollback</span><span data-l="zh">兼容 / 回滚</span></th><th><span data-l="en">Evidence</span><span data-l="zh">证据</span></th></tr></thead>
<tbody>
<tr class="selected"><td><span class="option-name"><span data-l="en">Stay and publish the attribution</span><span data-l="zh">先不动,把归因发出来</span></span><span class="source"><span data-l="en">recommended · the cheapest way to make any later option decidable</span><span data-l="zh">推荐 · 让后续任何方案变得可判定的最便宜做法</span></span></td><td><span class="scope-tag">STAY</span></td><td><span data-l="en">no risk reduction yet; unblocks the decision</span><span data-l="zh">暂时不降低风险;但让决策能做</span></td><td><span data-l="en">weeks of analysis; no recurring</span><span data-l="zh">数周分析;无经常性成本</span></td><td><span data-l="en">weeks</span><span data-l="zh">数周</span></td><td><span data-l="en">unchanged · nothing to roll back</span><span data-l="zh">不变 · 没有要回滚的东西</span></td><td><span class="ev-tag strong">STRONG</span></td></tr>
<tr class=""><td><span class="option-name"><span data-l="en">Continue fuzzing, review and hardening</span><span data-l="zh">继续 fuzzing、评审与加固</span></span><span class="source"><span data-l="en">retain · the funded baseline any Rust option must beat</span><span data-l="zh">保留 · 有预算的基线,任何 Rust 方案都要赢过它</span></span></td><td><span class="scope-tag">STAY</span></td><td><span data-l="en">reduces incidence, class stays reachable</span><span data-l="zh">降低发生率,但这类缺陷仍然可达</span></td><td><span data-l="en">none new; existing budget</span><span data-l="zh">无新增;用现有预算</span></td><td><span data-l="en">already running</span><span data-l="zh">已经在跑</span></td><td><span data-l="en">native · no risk</span><span data-l="zh">原生 · 无风险</span></td><td><span class="ev-tag moderate">MODERATE</span></td></tr>
<tr class=""><td><span class="option-name"><span data-l="en">Rust DER/X.509 decoder behind the ABI</span><span data-l="zh">ABI 后面的 Rust DER/X.509 解析器</span></span><span class="source"><span data-l="en">retain · the likely answer if the attribution lands where structure suggests</span><span data-l="zh">保留 · 如果归因落在结构所指的位置,答案大概就是它</span></span></td><td><span class="scope-tag">EXTRACT</span></td><td><span data-l="en">class removed in 6.2% of the .c tree, if that is where it lives</span><span data-l="zh">在 .c 树的 6.2% 里消除该缺陷类,前提是缺陷确实住在那里</span></td><td><span data-l="en">unpriced; Rust toolchain everywhere</span><span data-l="zh">未定价;每个平台都要带 Rust 工具链</span></td><td><span data-l="en">unknown</span><span data-l="zh">未知</span></td><td><span data-l="en">same ABI · flag-selectable if dual-built</span><span data-l="zh">ABI 不变 · 双构建时可用编译开关切换</span></td><td><span class="ev-tag unknown">UNKNOWN</span></td></tr>
<tr class="excluded"><td><span class="option-name"><span data-l="en">Rewrite OpenSSL in Rust</span><span data-l="zh">用 Rust 重写 OpenSSL</span></span><span class="source"><span data-l="en">exclude · fails G4 outright</span><span data-l="zh">排除 · 直接卡死在 G4</span></span></td><td><span class="scope-tag">MIGRATE</span></td><td><span data-l="en">class removed; no evidence it beats the extraction</span><span data-l="zh">该缺陷类消失;但没有证据说明它优于抽取方案</span></td><td><span data-l="en">multi-year; FIPS re-validation</span><span data-l="zh">以年计;外加 FIPS 重新验证</span></td><td><span data-l="en">years</span><span data-l="zh">以年计</span></td><td><span data-l="en">entire ABI + validation · no rollback</span><span data-l="zh">整个 ABI 加验证 · 无回滚</span></td><td><span class="ev-tag weak">WEAK</span></td></tr>
<tr class=""><td><span class="option-name"><span data-l="en">Consumers adopt a Rust TLS stack</span><span data-l="zh">使用方改用 Rust TLS 栈</span></span><span class="source"><span data-l="en">retain · demonstrably works — see curl's shipped rustls backend</span><span data-l="zh">保留 · 已经被跑通,见 curl 已发布的 rustls 后端</span></span></td><td><span class="scope-tag">ADOPT</span></td><td><span data-l="en">full class removal for that consumer's TLS path</span><span data-l="zh">该使用方的 TLS 路径上整类缺陷消失</span></td><td><span data-l="en">per consumer; narrower coverage</span><span data-l="zh">按使用方计;覆盖面更窄</span></td><td><span data-l="en">weeks per project</span><span data-l="zh">每个项目数周</span></td><td><span data-l="en">different API · caller-side rollback</span><span data-l="zh">API 不同 · 回滚在调用方手里</span></td><td><span class="ev-tag strong">STRONG</span></td></tr>
</tbody>
</table></div>
</section>
<!-- OPTIONAL: drop this section in quick mode -->
<section>
<h2 class="section-title"><span class="no">02</span><span data-l="en">Evidence ledger</span><span data-l="zh">证据账本</span></h2>
<p class="sub"><span data-l="en">States are evidence about named options, not points to be added up. D6 is UNKNOWN and stays UNKNOWN here. It sits under the two gates that decide this case, and no amount of argument closes it.</span><span data-l="zh">状态是针对具体方案的证据,不能相加成分数。D6 是 UNKNOWN,这里就照写 UNKNOWN。决定本案的两道门都压在它上面,靠论证关不上。</span></p>
<div class="ledger">
<div class="lens"><div><div class="name">D1 · <span data-l="en">Requirement & ownership</span><span data-l="zh">需求与归属</span></div><div class="tags"><span class="dir-tag rust">SUPPORTS · rust options</span><span class="ev-tag strong">STRONG</span></div></div><div><div class="claim"><span data-l="en">OpenSSL owns 808,625 lines of C. Of that, 49,844 lines decode attacker-supplied ASN.1 and X.509 structures and 115,280 terminate untrusted TLS records.</span><span data-l="zh">OpenSSL 自己维护 808,625 行 C。其中 49,844 行解析攻击者提供的 ASN.1 与 X.509 结构,115,280 行负责终结不受信任的 TLS 记录。</span></div><div class="source mono">crypto/asn1 65 .c files · crypto/x509 98 .c files · ssl/ 115 .c and .h files</div></div></div>
<div class="lens"><div><div class="name">D2 · <span data-l="en">End-to-end reach</span><span data-l="zh">端到端影响</span></div><div class="tags"><span class="dir-tag neutral">N/A</span><span class="ev-tag strong">STRONG</span></div></div><div><div class="claim"><span data-l="en">Nobody has asserted a performance requirement here. No Amdahl figure appears: a line share is not a time share, and inventing one would be a method error.</span><span data-l="zh">本案没有人提出性能要求。这里不给 Amdahl 数字:行数占比不等于时间占比,硬造一个属于方法错误。</span></div><div class="source mono">objective is safety, not latency</div></div></div>
<div class="lens"><div><div class="name">D3 · <span data-l="en">Tail & runtime</span><span data-l="zh">尾延迟与运行时</span></div><div class="tags"><span class="dir-tag neutral">N/A</span><span class="ev-tag strong">STRONG</span></div></div><div><div class="claim"><span data-l="en">No managed runtime or collector exists in the library, so no runtime mechanism is present in any tail.</span><span data-l="zh">库里没有托管运行时,也没有 GC,所以尾部时延里不存在运行时机制这一项。</span></div><div class="source mono">C library, no GC</div></div></div>
<div class="lens"><div><div class="name">D4 · <span data-l="en">Fleet footprint</span><span data-l="zh">机队占用</span></div><div class="tags"><span class="dir-tag neutral">N/A</span><span class="ev-tag strong">STRONG</span></div></div><div><div class="claim"><span data-l="en">The library runs inside the caller's process; this decision changes no fleet density or instance count.</span><span data-l="zh">库跑在调用方的进程里。这个决策不改变任何机队密度或实例数量。</span></div><div class="source mono">embedded linkage model</div></div></div>
<div class="lens"><div><div class="name">D5 · <span data-l="en">Startup shape</span><span data-l="zh">启动形态</span></div><div class="tags"><span class="dir-tag neutral">N/A</span><span class="ev-tag strong">STRONG</span></div></div><div><div class="claim"><span data-l="en">Handshake round-trips dominate connection setup; library initialization is not a stated constraint here.</span><span data-l="zh">连接建立的时间由握手往返主导。本案没有把库初始化列为约束。</span></div><div class="source mono">no startup requirement asserted</div></div></div>
<div class="lens"><div><div class="name">D6 · <span data-l="en">Safety & correctness</span><span data-l="zh">安全与正确性</span></div><div class="tags"><span class="dir-tag unknown">UNKNOWN · rust options</span><span class="ev-tag unknown">UNKNOWN</span></div></div><div><div class="claim"><span data-l="en">The classification this decision needs does not exist publicly: advisories split per component into eliminated-by-construction, downgraded-to-safe-failure and language-independent. Counting advisories is not a causal argument.</span><span data-l="zh">这项决策需要的分类在公开渠道里不存在:按组件把 advisory 拆成 eliminated-by-construction、downgraded-to-safe-failure、language-independent 三类。只数 advisory 的条数,构不成因果论证。</span></div><div class="source mono">no published per-component root-cause attribution</div></div></div>
<div class="lens"><div><div class="name">D7 · <span data-l="en">Concurrency & invariants</span><span data-l="zh">并发与不变量</span></div><div class="tags"><span class="dir-tag neutral">NEUTRAL · all</span><span class="ev-tag moderate">MODERATE</span></div></div><div><div class="claim"><span data-l="en">Thread safety is already a contract, carried by the library's own locking and init APIs. No option encodes those invariants better without changing the contract.</span><span data-l="zh">线程安全已经写进契约,由库自己的加锁与初始化 API 承担。在不改契约的前提下,没有哪个方案能把这些不变量表达得更好。</span></div><div class="source mono">crypto/threads_*.c · documented threading model</div></div></div>
<div class="lens"><div><div class="name">D8 · <span data-l="en">Distribution</span><span data-l="zh">分发</span></div><div class="tags"><span class="dir-tag rust">DISFAVORS · rust options</span><span class="ev-tag moderate">MODERATE</span></div></div><div><div class="claim"><span data-l="en">OpenSSL is a system dependency on platforms where a C compiler is the only guaranteed toolchain. Putting Rust in the build narrows that set, and it lands on every distributor.</span><span data-l="zh">在一些平台上 OpenSSL 是系统依赖,而那里唯一有保证的工具链就是 C 编译器。把 Rust 加进构建会缩小这个平台集合,而且每一个发行方都要跟着动。</span></div><div class="source mono">Configure · config · Configurations/ platform matrix</div></div></div>
<div class="lens"><div><div class="name">D9 · <span data-l="en">Ecosystem & alternatives</span><span data-l="zh">生态与替代方案</span></div><div class="tags"><span class="dir-tag current">SUPPORTS · adopt-rust-tls</span><span class="ev-tag strong">STRONG</span></div></div><div><div class="claim"><span data-l="en">Production Rust TLS stacks exist and major C consumers already ship them, so the adoption path is demonstrated rather than hypothetical.</span><span data-l="zh">生产级 Rust TLS 栈已经存在,主要的 C 使用方也已经把它发布出去了。所以这条采用路径是有人走通的,不是设想。</span></div><div class="source mono">curl ships lib/vtls/rustls.c (1,468 lines) as a selectable backend</div></div></div>
<div class="lens"><div><div class="name">D10 · <span data-l="en">Boundary & compatibility</span><span data-l="zh">边界与兼容性</span></div><div class="tags"><span class="dir-tag rust">DISFAVORS · rust-full</span><span class="ev-tag strong">STRONG</span></div></div><div><div class="claim"><span data-l="en">The compatibility surface is 5,729 libcrypto plus 628 libssl exported symbols across 144 public headers. Callers branch on the error semantics, so those are part of the surface too.</span><span data-l="zh">兼容面是 144 个公开头文件里的 5,729 个 libcrypto 符号加 628 个 libssl 符号。调用方还会拿错误语义做分支判断,所以错误语义也算在这个面里。</span></div><div class="source mono">util/libcrypto.num · util/libssl.num · include/openssl/ 116 .h + 28 .h.in</div></div></div>
<div class="lens"><div><div class="name">D11 · <span data-l="en">Delivery economics</span><span data-l="zh">交付经济性</span></div><div class="tags"><span class="dir-tag rust">DISFAVORS · rust-full</span><span class="ev-tag strong">STRONG</span></div></div><div><div class="claim"><span data-l="en">The FIPS provider ships with a checksummed source manifest. Replacing validated code is a re-validation project, not a refactor. Separately, 257,523 lines of perlasm generate the hand-tuned assembly that carries the library's performance and none of its safety objective.</span><span data-l="zh">FIPS provider 附带一份带 checksum 的源码清单。替换已验证的代码,走的是重新验证流程,重构那套做法覆盖不了。另外,257,523 行 perlasm 生成的是手写汇编,它们扛着库的性能,和安全目标无关。</span></div><div class="source mono">providers/fips-sources.checksums · providers/fips.checksum · crypto/**/asm/*.pl (213 files)</div></div></div>
<div class="lens"><div><div class="name">D12 · <span data-l="en">Counterfactual</span><span data-l="zh">反事实基线</span></div><div class="tags"><span class="dir-tag current">SUPPORTS · c-harden, adopt-rust-tls</span><span class="ev-tag moderate">MODERATE</span></div></div><div><div class="claim"><span data-l="en">Two funded alternatives already run. Inside OpenSSL, continuous fuzzing and review. Outside it, consumer-side adoption of a Rust TLS stack. Both have to be beaten, not assumed away.</span><span data-l="zh">已经有两个带预算的替代方案在跑。OpenSSL 内部是持续 fuzzing 和代码评审,外部是使用方自己换成 Rust TLS 栈。这两个都得赢过,不能当它们不存在。</span></div><div class="source mono">existing OpenSSL security process · curl's shipped rustls backend</div></div></div>
</div>
<p class="sub"><span data-l="en">N/A lenses: D3 tail latency, D4 fleet footprint and D5 startup shape have no bearing on a memory-safety objective for a library that runs inside the caller's process.</span><span data-l="zh">判为 N/A 的维度:D3 尾延迟、D4 机队占用、D5 启动形态。对一个跑在调用方进程里的库来说,它们与内存安全目标无关。</span></p>
</section>
<!-- OPTIONAL: drop this section in quick mode -->
<section>
<h2 class="section-title"><span class="no">03</span><span data-l="en">What decided it</span><span data-l="zh">决定性证据</span></h2>
<div class="cards">
<div class="card unknown"><h3><span data-l="en">The document this decision needs does not exist</span><span data-l="zh">这项决策需要的那份文档不存在</span></h3><p><span data-l="en">Picking a Rust scope means knowing which components produced OpenSSL's memory-safety advisories, and which of those Rust would have eliminated by construction. Nobody has published that classification. So every scope from 16,350 lines to 808,625 rests on the same evidence.</span><span data-l="zh">要选定 Rust 的范围,就得知道 OpenSSL 的内存安全 advisory 各自出自哪个组件,其中哪些是 Rust 能从构造上直接消除的。这份分类没有人公开过。于是从 16,350 行到 808,625 行,每一个候选范围拿到的都是同一份证据。</span></p><span class="ref mono">no public per-component root-cause attribution</span></div>
<div class="card current"><h3><span data-l="en">The compatibility surface is 6,357 exported symbols</span><span data-l="zh">兼容面就是这 6,357 个导出符号</span></h3><p><span data-l="en">util/libcrypto.num lists 5,729 exported symbols; util/libssl.num adds 628. They span 144 public headers. A rewrite owes every one of them, down to the error semantics callers branch on.</span><span data-l="zh">util/libcrypto.num 列了 5,729 个导出符号,util/libssl.num 再加 628 个,分布在 144 个公开头文件里。重写要对其中每一个负责,细到调用方拿来做分支的错误语义。</span></p><span class="ref mono">util/libcrypto.num · util/libssl.num · include/openssl/</span></div>
<div class="card current"><h3><span data-l="en">The FIPS module's sources are checksum-pinned</span><span data-l="zh">FIPS 模块的源码被 checksum 锁死</span></h3><p><span data-l="en">The pinning is literal. providers/ ships fips-sources.checksums, fips.checksum and fips.module.sources. Replacing validated source starts a re-validation project on a regulatory calendar, which is a different kind of cost from engineering effort.</span><span data-l="zh">这个锁定是字面意义上的。providers/ 里带着 fips-sources.checksums、fips.checksum 和 fips.module.sources。替换已验证的源码,等于启动一个受监管日历约束的重新验证项目,这类成本和工程投入不是一回事。</span></p><span class="ref mono">providers/fips-sources.checksums · providers/fips.module.sources</span></div>
<div class="card current"><h3><span data-l="en">257,523 lines of perlasm are not part of the safety objective</span><span data-l="zh">257,523 行 perlasm 不在安全目标之内</span></h3><p><span data-l="en">The hand-tuned assembly generators carry the library's throughput. A memory-safety rewrite has no reason to touch them. It cannot inherit them either. "Rewrite OpenSSL" and "make OpenSSL safe" are two different projects.</span><span data-l="zh">这些手写汇编生成器扛着库的吞吐。以内存安全为目标的重写没有理由去动它们,也继承不了它们。「重写 OpenSSL」和「让 OpenSSL 安全」是两个项目。</span></p><span class="ref mono">crypto/**/asm/*.pl · 213 files</span></div>
<div class="card rust"><h3><span data-l="en">The attacker-facing decoders are 6.2% of the .c tree</span><span data-l="zh">面向攻击者的解析器占 .c 树的 6.2%</span></h3><p><span data-l="en">crypto/asn1 and crypto/x509 come to 49,844 of 808,625 lines of .c. Two directories. If the class concentrates where the structure suggests, the authorized scope is an extraction roughly sixteen times smaller than the proposal usually made.</span><span data-l="zh">crypto/asn1 加 crypto/x509 一共 49,844 行,而 .c 总量是 808,625 行。两个目录。如果这类缺陷确实集中在结构所指的位置,那么被授权的范围是一次抽取,规模大约是常见提案的十六分之一。</span></p><span class="ref mono">crypto/asn1 16,350 + crypto/x509 33,494 lines of .c</span></div>
</div>
</section>
<!-- OPTIONAL: drop this section in quick mode -->
<section>
<h2 class="section-title"><span class="no">04</span><span data-l="en">What Rust buys here, and what it doesn't</span><span data-l="zh">Rust 在这里买得到什么,买不到什么</span></h2>
<div class="duo">
<div class="col"><h3><i style="background:var(--rust)"></i><span data-l="en">Buys</span><span data-l="zh">买得到</span></h3><ul>
<li><b><span data-l="en">Class elimination in decoded input handling</span><span data-l="zh">解析输入这一段的整类缺陷消失</span></b> — <span data-l="en">a Rust DER/X.509 decoder cannot produce a memory-unsafety defect in its own code, at the exact place attacker bytes arrive first.</span><span data-l="zh">Rust 写的 DER/X.509 解析器,不可能在自己的代码里产生内存不安全缺陷,而这里正是攻击者字节最先落地的位置。</span></li><li><b><span data-l="en">A demonstrated consumer escape hatch</span><span data-l="zh">使用方有一条已经走通的退路</span></b> — <span data-l="en">callers who need memory-safe TLS today can adopt a Rust stack, as curl already does behind a build flag.</span><span data-l="zh">今天就需要内存安全 TLS 的调用方,可以直接换 Rust 栈;curl 已经这么做了,用一个编译开关控制。</span></li><li><b><span data-l="en">Nothing else, yet</span><span data-l="zh">目前就这些</span></b> — <span data-l="en">every wider claim in this decision is currently unmeasured, and the report labels it UNKNOWN rather than estimating it.</span><span data-l="zh">这项决策里更大的主张全都没有测量过。报告把它们标成 UNKNOWN,而不是随手估一个数出来。</span></li>
</ul></div>
<div class="col"><h3><i style="background:var(--blue)"></i><span data-l="en">Doesn't buy</span><span data-l="zh">买不到</span></h3><ul>
<li><b><span data-l="en">Throughput</span><span data-l="zh">吞吐</span></b> — <span data-l="en">the performance lives in 257,523 lines of perlasm-generated assembly that a Rust rewrite would have to keep or re-create.</span><span data-l="zh">性能落在 257,523 行由 perlasm 生成的汇编里。Rust 重写要么把它们留着,要么重造一遍。</span></li><li><b><span data-l="en">Relief from the ABI</span><span data-l="zh">从 ABI 里解脱</span></b> — <span data-l="en">6,357 exported symbols and their error semantics survive any implementation change; they are the contract, not an implementation detail.</span><span data-l="zh">6,357 个导出符号连同它们的错误语义,在任何实现变更之后都还在。它们本身就是契约。</span></li><li><b><span data-l="en">A shortcut through FIPS</span><span data-l="zh">绕开 FIPS 的捷径</span></b> — <span data-l="en">the validated module's sources are checksum-pinned; changing them re-opens validation regardless of language.</span><span data-l="zh">已验证模块的源码被 checksum 锁死。改动它就要重开验证,和用什么语言无关。</span></li>
</ul></div>
</div>
</section>
<!-- OPTIONAL: drop this section in quick mode -->
<section>
<h2 class="section-title"><span class="no">05</span><span data-l="en">Who has done this before</span><span data-l="zh">同型先例</span></h2>
<div class="precedents">
<article class="precedent"><div class="top"><h3>curl · rustls backend</h3><span class="outcome">EXTRACT SHIPPED</span></div><p><span data-l="en">A major C consumer of TLS added a Rust backend as 1,468 lines behind an abstraction that already held six of them, and kept it. The same project pushed Rust deeper into its HTTP internals, and deleted that.</span><span data-l="zh">一个重量级的 TLS C 使用方,在一个已经有六个后端的抽象层后面,加了 1,468 行的 Rust 后端,并且留了下来。同一个项目把 Rust 往 HTTP 内部推的那次尝试,后来被删掉了。</span></p><div class="match"><span data-l="en">matches</span><span data-l="zh">匹配</span>: <span data-l="en">same trust boundary, same language pair, C caller with a stable ABI</span><span data-l="zh">同一条信任边界,同一对语言,调用方是带稳定 ABI 的 C</span> · <span data-l="en">differs</span><span data-l="zh">不匹配</span>: <span data-l="en">curl consumes TLS; OpenSSL is the implementation with the exported ABI and FIPS obligation</span><span data-l="zh">curl 是 TLS 的使用方;OpenSSL 是实现方,扛着导出 ABI 和 FIPS 义务</span></div><a class="ref mono" href="https://daniel.haxx.se/blog/2024/12/21/dropping-hyper/">first-party · maintainer blog</a></article>
<article class="precedent"><div class="top"><h3>Google · Android memory-safety program</h3><span class="outcome">INCREMENTAL</span></div><p><span data-l="en">Memory safety's share of Android vulnerabilities fell from 76% to below 20%. The method was writing new code in safe languages and letting the old C/C++ age out. Google's own data puts five-year-old code at 3.4–7.4× lower vulnerability density than new code.</span><span data-l="zh">Android 漏洞里内存安全类的占比,从 76% 降到 20% 以下。做法是新代码用安全语言写,旧的 C/C++ 自然老化退场。Google 自己的数据显示,五年前写的代码,漏洞密度比新代码低 3.4–7.4 倍。</span></p><div class="match"><span data-l="en">matches</span><span data-l="zh">匹配</span>: <span data-l="en">same requirement class; the strongest published safety numbers available</span><span data-l="zh">同一类需求;也是目前公开的安全数据里最强的一组</span> · <span data-l="en">differs</span><span data-l="zh">不匹配</span>: <span data-l="en">an OS with many entry points, and the figures are C/C++-relative rather than component-attributed</span><span data-l="zh">那是一个入口众多的操作系统;而且那些数字是相对整体 C/C++ 算的,没有落到具体组件</span></div><a class="ref mono" href="https://blog.google/security/rust-in-android-move-fast-fix-things/">first-party · vendor security blog</a></article>
<article class="precedent"><div class="top"><h3>Chromium · memory-safety bug analysis</h3><span class="outcome">ATTRIBUTION PUBLISHED</span></div><p><span data-l="en">Chromium published the analysis OpenSSL has not. Of 912 high and critical security bugs since 2015, roughly 70% were memory unsafety, about half of those use-after-free. A table like that is what makes a scope decision fundable.</span><span data-l="zh">Chromium 公布了 OpenSSL 没有公布的那份分析。2015 年以来的 912 个高危与严重安全缺陷中,约 70% 是内存不安全,其中约一半是 use-after-free。有了这样一张表,范围决策才拿得到预算。</span></p><div class="match"><span data-l="en">matches</span><span data-l="zh">匹配</span>: <span data-l="en">demonstrates the exact artifact this decision is missing</span><span data-l="zh">它正好就是本决策缺的那件东西</span> · <span data-l="en">differs</span><span data-l="zh">不匹配</span>: <span data-l="en">a browser's bug corpus and component structure differ entirely from a crypto library's</span><span data-l="zh">浏览器的缺陷样本和组件结构,和密码学库完全不同</span></div><a class="ref mono" href="https://www.chromium.org/Home/chromium-security/memory-safety/">first-party · project security page</a></article>
<article class="precedent"><div class="top"><h3>Mozilla · Stylo</h3><span class="outcome">EXTRACT SHIPPED</span></div><p><span data-l="en">One Rust component landed inside a large C++ codebase in about two years. The move was to pick a single subsystem with a clean interface. The whole-engine replacement at the same organisation was cancelled.</span><span data-l="zh">一个 Rust 组件用大约两年时间落进了一个大型 C++ 代码库。关键动作是只挑一个接口干净的子系统。同一家机构里那个整引擎替换项目被取消了。</span></p><div class="match"><span data-l="en">matches</span><span data-l="zh">匹配</span>: <span data-l="en">component-scoped Rust inside a large, ABI-constrained native codebase</span><span data-l="zh">在一个受 ABI 约束的大型原生代码库里,把 Rust 的范围限定到单个组件</span> · <span data-l="en">differs</span><span data-l="zh">不匹配</span>: <span data-l="en">no exported C ABI of comparable size and no validation regime</span><span data-l="zh">那里没有同等规模的导出 C ABI,也没有验证合规体系</span></div><a class="ref mono" href="https://bholley.net/blog/2017/stylo.html">first-party · engineer account</a></article>
</div>
</section>
<section>
<h2 class="section-title"><span class="no">06</span><span data-l="en">What to do next</span><span data-l="zh">可逆路径</span></h2>
<div class="path">
<div class="step"><span class="number mono"></span><div><h3><span data-l="en">Publish the advisory attribution</span><span data-l="zh">把 advisory 归因发出来</span></h3><p><span data-l="en">The OpenSSL security team, or an independent analyst, turns the advisory history into a table: component, root cause, and one of eliminated-by-construction / downgraded-to-safe-failure / language-independent. Every advisory in the published set gets classified, and the method gets written down with it. If the public advisory text is too thin to classify, stop and say so — that answer is itself the finding. No code moves in this step.</span><span data-l="zh">由 OpenSSL 安全团队或一位独立分析者,把 advisory 历史整理成一张表:组件、根因,以及 eliminated-by-construction / downgraded-to-safe-failure / language-independent 三选一。公开集合里的每一条 advisory 都要归类,方法也要一并写清楚。如果公开的 advisory 文本不足以支撑分类,就停下来把这一点讲明白,这本身就是结论。这一步不动任何代码。</span></p></div><span class="cost"><span data-l="en">3–6 weeks</span><span data-l="zh">3–6 周</span></span></div>
<div class="step"><span class="number mono"></span><div><h3><span data-l="en">Analyse the FIPS impact of the candidate component</span><span data-l="zh">分析候选组件对 FIPS 的影响</span></h3><p><span data-l="en">Whoever owns the validated module writes down whether replacing the candidate component's implementation crosses the module boundary, and what re-validation would cost. Two answers pass: "outside the boundary" with evidence, or a calendar and a price for re-validation. If the component sits inside the boundary and re-validation is unfundable, the Rust track stops here. Documentation only.</span><span data-l="zh">由已验证模块的负责人写清楚两件事:替换候选组件的实现会不会越过模块边界,以及重新验证要花多少。可通过的答案只有两种:有证据地说明「在边界之外」,或者给出重新验证的日历与价格。如果组件落在边界之内、而重新验证的钱拿不出来,Rust 这条线就到此为止。这一步只出文档。</span></p></div><span class="cost"><span data-l="en">2–4 weeks</span><span data-l="zh">2–4 周</span></span></div>
<div class="step"><span class="number mono"></span><div><h3><span data-l="en">Prototype one decoder behind the unchanged ABI</span><span data-l="zh">在不变的 ABI 后面做一个解析器原型</span></h3><p><span data-l="en">Two engineers who know both C and Rust reimplement the highest-attribution decoder in Rust, exposed through the existing symbols, with a differential-fuzzing harness pointed at the C version. Passing means byte-identical accept/reject decisions and identical error codes on that corpus, plus no measured throughput regression on the library's own test workloads. Ten weeks is the limit. Miss differential parity by then and the divergences get published instead; the C implementation stays the default build and the Rust path stays a disabled build option.</span><span data-l="zh">两位同时熟悉 C 和 Rust 的工程师,用 Rust 重写归因最高的那个解析器,通过现有符号暴露出去,并配一套对着 C 版本跑的 differential fuzzing。通过的标准是:在那份语料上,接受/拒绝的判定逐字节一致,错误码一致,并且在库自己的测试负载上测不到吞吐回退。上限是十周。到期还没做到对照一致,就把差异公开出来收工;C 实现仍然是默认构建,Rust 路径仍然是默认关闭的构建选项。</span></p></div><span class="cost"><span data-l="en">6–10 weeks</span><span data-l="zh">6–10 周</span></span></div>
<div class="step"><span class="number mono"></span><div><h3><span data-l="en">Re-run these gates on the new evidence</span><span data-l="zh">拿新证据重跑这四道门</span></h3><p><span data-l="en">OpenSSL maintainers re-run the assessment against the attribution table, the FIPS analysis and the prototype's parity results. It passes when G2 and G3 leave UNKNOWN with citations attached and the selected scope is written down in words. If G2 is still UNKNOWN, authorization stays DEFER–MEASURE however good the prototype looks. Either way the C implementation stays. So does the hardening programme.</span><span data-l="zh">由 OpenSSL 维护者拿归因表、FIPS 分析和原型的对照结果,把评估重跑一遍。通过的标准是:G2 与 G3 带着引用离开 UNKNOWN,并且用文字写明选定的范围。如果 G2 还停在 UNKNOWN,不管原型做得多好,授权都维持 DEFER–MEASURE。无论如何,C 实现照旧保留,加固计划也照常继续。</span></p></div><span class="cost"><span data-l="en">1 week</span><span data-l="zh">1 周</span></span></div>
</div>
</section>
<section>
<h2 class="section-title"><span class="no">07</span><span data-l="en">Arguing both sides</span><span data-l="zh">对称反证</span></h2>
<div class="challenge-grid">
<div class="challenge-col"><h3><span data-l="en">Against migrating</span><span data-l="zh">反驳「迁移」</span></h3>
<div class="check"><span class="state pass">PASS</span><b><span data-l="en">End-to-end reach</span><span data-l="zh">端到端影响</span></b> — <span data-l="en">No performance benefit is claimed here, and no Amdahl number is invented to fill the space. D2 is N/A because the objective is safety.</span><span data-l="zh">本案不主张性能收益,也没有为了填空造一个 Amdahl 数字。目标是安全,所以 D2 判为 N/A。</span></div>
<div class="check"><span class="state unknown">HIT</span><b><span data-l="en">Attribution</span><span data-l="zh">归因</span></b> — <span data-l="en">The causal share Rust would capture cannot be located. The per-component root-cause classification is not published, so there is nothing to locate it in.</span><span data-l="zh">Rust 能吃下的那部分因果份额定位不了。按组件划分的根因分类没有公开,也就没有可供定位的底图。</span></div>
<div class="check"><span class="state unknown">HIT</span><b><span data-l="en">Baseline and regime</span><span data-l="zh">基线与口径</span></b> — <span data-l="en">Structural line counts are standing in for defect distribution. That is the substitution this method forbids as a basis for authorization.</span><span data-l="zh">这里是用结构性的行数在顶替缺陷分布。这套方法禁止用这种顶替作为授权依据。</span></div>
<div class="check"><span class="state pass">PASS</span><b><span data-l="en">Boundary and compatibility</span><span data-l="zh">边界与兼容性</span></b> — <span data-l="en">6,357 exported symbols, 144 public headers, the error semantics and the checksum-pinned FIPS module are all named, and all priced against the full-rewrite option.</span><span data-l="zh">6,357 个导出符号、144 个公开头文件、错误语义,以及被 checksum 锁死的 FIPS 模块,全都点了名,也全都按整体重写方案算了成本。</span></div>
<div class="check"><span class="state unknown">HIT</span><b><span data-l="en">Delivery ownership</span><span data-l="zh">交付归属</span></b> — <span data-l="en">No proposed Rust scope names an owner, a dual-run plan or a re-validation calendar. The report withholds authorization rather than assume those will turn up.</span><span data-l="zh">现在提出的 Rust 范围,没有一个指定了负责人、双跑方案或重新验证日历。报告选择不授权,而不是假定这些东西以后会自己冒出来。</span></div>
</div>
<div class="challenge-col"><h3><span data-l="en">Against staying</span><span data-l="zh">反驳「不动」</span></h3>
<div class="check"><span class="state pass">PASS</span><b><span data-l="en">Funded counterfactual</span><span data-l="zh">有预算的反事实</span></b> — <span data-l="en">Fuzzing and review inside OpenSSL, and Rust-TLS adoption outside it, are both running, both funded, and both recorded here as options.</span><span data-l="zh">OpenSSL 内部的 fuzzing 与评审,外部使用方改用 Rust TLS,两条路都在跑,都有预算,也都作为方案记录在案。</span></div>
<div class="check"><span class="state unknown">HIT</span><b><span data-l="en">Cost of inaction</span><span data-l="zh">不动的代价</span></b> — <span data-l="en">Staying keeps the memory-unsafety class reachable in 49,844 lines that decode attacker-supplied structures. Nothing here presents staying as the safe choice.</span><span data-l="zh">不动,就意味着在 49,844 行解析攻击者提供结构的代码里,内存不安全这一类缺陷仍然可达。报告没有把「不动」写成安全的那个选择。</span></div>
<div class="check"><span class="state pass">PASS</span><b><span data-l="en">Unsafe-surface omission</span><span data-l="zh">遗漏的 unsafe 面</span></b> — <span data-l="en">G1 and the mechanism behind G2 are both granted to the Rust options. What the report withholds is scope selection and delivery, not the premise.</span><span data-l="zh">G1 以及 G2 背后的机制,报告都认给了 Rust 方案。卡住的是范围选择和交付,不是前提。</span></div>
<div class="check"><span class="state pass">PASS</span><b><span data-l="en">Native-advantage denial</span><span data-l="zh">对原生优势的否认</span></b> — <span data-l="en">A shipped Rust TLS integration in another major C project is cited here as working evidence, not waved off.</span><span data-l="zh">另一个重量级 C 项目里已经发布的 Rust TLS 接入,在这里是当作有效证据引用的,没有被打发掉。</span></div>
<div class="check"><span class="state pass">PASS</span><b><span data-l="en">Stop condition</span><span data-l="zh">停止条件</span></b> — <span data-l="en">DEFER–MEASURE names two documents and one prototype, each with an acceptance threshold. "Harden C forever" is not the default outcome.</span><span data-l="zh">DEFER–MEASURE 点名了两份文档和一个原型,每一项都带验收阈值。所以默认结局不会是「永远加固 C」。</span></div>
</div>
</div>
</section>
<section>
<h2 class="section-title"><span class="no">08</span><span data-l="en">How this was produced</span><span data-l="zh">方法与记录</span></h2>
<div class="method"><h3><span data-l="en">openssl/openssl at cdcb6ad · static read-only analysis · why-not-rust method 2.0</span><span data-l="zh">openssl/openssl @ cdcb6ad · 只读静态分析 · why-not-rust 方法 2.0</span></h3><p><span data-l="en">Repository: github.com/openssl/openssl at commit cdcb6ad, shallow clone, 6,152 tracked files. Scope: the whole repository, with crypto/asn1 and crypto/x509 as the candidate extraction components. Sampling: 808,625 lines of C across 1,695 files; and, on a .c-and-.h basis, crypto/ 350,280, ssl/ 115,280, providers/ 87,318 and test/ 214,064. The bases differ. The per-directory figures count .c and .h together, the 808,625 total is .c only, and crypto/asn1 (16,350) and crypto/x509 (33,494) are quoted .c-only so the 6.2% share compares like with like. Exported-symbol manifests were read as one symbol per line: util/libcrypto.num (5,729) and util/libssl.num (628). No build, test, benchmark or network call was run against the project, and no advisory database was ingested; that absence is the gap this report reports. Objective: eliminate the memory-unsafety class where OpenSSL decodes attacker-controlled input without breaking the exported ABI or FIPS validation. User-supplied facts: none. No Amdahl calculation appears; the objective is safety, and substituting a line share for a time share would be a method error. Confidence is LOW and robustness INDETERMINATE by the method's own definitions, because two of the four gates are UNKNOWN and the candidate scopes span two orders of magnitude. This is a structured decision protocol, not a statistical predictor. DEFER–MEASURE here means the evidence to choose does not exist yet. It does not mean Rust cannot help.</span><span data-l="zh">仓库:github.com/openssl/openssl,commit cdcb6ad,浅克隆,6,152 个受版本控制的文件。范围:整个仓库,候选抽取组件是 crypto/asn1 与 crypto/x509。采样:1,695 个文件、808,625 行 C;按 .c 与 .h 合计的口径,crypto/ 350,280 行,ssl/ 115,280 行,providers/ 87,318 行,test/ 214,064 行。口径不一样。目录级数字是 .c 与 .h 合计,808,625 这个总数只算 .c,crypto/asn1(16,350)与 crypto/x509(33,494)也只算 .c,这样 6.2% 才是同口径比较。导出符号清单按每行一个符号读取:util/libcrypto.num 5,729 个,util/libssl.num 628 个。没有对项目做过任何构建、测试、基准或网络调用,也没有导入任何 advisory 数据库;这份缺失正是本报告要报告的缺口。目标:在 OpenSSL 解析攻击者可控输入的地方消除内存不安全这一缺陷类,同时不破坏导出 ABI 与 FIPS 验证。用户提供的事实:无。本报告不做 Amdahl 计算;目标是安全,用行数占比顶替时间占比属于方法错误。按方法自身的定义,confidence 为 LOW,robustness 为 INDETERMINATE:四道门里有两道停在 UNKNOWN,候选范围跨了两个数量级。这是一套结构化决策协议,不是统计预测器。这里的 DEFER–MEASURE 是说,用来做选择的证据还不存在。它不是说 Rust 帮不上忙。</span></p>
<div class="table-wrap"><table>
<thead><tr><th><span data-l="en">What we don't know</span><span data-l="zh">证据缺口</span></th><th><span data-l="en">What it would change</span><span data-l="zh">会改变什么</span></th></tr></thead>
<tbody><tr><td><span data-l="en">Advisory history split by component and root cause</span><span data-l="zh">按组件与根因拆分的 advisory 历史</span></td><td><span data-l="en">The gap that decides the rest. While it is missing, G2 and G3 stay UNKNOWN and no Rust scope gets authorized — including the small one this report thinks is most likely right.</span><span data-l="zh">决定其余一切的那个缺口。只要它还缺着,G2 和 G3 就停在 UNKNOWN,任何 Rust 范围都拿不到授权,包括报告认为最可能正确的那个小范围。</span></td></tr><tr><td><span data-l="en">FIPS boundary impact for the candidate component</span><span data-l="zh">候选组件对 FIPS 边界的影响</span></td><td><span data-l="en">If the component sits inside the validated module, re-validation cost may make even the small extraction unfundable, and the answer stays STAY.</span><span data-l="zh">如果这个组件落在已验证模块之内,重新验证的成本可能连那次小规模抽取都撑不住,结论就仍然是 STAY。</span></td></tr><tr><td><span data-l="en">Ownership and dual-run plan for any Rust scope</span><span data-l="zh">任何 Rust 范围的负责人与双跑方案</span></td><td><span data-l="en">Without named owners and a rollback path, G4 stays FAIL for every migration option, whatever the attribution turns out to show.</span><span data-l="zh">没有指名的负责人和回滚路径,G4 对每个迁移方案都停在 FAIL,归因最后查出什么都一样。</span></td></tr></tbody>
</table></div>
</div>
</section>
<footer><span>why-not-rust · method 2.0 · <span data-l="en">public repository · static analysis at commit cdcb6ad · no build, benchmark or network call</span><span data-l="zh">公开仓库 · 在 commit cdcb6ad 上做静态分析 · 无构建、基准或网络调用</span></span><span class="mono">github.com/xiaotonng/why-not-rust</span></footer>
</div>
<script type="application/json" id="why-not-rust-assessment">
{
"method": "why-not-rust/2.0",
"generated_at": "2026-08-01",
"repository": {
"path": "https://github.com/openssl/openssl",
"commit": "cdcb6adf8c399c14a5ee8cad5c76b6cc9e333e82",
"scope": "whole repository; crypto/asn1 and crypto/x509 are the candidate extraction components",
"sampling": "shallow clone; 6,152 tracked files enumerated; crypto/, ssl/, providers/, include/openssl/, util/ and test/ measured; no advisory database was ingested"
},
"analysis": {
"mode": "public-repository static analysis; no build, benchmark, or network run against the target",
"user_supplied_facts": [],
"evidence_gaps": [
"Advisory history split by component and root cause",
"FIPS boundary impact for the candidate component",
"Ownership and dual-run plan for any Rust scope"
]
},
"objective": {
"driver": "safety",
"requirement": "eliminate the memory-unsafety class where OpenSSL decodes attacker-controlled input, without breaking the exported ABI or the FIPS validation",
"baseline": "808,625 lines of .c, of which 49,844 decode attacker-supplied ASN.1/X.509; ssl/ adds 115,280 lines of .c and .h terminating untrusted TLS records",
"target": "class elimination in the components where advisories concentrate — a location that is currently unpublished"
},
"decision": {
"authorization": "DEFER–MEASURE",
"scope": "STAY",
"selected_option_id": "stay-measure",
"confidence": "LOW",
"robustness": "INDETERMINATE",
"because": "The requirement holds. The mechanism is the textbook one: C parsing attacker-controlled DER at a network trust boundary. What nobody has published is OpenSSL's advisory history split by component and root cause, so \"rewrite the 16,350 lines of crypto/asn1\" and \"rewrite all 808,625\" rest on identical evidence — none. And no proposed scope has a delivery plan for 6,357 exported symbols and a checksum-pinned FIPS module.",
"change_trigger": "INDETERMINATE by construction: nobody has produced the evidence that would settle it. Two documents reopen the decision. First, the advisory history split by component and by eliminated-by-construction / downgraded-to-safe-failure / language-independent. Second, a FIPS impact analysis for the candidate component. If the class concentrates in the decoders, the authorized scope becomes EXTRACT behind the existing ABI — never MIGRATE."
},
"gates": [
{
"id": "G1",
"option_id": "stay-measure",
"name": "requirement",
"state": "PASS",
"evidence": "crypto/asn1 (16,350 lines of .c) and crypto/x509 (33,494) decode attacker-supplied structures; ssl/ (115,280 lines) terminates untrusted records. The exposure is structural."
},
{
"id": "G2",
"option_id": "stay-measure",
"name": "rust-specific causality",
"state": "UNKNOWN",
"evidence": "Rust removes the memory-unsafety class from the code it replaces. Which code, and how much of OpenSSL's advisory history that would have covered, is published nowhere. The causal share can only be assumed."
},
{
"id": "G3",
"option_id": "stay-measure",
"name": "economics and smallest sufficient option",
"state": "UNKNOWN",
"evidence": "Without G2's attribution there is no way to name the smallest sufficient option. A 2% decoder extraction and a 100% rewrite differ by two orders of magnitude in cost, and both rest on the same evidence: none."
},
{
"id": "G4",
"option_id": "stay-measure",
"name": "delivery and reversibility",
"state": "FAIL",
"evidence": "No proposed Rust scope has a delivery plan for 5,729 libcrypto plus 628 libssl exported symbols across 144 public headers, nor for a FIPS provider whose source manifest is checksummed for validation. The measure step carries no delivery risk of its own."
}
],
"options": [
{
"id": "stay-measure",
"name": "Stay and publish the attribution",
"implementation": "current",
"scope": "stay",
"target": "eliminate the memory-unsafety class where OpenSSL decodes attacker-controlled input, without breaking the exported ABI or the FIPS validation",
"benefit_interval": "no risk reduction yet; unblocks the decision",
"one_time_cost": "weeks of analyst time",
"recurring_cost": "none",
"time_to_value": "weeks",
"compatibility": "unchanged",
"reversibility": "n/a",
"evidence_strength": "STRONG",
"disposition": "selected",
"reason": "G2 and G3 are UNKNOWN, and this is the only option that changes that."
},
{
"id": "c-harden",
"name": "Continue fuzzing, review and hardening",
"implementation": "current",
"scope": "stay",
"target": "eliminate the memory-unsafety class where OpenSSL decodes attacker-controlled input, without breaking the exported ABI or the FIPS validation",
"benefit_interval": "reduces incidence, class stays reachable",
"one_time_cost": "none new",
"recurring_cost": "existing security budget",
"time_to_value": "already running",
"compatibility": "native",
"reversibility": "n/a",
"evidence_strength": "MODERATE",
"disposition": "retain",
"reason": "Runs today and must be priced as the counterfactual rather than assumed away."
},
{
"id": "rust-decoder-extract",
"name": "Rust DER/X.509 decoder behind the ABI",
"implementation": "rust",
"scope": "extract",
"target": "eliminate the memory-unsafety class where OpenSSL decodes attacker-controlled input, without breaking the exported ABI or the FIPS validation",
"benefit_interval": "class removed in 6.2% of the .c tree, if that is where it lives",
"one_time_cost": "unpriced; parity plus FIPS analysis",
"recurring_cost": "Rust in the build for every platform",
"time_to_value": "unknown",
"compatibility": "must preserve exact error semantics",
"reversibility": "build flag, if kept dual",
"evidence_strength": "UNKNOWN",
"disposition": "retain",
"reason": "Correct shape and smallest plausible scope, but its benefit is unmeasured and its FIPS impact unanalysed."
},
{
"id": "rust-full",
"name": "Rewrite OpenSSL in Rust",
"implementation": "rust",
"scope": "full",
"target": "eliminate the memory-unsafety class where OpenSSL decodes attacker-controlled input, without breaking the exported ABI or the FIPS validation",
"benefit_interval": "class removed; no evidence it beats the extraction",
"one_time_cost": "multi-year, unbudgeted",
"recurring_cost": "re-validate FIPS; two toolchains forever",
"time_to_value": "years",
"compatibility": "6,357 symbols + 144 headers + FIPS",
"reversibility": "none",
"evidence_strength": "WEAK",
"disposition": "exclude",
"reason": "No delivery plan exists for the exported ABI or the checksum-pinned FIPS module, and 257,523 lines of perlasm carry no part of the safety objective."
},
{
"id": "adopt-rust-tls",
"name": "Consumers adopt a Rust TLS stack",
"implementation": "external",
"scope": "adopt",
"target": "eliminate the memory-unsafety class where OpenSSL decodes attacker-controlled input, without breaking the exported ABI or the FIPS validation",
"benefit_interval": "full class removal for that consumer's TLS path",
"one_time_cost": "per consumer",
"recurring_cost": "narrower algorithm and platform coverage",
"time_to_value": "weeks per project",
"compatibility": "different API; FIPS varies",
"reversibility": "caller-side",
"evidence_strength": "STRONG",
"disposition": "retain",
"reason": "Meets the objective for an individual consumer today; it is not a decision OpenSSL can make on their behalf."
}
],
"lenses": [
{
"id": "D1",
"name": "Requirement \u0026 ownership",
"option_ids": [
"rust-decoder-extract",
"rust-full",
"adopt-rust-tls"
],
"state": "SUPPORTS",
"strength": "STRONG",
"claim": "OpenSSL owns 808,625 lines of C. Of that, 49,844 lines decode attacker-supplied ASN.1 and X.509 structures and 115,280 terminate untrusted TLS records.",
"source": "crypto/asn1 65 .c files · crypto/x509 98 .c files · ssl/ 115 .c and .h files",
"baseline_regime": "static structure of the shipped library",
"caveat": "Structural exposure only; no per-component defect attribution is published.",
"change_trigger": ""
},
{
"id": "D2",
"name": "End-to-end reach",
"option_ids": [],
"state": "N/A",
"strength": "STRONG",
"claim": "Nobody has asserted a performance requirement here. No Amdahl figure appears: a line share is not a time share, and inventing one would be a method error.",
"source": "objective is safety, not latency",
"baseline_regime": "n/a",
"caveat": "",
"change_trigger": ""
},
{
"id": "D3",
"name": "Tail \u0026 runtime",
"option_ids": [],
"state": "N/A",
"strength": "STRONG",
"claim": "No managed runtime or collector exists in the library, so no runtime mechanism is present in any tail.",
"source": "C library, no GC",
"baseline_regime": "n/a",
"caveat": "",
"change_trigger": ""
},
{
"id": "D4",
"name": "Fleet footprint",
"option_ids": [],
"state": "N/A",
"strength": "STRONG",
"claim": "The library runs inside the caller's process; this decision changes no fleet density or instance count.",
"source": "embedded linkage model",
"baseline_regime": "n/a",
"caveat": "",
"change_trigger": ""
},
{
"id": "D5",
"name": "Startup shape",
"option_ids": [],
"state": "N/A",
"strength": "STRONG",
"claim": "Handshake round-trips dominate connection setup; library initialization is not a stated constraint here.",
"source": "no startup requirement asserted",
"baseline_regime": "n/a",
"caveat": "",
"change_trigger": ""
},
{
"id": "D6",
"name": "Safety \u0026 correctness",
"option_ids": [
"rust-decoder-extract",
"rust-full"
],
"state": "UNKNOWN",
"strength": "UNKNOWN",
"claim": "The classification this decision needs does not exist publicly: advisories split per component into eliminated-by-construction, downgraded-to-safe-failure and language-independent. Counting advisories is not a causal argument.",
"source": "no published per-component root-cause attribution",
"baseline_regime": "n/a — the artifact is absent",
"caveat": "This is the single gap that makes the whole decision indeterminate.",
"change_trigger": "Publishing the classification moves G2 and G3 out of UNKNOWN and likely selects the decoder extraction."
},
{
"id": "D7",
"name": "Concurrency \u0026 invariants",
"option_ids": [],
"state": "NEUTRAL",
"strength": "MODERATE",
"claim": "Thread safety is already a contract, carried by the library's own locking and init APIs. No option encodes those invariants better without changing the contract.",
"source": "crypto/threads_*.c · documented threading model",
"baseline_regime": "library concurrency contract",
"caveat": "A Rust component would have to honour the same externally visible contract.",
"change_trigger": ""
},
{
"id": "D8",
"name": "Distribution",
"option_ids": [
"rust-decoder-extract",
"rust-full"
],
"state": "DISFAVORS",
"strength": "MODERATE",
"claim": "OpenSSL is a system dependency on platforms where a C compiler is the only guaranteed toolchain. Putting Rust in the build narrows that set, and it lands on every distributor.",
"source": "Configure · config · Configurations/ platform matrix",
"baseline_regime": "supported platform inventory",
"caveat": "The cost differs by two orders of magnitude between one component and a full rewrite.",
"change_trigger": ""
},
{
"id": "D9",
"name": "Ecosystem \u0026 alternatives",
"option_ids": [
"adopt-rust-tls"
],
"state": "SUPPORTS",
"strength": "STRONG",
"claim": "Production Rust TLS stacks exist and major C consumers already ship them, so the adoption path is demonstrated rather than hypothetical.",
"source": "curl ships lib/vtls/rustls.c (1,468 lines) as a selectable backend",
"baseline_regime": "shipped integration in another project",
"caveat": "Algorithm coverage, platform coverage and FIPS status differ from OpenSSL's.",
"change_trigger": ""
},
{
"id": "D10",
"name": "Boundary \u0026 compatibility",
"option_ids": [
"rust-full"
],
"state": "DISFAVORS",
"strength": "STRONG",
"claim": "The compatibility surface is 5,729 libcrypto plus 628 libssl exported symbols across 144 public headers. Callers branch on the error semantics, so those are part of the surface too.",
"source": "util/libcrypto.num · util/libssl.num · include/openssl/ 116 .h + 28 .h.in",
"baseline_regime": "exported symbol manifests in this commit",
"caveat": "One decoder behind the same ABI inherits a small share of this surface, which is why that option stays retained.",
"change_trigger": ""
},
{
"id": "D11",
"name": "Delivery economics",
"option_ids": [
"rust-full"
],
"state": "DISFAVORS",
"strength": "STRONG",
"claim": "The FIPS provider ships with a checksummed source manifest. Replacing validated code is a re-validation project, not a refactor. Separately, 257,523 lines of perlasm generate the hand-tuned assembly that carries the library's performance and none of its safety objective.",
"source": "providers/fips-sources.checksums · providers/fips.checksum · crypto/**/asm/*.pl (213 files)",
"baseline_regime": "build and validation artifacts in this commit",
"caveat": "Not every consumer needs FIPS; every distributor-shipped OpenSSL has to decide about it.",
"change_trigger": ""
},
{
"id": "D12",
"name": "Counterfactual",
"option_ids": [
"c-harden",
"adopt-rust-tls"
],
"state": "SUPPORTS",
"strength": "MODERATE",
"claim": "Two funded alternatives already run. Inside OpenSSL, continuous fuzzing and review. Outside it, consumer-side adoption of a Rust TLS stack. Both have to be beaten, not assumed away.",
"source": "existing OpenSSL security process · curl's shipped rustls backend",
"baseline_regime": "current practice in and around the project",
"caveat": "Neither eliminates the class inside OpenSSL itself.",
"change_trigger": ""
}
],
"math": {
"amdahl": null,
"break_even": null
},
"precedents": [
{
"name": "curl · rustls backend",
"outcome": "EXTRACT SHIPPED",
"match": "same trust boundary, same language pair, C caller with a stable ABI",
"mismatch": "curl consumes TLS; OpenSSL is the implementation with the exported ABI and FIPS obligation",
"workload_regime": "shipped backend, curl 8.x",
"source_class": "first-party · maintainer blog",
"url": "https://daniel.haxx.se/blog/2024/12/21/dropping-hyper/"
},
{
"name": "Google · Android memory-safety program",
"outcome": "INCREMENTAL",
"match": "same requirement class; the strongest published safety numbers available",
"mismatch": "an OS with many entry points, and the figures are C/C++-relative rather than component-attributed",
"workload_regime": "2019–2025 vulnerability share",
"source_class": "first-party · vendor security blog",
"url": "https://blog.google/security/rust-in-android-move-fast-fix-things/"
},
{
"name": "Chromium · memory-safety bug analysis",
"outcome": "ATTRIBUTION PUBLISHED",
"match": "demonstrates the exact artifact this decision is missing",
"mismatch": "a browser's bug corpus and component structure differ entirely from a crypto library's",
"workload_regime": "912 high/critical bugs, 2015 onward",
"source_class": "first-party · project security page",
"url": "https://www.chromium.org/Home/chromium-security/memory-safety/"
},
{
"name": "Mozilla · Stylo",
"outcome": "EXTRACT SHIPPED",
"match": "component-scoped Rust inside a large, ABI-constrained native codebase",
"mismatch": "no exported C ABI of comparable size and no validation regime",
"workload_regime": "shipped in Firefox 57",
"source_class": "first-party · engineer account",
"url": "https://bholley.net/blog/2017/stylo.html"
}
],
"path": [
{
"step": 1,
"title": "Publish the advisory attribution",
"owner": "OpenSSL security team or an independent analyst",
"cost_range": "3–6 weeks",
"artifact": "OpenSSL's advisory history as a table: component, root cause, and one of eliminated-by-construction / downgraded-to-safe-failure / language-independent",
"acceptance": "every advisory in the published set is classified and attributed to a component, with the method stated",
"deadline_or_stop": "stop and say so if public advisory text is insufficient to classify — that itself is the finding",
"rollback": "documentation only; no code changes"
},
{
"step": 2,
"title": "Analyse the FIPS impact of the candidate component",
"owner": "whoever owns the validated module",
"cost_range": "2–4 weeks",
"artifact": "a written analysis of whether replacing the candidate component's implementation crosses the validated module boundary, and what re-validation would cost",
"acceptance": "the analysis states either 'outside the boundary' with evidence, or the calendar and cost of re-validation",
"deadline_or_stop": "stop the Rust track entirely if the component is inside the boundary and re-validation is unfundable",
"rollback": "documentation only"
},
{
"step": 3,
"title": "Prototype one decoder behind the unchanged ABI",
"owner": "two engineers with C and Rust experience",
"cost_range": "6–10 weeks",
"artifact": "a Rust implementation of the highest-attribution decoder, exposed through the existing symbols, with a differential-fuzzing harness against the C version",
"acceptance": "byte-identical accept/reject decisions and identical error codes on a differential-fuzzing corpus, plus no measured throughput regression on the library's own test workloads",
"deadline_or_stop": "stop after ten weeks if differential parity is not reached; publish the divergences",
"rollback": "the C implementation remains the default build; the Rust path is a disabled build option"
},
{
"step": 4,
"title": "Re-run these gates on the new evidence",
"owner": "OpenSSL maintainers",
"cost_range": "1 week",
"artifact": "a re-assessment using the attribution table, the FIPS analysis and the prototype's parity results",
"acceptance": "G2 and G3 leave UNKNOWN with cited evidence, and the selected scope is stated explicitly",
"deadline_or_stop": "if G2 stays UNKNOWN, authorization remains DEFER–MEASURE regardless of prototype quality",
"rollback": "the current C implementation and hardening programme continue unchanged"
}
],
"assumptions": [
"The shallow clone at the named commit represents the shipped tree; the exported-symbol manifests are read as one symbol per line.",
"OpenSSL continues to support platforms whose only guaranteed toolchain is a C compiler.",
"No performance requirement is attached to this decision; if one appears, D2 must be recomputed from a real profile."
],
"challenge_audit": {
"migration_case": [
{
"id": "M1",
"name": "End-to-end reach",
"state": "PASS",
"claim": "No performance benefit is claimed here, and no Amdahl number is invented to fill the space. D2 is N/A because the objective is safety.",
"evidence": "D2 record"
},
{
"id": "M2",
"name": "Attribution",
"state": "HIT",
"claim": "The causal share Rust would capture cannot be located. The per-component root-cause classification is not published, so there is nothing to locate it in.",
"evidence": "D6 · G2 UNKNOWN"
},
{
"id": "M3",
"name": "Baseline and regime",
"state": "HIT",
"claim": "Structural line counts are standing in for defect distribution. That is the substitution this method forbids as a basis for authorization.",
"evidence": "D1 caveat"
},
{
"id": "M4",
"name": "Boundary and compatibility",
"state": "PASS",
"claim": "6,357 exported symbols, 144 public headers, the error semantics and the checksum-pinned FIPS module are all named, and all priced against the full-rewrite option.",
"evidence": "D10, D11 · util/libcrypto.num, providers/fips-sources.checksums"
},
{
"id": "M5",
"name": "Delivery ownership",
"state": "HIT",
"claim": "No proposed Rust scope names an owner, a dual-run plan or a re-validation calendar. The report withholds authorization rather than assume those will turn up.",
"evidence": "G4 FAIL"
}
],
"staying_case": [
{
"id": "S1",
"name": "Funded counterfactual",
"state": "PASS",
"claim": "Fuzzing and review inside OpenSSL, and Rust-TLS adoption outside it, are both running, both funded, and both recorded here as options.",
"evidence": "D12 · c-harden, adopt-rust-tls"
},
{
"id": "S2",
"name": "Cost of inaction",
"state": "HIT",
"claim": "Staying keeps the memory-unsafety class reachable in 49,844 lines that decode attacker-supplied structures. Nothing here presents staying as the safe choice.",
"evidence": "D1 · G1 PASS"
},
{
"id": "S3",
"name": "Unsafe-surface omission",
"state": "PASS",
"claim": "G1 and the mechanism behind G2 are both granted to the Rust options. What the report withholds is scope selection and delivery, not the premise.",
"evidence": "G1 PASS, D6 mechanism"
},
{
"id": "S4",
"name": "Native-advantage denial",
"state": "PASS",
"claim": "A shipped Rust TLS integration in another major C project is cited here as working evidence, not waved off.",
"evidence": "D9 · curl lib/vtls/rustls.c"
},
{
"id": "S5",
"name": "Stop condition",
"state": "PASS",
"claim": "DEFER–MEASURE names two documents and one prototype, each with an acceptance threshold. \"Harden C forever\" is not the default outcome.",
"evidence": "reversible path steps 1–3"
}
]
}
}
</script>
</body>
</html>