a384d9d6d53d

fix: PCH-01-011 cap envelope stripe count S at MAX_STRIPE_COUNT (64) at accept time — prevents O(S) allocation DoS on Codex restore from a crafted code (v3.8.1)

time
2026-06-10 19:51 UTC · 4 months ago
machine
koh2
agent
claude-opus-4-8
session
pch-01 fix integration
parent
95f03cf1813b
3 files src/popup/codex/codex.adversarial.test.ts+17 −0src/popup/codex/restore.ts+8 −0src/shared/codex-types.ts+8 −0
src/popup/codex/codex.adversarial.test.ts+17 −0
@@ -247,6 +247,23 @@247247     const counts = deriveShardCounts(total);248248     expect(counts.dataShards).toBe(200);249249   });250+251+  // PCH-01-011: a crafted stripe count S must be rejected before it pins252+  // this.stripeCount and drives O(S) allocation on the popup's render path.253+  it("chunk_buffer_rejects_oversized_stripe_count", async () => {254+    const buf = new ChunkBuffer();255+    const env = await buildChunkEnvelope(new Uint8Array([1, 2, 3, 4, 5]), 0, 5, false, VERIFY_CODE, 0, 2 ** 31);256+    const res = await buf.add(env);257+    expect(res).toEqual({ accepted: false, reason: "parse_error" });258+    expect(buf.getContext()).toBeNull();259+  });260+261+  it("chunk_buffer_accepts_max_stripe_count", async () => {262+    const buf = new ChunkBuffer();263+    const env = await buildChunkEnvelope(new Uint8Array([1, 2, 3, 4, 5]), 0, 5, false, VERIFY_CODE, 0, 64);264+    const res = await buf.add(env);265+    expect(res.accepted).toBe(true);266+  });250267 });251268 252269 // ── verifyChunkIntegrity adversarial ────────────────────────────────────────

view file

src/popup/codex/restore.ts+8 −0
@@ -24,6 +24,7 @@2424 import {2525   CodexError,2626   deriveShardCounts,27+  MAX_STRIPE_COUNT,2728   type ChunkEnvelope,2829   type CodexPayload,2930 } from "../../shared/codex-types";@@ -110,6 +111,13 @@110111 111112     const stripe = envelope.v === 3 ? (envelope.s ?? 0) : 0;112113     const stripeCount = envelope.v === 3 ? (envelope.S ?? 1) : 1;114+115+    // PCH-01-011: `S` is attacker-controlled. Reject an out-of-range stripe116+    // count before it can pin this.stripeCount and drive O(S) allocation in117+    // stripeReadiness()/toStripeChunks() on the popup's render path.118+    if (stripeCount < 1 || stripeCount > MAX_STRIPE_COUNT) {119+      return { accepted: false, reason: "parse_error" };120+    }113121 114122     if (this.verifyCode === null) {115123       // First valid chunk establishes verifyCode and stripeCount. Reject

view file

src/shared/codex-types.ts+8 −0
@@ -200,6 +200,14 @@200200  */201201 export const MAX_DATA_SHARDS_PER_STRIPE = 200;202202 203+/**204+ * Upper bound on the stripe count `S` accepted from a scanned envelope. A real205+ * Codex prints only a handful of stripes; this ceiling exists purely to stop a206+ * crafted `S` (e.g. 2^31) from driving O(S) allocation on the restore hot path207+ * (PCH-01-011). 64 is far above any real layout.208+ */209+export const MAX_STRIPE_COUNT = 64;210+203211 /** Floor on parity shards: a Codex must always survive losing at least 2 codes. */204212 export const CODEX_MIN_PARITY_SHARDS = 2;205213 

view file