|
136 | 136 | ), |
137 | 137 | } |
138 | 138 |
|
| 139 | +REMOTE_ONLY_DECISIONS = { |
| 140 | + "e84e13f52af9dacd": ( |
| 141 | + "remote_protocol_logic_defect_no_availability_outcome", |
| 142 | + "no_failure", |
| 143 | + True, |
| 144 | + True, |
| 145 | + False, |
| 146 | + "deployed_code", |
| 147 | + "The source explicitly identifies crafted GetBlockHeaders Skip values and fixes " |
| 148 | + "overflow/underflow traversal. It does not report crash, panic, resource " |
| 149 | + "exhaustion, or node unavailability from the single request.", |
| 150 | + ), |
| 151 | + "6fad56ffd2f16be8": ( |
| 152 | + "predeployment_internal_race_no_remote_attack", |
| 153 | + "functional_failure", |
| 154 | + False, |
| 155 | + False, |
| 156 | + False, |
| 157 | + "predeployment", |
| 158 | + "The source establishes a PeerDAS getBlobs race that prevents data-column " |
| 159 | + "persistence. It does not identify attacker control, one-message failure, or a " |
| 160 | + "deployed network impact; the commit dates to 2025-01-15.", |
| 161 | + ), |
| 162 | + "lighthouse:networking:PR#832": ( |
| 163 | + "pre_mainnet_multi_message_amplification_no_crash", |
| 164 | + "resource_amplification", |
| 165 | + False, |
| 166 | + True, |
| 167 | + False, |
| 168 | + "pre_mainnet", |
| 169 | + "The source describes duplicate gossip loops after more than 256 unique messages " |
| 170 | + "and explicitly discusses non-malicious validators. It does not report a crash, " |
| 171 | + "requires a repeated multi-message cycle, and merged on 2020-01-31 before " |
| 172 | + "Beacon Chain mainnet.", |
| 173 | + ), |
| 174 | +} |
| 175 | + |
139 | 176 |
|
140 | 177 | def pct(count: int, denominator: int) -> float: |
141 | 178 | return round(100 * count / denominator, 3) if denominator else 0.0 |
@@ -329,6 +366,64 @@ def main() -> int: |
329 | 366 | args.output_dir / "liveness_both_terms_audit.csv", index=False |
330 | 367 | ) |
331 | 368 |
|
| 369 | + remote_only_rows = candidates[ |
| 370 | + candidates["evidence_screen"].eq("remote_term_only") |
| 371 | + ].copy() |
| 372 | + actual = set(remote_only_rows["id"]) |
| 373 | + expected = set(REMOTE_ONLY_DECISIONS) |
| 374 | + if actual != expected: |
| 375 | + raise ValueError( |
| 376 | + f"Remote-only decisions mismatch; " |
| 377 | + f"missing={sorted(actual - expected)}, extra={sorted(expected - actual)}" |
| 378 | + ) |
| 379 | + remote_only_rows["audit_verdict"] = remote_only_rows["id"].map( |
| 380 | + lambda row_id: REMOTE_ONLY_DECISIONS[row_id][0] |
| 381 | + ) |
| 382 | + remote_only_rows["availability_evidence"] = remote_only_rows["id"].map( |
| 383 | + lambda row_id: REMOTE_ONLY_DECISIONS[row_id][1] |
| 384 | + ) |
| 385 | + remote_only_rows["attacker_controlled_input_evidenced"] = remote_only_rows[ |
| 386 | + "id" |
| 387 | + ].map(lambda row_id: REMOTE_ONLY_DECISIONS[row_id][2]) |
| 388 | + remote_only_rows["remote_input_evidenced"] = remote_only_rows["id"].map( |
| 389 | + lambda row_id: REMOTE_ONLY_DECISIONS[row_id][3] |
| 390 | + ) |
| 391 | + remote_only_rows["single_input_failure_evidenced"] = remote_only_rows[ |
| 392 | + "id" |
| 393 | + ].map(lambda row_id: REMOTE_ONLY_DECISIONS[row_id][4]) |
| 394 | + remote_only_rows["deployment_context"] = remote_only_rows["id"].map( |
| 395 | + lambda row_id: REMOTE_ONLY_DECISIONS[row_id][5] |
| 396 | + ) |
| 397 | + remote_only_rows["audit_reason"] = remote_only_rows["id"].map( |
| 398 | + lambda row_id: REMOTE_ONLY_DECISIONS[row_id][6] |
| 399 | + ) |
| 400 | + remote_only_rows["confirmed_high"] = False |
| 401 | + remote_only_rows[ |
| 402 | + [ |
| 403 | + "id", |
| 404 | + "source_platform", |
| 405 | + "title", |
| 406 | + "source_url", |
| 407 | + "severity_estimated", |
| 408 | + "severity_analysis_label", |
| 409 | + "audit_verdict", |
| 410 | + "availability_evidence", |
| 411 | + "attacker_controlled_input_evidenced", |
| 412 | + "remote_input_evidenced", |
| 413 | + "single_input_failure_evidenced", |
| 414 | + "deployment_context", |
| 415 | + "confirmed_high", |
| 416 | + "audit_reason", |
| 417 | + "blast_radius", |
| 418 | + "authority_tier", |
| 419 | + "root_cause", |
| 420 | + "attack_path", |
| 421 | + "label", |
| 422 | + ] |
| 423 | + ].sort_values(["audit_verdict", "id"]).to_csv( |
| 424 | + args.output_dir / "liveness_remote_only_audit.csv", index=False |
| 425 | + ) |
| 426 | + |
332 | 427 | exact_duplicate_rows = int(candidates["exact_diff_duplicate"].sum()) |
333 | 428 | duplicate_groups = len(duplicate_fingerprints) |
334 | 429 | distinct_artifacts = len(candidates) - (exact_duplicate_rows - duplicate_groups) |
@@ -434,6 +529,24 @@ def main() -> int: |
434 | 529 | len(both_rows), |
435 | 530 | "none establish deployed >33% impact at the fix date", |
436 | 531 | ), |
| 532 | + ( |
| 533 | + "remote_only_audited", |
| 534 | + len(remote_only_rows), |
| 535 | + len(candidates), |
| 536 | + "all rows containing remote-trigger but no availability vocabulary", |
| 537 | + ), |
| 538 | + ( |
| 539 | + "remote_only_single_input_failure_evidenced", |
| 540 | + int(remote_only_rows["single_input_failure_evidenced"].sum()), |
| 541 | + len(remote_only_rows), |
| 542 | + "none connect one remote input to an availability failure", |
| 543 | + ), |
| 544 | + ( |
| 545 | + "remote_only_confirmed_high", |
| 546 | + int(remote_only_rows["confirmed_high"].sum()), |
| 547 | + len(remote_only_rows), |
| 548 | + "none establish the EF >33% threshold", |
| 549 | + ), |
437 | 550 | ] |
438 | 551 | summary_frame = pd.DataFrame( |
439 | 552 | summary, columns=["metric", "rows", "denominator", "definition"] |
|
0 commit comments