feat: add bounded MSZIP cabinet extraction

This commit is contained in:
2026-07-22 19:01:50 +02:00
parent a0c26d604d
commit d282b832ac
15 changed files with 318 additions and 48 deletions

View File

@@ -13,6 +13,7 @@ import { checkCancellation, yieldForCancellation } from './cancellation.js';
import { calculateDataBlockChecksum } from './checksum.js';
import { fail, warning } from './errors.js';
import { LzxDecoder } from './lzx/decoder.js';
import { MsZipDecoder } from './mszip/decoder.js';
import { decodeCabFileName, normalizeCabPath } from './path.js';
import {
resolveLimits,
@@ -30,6 +31,7 @@ const FLAG_NEXT_CABINET = 0x0002;
const FLAG_RESERVE_PRESENT = 0x0004;
const ATTRIBUTE_NAME_IS_UTF8 = 0x0080;
const MAX_CAB_BLOCK_OUTPUT = 32 * 1024;
const MAX_MSZIP_BLOCK_INPUT = 32 * 1024 + 12;
interface CabDataBlock {
headerOffset: number;
@@ -458,6 +460,16 @@ function parseCabinet(
{ offset: headerOffset + 4, structure: 'CFDATA' }
);
}
if (
folder.compression.kind === 'mszip' &&
compressedSize > MAX_MSZIP_BLOCK_INPUT
) {
fail(
'cab-invalid-mszip-block-size',
`MSZIP CAB data block size ${compressedSize} exceeds ${MAX_MSZIP_BLOCK_INPUT} bytes.`,
{ offset: headerOffset + 4, structure: 'CFDATA.cbData' }
);
}
if (
folder.compression.kind === 'none' &&
compressedSize !== uncompressedSize
@@ -589,10 +601,7 @@ function parseCabinet(
);
}
for (const folder of folders) {
if (
folder.compression.kind === 'mszip' ||
folder.compression.kind === 'quantum'
) {
if (folder.compression.kind === 'quantum') {
diagnostics.push(
warning(
'cab-compression-unsupported',
@@ -646,10 +655,7 @@ export async function extractCabinet(
const extractedEntries = [];
for (const folder of parsed.folders) {
checkCancellation(options.cancellation);
if (
folder.compression.kind === 'mszip' ||
folder.compression.kind === 'quantum'
) {
if (folder.compression.kind === 'quantum') {
fail(
'cab-compression-unsupported',
`${folder.compression.label} extraction is not implemented.`,
@@ -665,6 +671,8 @@ export async function extractCabinet(
checkCancellation: () => checkCancellation(options.cancellation),
})
: undefined;
const msZipDecoder =
folder.compression.kind === 'mszip' ? new MsZipDecoder() : undefined;
let outputOffset = 0;
for (const block of folder.blocks) {
checkCancellation(options.cancellation);
@@ -688,9 +696,11 @@ export async function extractCabinet(
}
}
const output =
decoder === undefined
? compressed
: decoder.decompressNext(compressed, block.uncompressedSize);
decoder !== undefined
? decoder.decompressNext(compressed, block.uncompressedSize)
: msZipDecoder !== undefined
? msZipDecoder.decompressNext(compressed, block.uncompressedSize)
: compressed;
if (output.length !== block.uncompressedSize) {
fail(
'cab-output-size-mismatch',