mirror of
https://github.com/bulwarkmail/native.git
synced 2026-10-31 16:06:32 +00:00
fix: stop a crafted winmail.dat from freezing the app
This commit is contained in:
@@ -65,6 +65,33 @@ describe('parseTnef', () => {
|
|||||||
expect(res.htmlBody).toBe('<p>hi</p>');
|
expect(res.htmlBody).toBe('<p>hi</p>');
|
||||||
expect(res.attachments[0]).toMatchObject({ name: 'report.pdf', mimeType: 'application/pdf' });
|
expect(res.attachments[0]).toMatchObject({ name: 'report.pdf', mimeType: 'application/pdf' });
|
||||||
});
|
});
|
||||||
|
// A sender-chosen value count of 2^32 - 1 over a block with no room for a
|
||||||
|
// single value used to spin the JS thread on reads that never advance.
|
||||||
|
it('does not spin on a value count the block cannot hold', () => {
|
||||||
|
const mapiVar = [
|
||||||
|
...u32(1),
|
||||||
|
0x02, 0x01, 0x01, 0x00, // PT_BINARY, id 0x0001
|
||||||
|
...u32(0xffffffff),
|
||||||
|
0x00, 0x00,
|
||||||
|
];
|
||||||
|
const mapiFixed = [
|
||||||
|
...u32(1),
|
||||||
|
0x99, 0x10, 0x01, 0x00, // multi-value of an unknown fixed type
|
||||||
|
...u32(0xffffffff),
|
||||||
|
0x00, 0x00,
|
||||||
|
];
|
||||||
|
for (const mapi of [mapiVar, mapiFixed]) {
|
||||||
|
const data = new Uint8Array([
|
||||||
|
...u32(0x223e9f78), 0x00, 0x00,
|
||||||
|
...attr(0x01, 0x0002800c, bytes('Hello body')),
|
||||||
|
...attr(0x01, 0x00069003, mapi),
|
||||||
|
]);
|
||||||
|
const started = Date.now();
|
||||||
|
const res = parseTnef(data);
|
||||||
|
expect(Date.now() - started).toBeLessThan(1000);
|
||||||
|
expect(res.body).toBe('Hello body');
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('isTnefAttachment', () => {
|
describe('isTnefAttachment', () => {
|
||||||
|
|||||||
+18
-3
@@ -165,6 +165,15 @@ function decodeMAPIString(data: Uint8Array, propType: number): string {
|
|||||||
return decodeUtf8(data.subarray(0, len));
|
return decodeUtf8(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 JS thread.
|
||||||
|
*/
|
||||||
|
function readValueCount(r: BinaryReader): number {
|
||||||
|
return Math.min(r.readUint32LE(), Math.floor(r.remaining / 4));
|
||||||
|
}
|
||||||
|
|
||||||
function parseMAPIProps(data: Uint8Array): Map<number, { type: number; value: Uint8Array | number | null }> {
|
function parseMAPIProps(data: Uint8Array): Map<number, { type: number; value: Uint8Array | number | null }> {
|
||||||
const props = new Map<number, { type: number; value: Uint8Array | number | null }>();
|
const props = new Map<number, { type: number; value: Uint8Array | number | null }>();
|
||||||
const r = new BinaryReader(data);
|
const r = new BinaryReader(data);
|
||||||
@@ -198,19 +207,25 @@ function parseMAPIProps(data: Uint8Array): Map<number, { type: number; value: Ui
|
|||||||
|
|
||||||
if (isVarLengthType(baseType)) {
|
if (isVarLengthType(baseType)) {
|
||||||
if (r.remaining < 4) break;
|
if (r.remaining < 4) break;
|
||||||
const valueCount = r.readUint32LE();
|
const valueCount = readValueCount(r);
|
||||||
let lastValue: Uint8Array | null = null;
|
let lastValue: Uint8Array | null = null;
|
||||||
for (let j = 0; j < valueCount && r.remaining > 0; j++) {
|
for (let j = 0; j < valueCount && r.remaining > 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) {
|
if (!isMultiValue && lastValue) {
|
||||||
props.set(propID, { type: propType, value: lastValue });
|
props.set(propID, { type: propType, value: lastValue });
|
||||||
}
|
}
|
||||||
} else if (isMultiValue) {
|
} else if (isMultiValue) {
|
||||||
if (r.remaining < 4) break;
|
if (r.remaining < 4) break;
|
||||||
const valueCount = r.readUint32LE();
|
const valueCount = readValueCount(r);
|
||||||
for (let j = 0; j < valueCount && r.remaining > 0; j++) {
|
for (let j = 0; j < valueCount && r.remaining > 0; j++) {
|
||||||
|
const before = r.remaining;
|
||||||
readMAPIFixedValue(r, baseType);
|
readMAPIFixedValue(r, baseType);
|
||||||
|
if (r.remaining >= before) break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const value = readMAPIFixedValue(r, baseType);
|
const value = readMAPIFixedValue(r, baseType);
|
||||||
|
|||||||
Reference in New Issue
Block a user