diff --git a/src/index.ts b/src/index.ts index 742c8ac..04e3968 100644 --- a/src/index.ts +++ b/src/index.ts @@ -569,6 +569,8 @@ const rzb = (dat: Uint8Array, st: DZstdState, out?: Uint8Array) => { st.o[0] = off; } else off = st.o[0]; } + // decoded output must stay within the block buffer + if (oubt + ll + ml > buf.length) err(0); for (let i = 0; i < ll; ++i) { buf[oubt + i] = buf[spl + i]; } diff --git a/tests/dos_malformed_sequences_test.ts b/tests/dos_malformed_sequences_test.ts new file mode 100644 index 0000000..ba7e374 --- /dev/null +++ b/tests/dos_malformed_sequences_test.ts @@ -0,0 +1,16 @@ +import { assertThrows } from "https://deno.land/std@0.103.0/testing/asserts.ts"; +import * as fzstd from "../src/index.ts"; + +// A 17-byte frame with a compressed block whose sequence section uses RLE-mode +// FSE tables to force every sequence to emit match-length code 52 (baseline +// 65539). The declared regenerated size (window) is 240 bytes, so the sequences +// claim far more output than the block can hold. A conformant decoder rejects +// this; without a bound the sequence loop performs unbounded out-of-bounds +// copies (CPU denial-of-service). +Deno.test("Malformed sequence overflow is rejected", () => { + const data = [ + 0x28, 0xB5, 0x2F, 0xFD, 0x20, 0xF0, 0x45, 0x00, 0x00, + 0x01, 0x00, 0x7F, 0x54, 0x00, 0x00, 0x34, 0x80 + ]; + assertThrows(() => fzstd.decompress(new Uint8Array(data))); +});