diff --git a/lib/__tests__/tnef.test.ts b/lib/__tests__/tnef.test.ts index a3e64c453..67cfc7f7a 100644 Binary files a/lib/__tests__/tnef.test.ts and b/lib/__tests__/tnef.test.ts differ diff --git a/lib/tnef.ts b/lib/tnef.ts index f154048a5..a620fd17a 100644 --- a/lib/tnef.ts +++ b/lib/tnef.ts @@ -165,6 +165,15 @@ function decodeMAPIString(data: Uint8Array, propType: number): string { return new TextDecoder('utf-8').decode(data.subarray(0, len)); } +/** + * Read a multi-value count. Every value takes at least four bytes, so a count + * larger than what is left of the block is a lie; clamp it rather than loop + * up to 2^32 times on the UI thread. + */ +function readValueCount(r: BinaryReader): number { + return Math.min(r.readUint32LE(), Math.floor(r.remaining / 4)); +} + /** Parse MAPI properties from a raw attribute data block */ function parseMAPIProps(data: Uint8Array): Map { const props = new Map(); @@ -200,19 +209,25 @@ function parseMAPIProps(data: Uint8Array): Map 0; j++) { - lastValue = readMAPIVarValue(r); + const value = readMAPIVarValue(r); + // A truncated value consumes nothing; retrying it would spin on the + // same bytes for as many rounds as the sender's count asks for. + if (value === null) break; + lastValue = value; } if (!isMultiValue && lastValue) { props.set(propID, { type: propType, value: lastValue }); } } else if (isMultiValue) { if (r.remaining < 4) break; - const valueCount = r.readUint32LE(); + const valueCount = readValueCount(r); for (let j = 0; j < valueCount && r.remaining > 0; j++) { + const before = r.remaining; readMAPIFixedValue(r, baseType); + if (r.remaining >= before) break; } } else { const value = readMAPIFixedValue(r, baseType);