From b6b2682e2d5e1de0d8ab46415730440f8e4b7a54 Mon Sep 17 00:00:00 2001 From: Elena Nadolinski Date: Mon, 22 Jun 2026 11:06:09 -0700 Subject: [PATCH] fix: bound JS CBOR parsing depth --- .github/workflows/test.yml | 1 + tools/p384_hints.js | 18 ++++++++++++------ tools/p384_hints.test.js | 26 ++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 tools/p384_hints.test.js diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2d70a28..ccd33c0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -64,6 +64,7 @@ jobs: run: | node --check tools/nitro_attestation_input.js node --check tools/hinted_attestation_calls.js + node --test tools/p384_hints.test.js node tools/nitro_attestation_input.js fixture > /dev/null node tools/hinted_attestation_calls.js fixture > /dev/null id: tools diff --git a/tools/p384_hints.js b/tools/p384_hints.js index f07072a..f707401 100644 --- a/tools/p384_hints.js +++ b/tools/p384_hints.js @@ -12,6 +12,7 @@ const B = hexToBigInt("b3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5 const GX = hexToBigInt("aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7"); const GY = hexToBigInt("3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f"); const MASK_256 = (1n << 256n) - 1n; +const MAX_CBOR_NESTING_DEPTH = 128; if (require.main === module) { main(); @@ -612,7 +613,11 @@ function parseAttestationPayload(attestation) { return result; } -function readCborItem(bytes, start) { +function readCborItem(bytes, start, depth = 0) { + if (depth > MAX_CBOR_NESTING_DEPTH) { + throw new Error("CBOR nesting depth exceeded"); + } + const initial = bytes[start]; if (initial === undefined) { throw new Error("CBOR read out of bounds"); @@ -635,12 +640,12 @@ function readCborItem(bytes, start) { if (end >= bytes.length) { throw new Error("indefinite CBOR array missing break"); } - end = readCborItem(bytes, end).end; + end = readCborItem(bytes, end, depth + 1).end; } end++; } else { for (let i = 0n; i < value; ++i) { - end = readCborItem(bytes, end).end; + end = readCborItem(bytes, end, depth + 1).end; } } } else if (major === 5) { @@ -650,14 +655,14 @@ function readCborItem(bytes, start) { if (end >= bytes.length) { throw new Error("indefinite CBOR map missing break"); } - const key = readCborItem(bytes, end); - const mapValue = readCborItem(bytes, key.end); + const key = readCborItem(bytes, end, depth + 1); + const mapValue = readCborItem(bytes, key.end, depth + 1); end = mapValue.end; } end++; } else { for (let i = 0n; i < value * 2n; ++i) { - end = readCborItem(bytes, end).end; + end = readCborItem(bytes, end, depth + 1).end; } } } else if (major === 0 || major === 1 || major === 6 || major === 7) { @@ -750,6 +755,7 @@ function hexToBigInt(hex) { } module.exports = { + MAX_CBOR_NESTING_DEPTH, collectAttestationHintBytes, collectCertSignatureHintBytes, collectVerifyHintBytes, diff --git a/tools/p384_hints.test.js b/tools/p384_hints.test.js new file mode 100644 index 0000000..e5dc710 --- /dev/null +++ b/tools/p384_hints.test.js @@ -0,0 +1,26 @@ +"use strict"; + +const assert = require("assert"); +const test = require("node:test"); + +const { MAX_CBOR_NESTING_DEPTH, parseAttestationSignature } = require("./p384_hints"); + +test("parseAttestationSignature rejects excessive CBOR nesting", () => { + const protectedHeader = Buffer.from([0x44, 0xa1, 0x01, 0x38, 0x22]); + const unprotectedHeaderPrefix = Buffer.from([0xa1, 0x00]); + const nestedArrays = Buffer.alloc(MAX_CBOR_NESTING_DEPTH + 1, 0x81); + const terminalItem = Buffer.from([0x00]); + const attestation = Buffer.concat([ + Buffer.from([0x84]), + protectedHeader, + unprotectedHeaderPrefix, + nestedArrays, + terminalItem, + ]); + + assert.throws( + () => parseAttestationSignature(attestation), + (err) => + err instanceof Error && !(err instanceof RangeError) && err.message === "CBOR nesting depth exceeded", + ); +});