зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1598298 [wpt PR 20359] - Make DecompressionStream error checking stricter, a=testonly
Automatic update from web-platform-tests Make DecompressionStream error checking stricter Error the stream if the input to DecompressionStream is truncated, or there is junk after the end of the stream. This will help avoid accidental loss of data. Also add a thorough test of the behaviour when various fields of a compressed stream are corrupted. Also test that errors are correctly issued when the stream is truncated or has trailing junk. BUG=1014422 Change-Id: I8aceeafd279b75d4fea76d7edca19024856417c0 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1928428 Reviewed-by: Yutaka Hirano <yhirano@chromium.org> Commit-Queue: Adam Rice <ricea@chromium.org> Cr-Commit-Position: refs/heads/master@{#718543} -- wpt-commits: 157b71e48394fb63f91fa497be429c3543b0ab03 wpt-pr: 20359
This commit is contained in:
Родитель
6738cb55a4
Коммит
72e9effe26
|
@ -0,0 +1,299 @@
|
|||
// META global=worker
|
||||
|
||||
// This test checks that DecompressionStream behaves according to the standard
|
||||
// when the input is corrupted. To avoid a combinatorial explosion in the
|
||||
// number of tests, we only mutate one field at a time, and we only test
|
||||
// "interesting" values.
|
||||
|
||||
'use strict';
|
||||
|
||||
// The many different cases are summarised in this data structure.
|
||||
const expectations = [
|
||||
{
|
||||
format: 'deflate',
|
||||
|
||||
// Decompresses to 'expected output'.
|
||||
baseInput: [120, 156, 75, 173, 40, 72, 77, 46, 73, 77, 81, 200, 47, 45, 41,
|
||||
40, 45, 1, 0, 48, 173, 6, 36],
|
||||
|
||||
// See RFC1950 for the definition of the various fields used by deflate:
|
||||
// https://tools.ietf.org/html/rfc1950.
|
||||
fields: [
|
||||
{
|
||||
// The function of this field. This matches the name used in the RFC.
|
||||
name: 'CMF',
|
||||
|
||||
// The offset of the field in bytes from the start of the input.
|
||||
offset: 0,
|
||||
|
||||
// The length of the field in bytes.
|
||||
length: 1,
|
||||
|
||||
cases: [
|
||||
{
|
||||
// The value to set the field to. If the field contains multiple
|
||||
// bytes, all the bytes will be set to this value.
|
||||
value: 0,
|
||||
|
||||
// The expected result. 'success' means the input is decoded
|
||||
// successfully. 'error' means that the stream will be errored.
|
||||
result: 'error'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'FLG',
|
||||
offset: 1,
|
||||
length: 1,
|
||||
|
||||
// FLG contains a 4-bit checksum (FCHECK) which is calculated in such a
|
||||
// way that there are 4 valid values for this field.
|
||||
cases: [
|
||||
{
|
||||
value: 218,
|
||||
result: 'success'
|
||||
},
|
||||
{
|
||||
value: 1,
|
||||
result: 'success'
|
||||
},
|
||||
{
|
||||
value: 94,
|
||||
result: 'success'
|
||||
},
|
||||
{
|
||||
// The remaining 252 values cause an error.
|
||||
value: 157,
|
||||
result: 'error'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'DATA',
|
||||
// In general, changing any bit of the data will trigger a checksum
|
||||
// error. Only the last byte does anything else.
|
||||
offset: 18,
|
||||
length: 1,
|
||||
cases: [
|
||||
{
|
||||
value: 4,
|
||||
result: 'success'
|
||||
},
|
||||
{
|
||||
value: 5,
|
||||
result: 'error'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'ADLER',
|
||||
offset: -4,
|
||||
length: 4,
|
||||
cases: [
|
||||
{
|
||||
value: 255,
|
||||
result: 'error'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
format: 'gzip',
|
||||
|
||||
// Decompresses to 'expected output'.
|
||||
baseInput: [31, 139, 8, 0, 0, 0, 0, 0, 0, 3, 75, 173, 40, 72, 77, 46, 73,
|
||||
77, 81, 200, 47, 45, 41, 40, 45, 1, 0, 176, 1, 57, 179, 15, 0,
|
||||
0, 0],
|
||||
|
||||
// See RFC1952 for the definition of the various fields used by gzip:
|
||||
// https://tools.ietf.org/html/rfc1952.
|
||||
fields: [
|
||||
{
|
||||
name: 'ID',
|
||||
offset: 0,
|
||||
length: 2,
|
||||
cases: [
|
||||
{
|
||||
value: 255,
|
||||
result: 'error'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'CM',
|
||||
offset: 2,
|
||||
length: 1,
|
||||
cases: [
|
||||
{
|
||||
value: 0,
|
||||
result: 'error'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'FLG',
|
||||
offset: 3,
|
||||
length: 1,
|
||||
cases: [
|
||||
{
|
||||
value: 1, // FTEXT
|
||||
result: 'success'
|
||||
},
|
||||
{
|
||||
value: 2, // FHCRC
|
||||
result: 'error'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'MTIME',
|
||||
offset: 4,
|
||||
length: 4,
|
||||
cases: [
|
||||
{
|
||||
// Any value is valid for this field.
|
||||
value: 255,
|
||||
result: 'success'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'XFL',
|
||||
offset: 8,
|
||||
length: 1,
|
||||
cases: [
|
||||
{
|
||||
// Any value is accepted.
|
||||
value: 255,
|
||||
result: 'success'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'OS',
|
||||
offset: 9,
|
||||
length: 1,
|
||||
cases: [
|
||||
{
|
||||
// Any value is accepted.
|
||||
value: 128,
|
||||
result: 'success'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'DATA',
|
||||
|
||||
// The last byte of the data is the most interesting.
|
||||
offset: 26,
|
||||
length: 1,
|
||||
cases: [
|
||||
{
|
||||
value: 3,
|
||||
result: 'error'
|
||||
},
|
||||
{
|
||||
value: 4,
|
||||
result: 'success'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'CRC',
|
||||
offset: -8,
|
||||
length: 4,
|
||||
cases: [
|
||||
{
|
||||
// Any change will error the stream.
|
||||
value: 0,
|
||||
result: 'error'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'ISIZE',
|
||||
offset: -4,
|
||||
length: 4,
|
||||
cases: [
|
||||
{
|
||||
// A mismatch will error the stream.
|
||||
value: 1,
|
||||
result: 'error'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
async function tryDecompress(input, format) {
|
||||
const ds = new DecompressionStream(format);
|
||||
const reader = ds.readable.getReader();
|
||||
const writer = ds.writable.getWriter();
|
||||
writer.write(input).catch(() => {});
|
||||
writer.close().catch(() => {});
|
||||
let out = [];
|
||||
while (true) {
|
||||
try {
|
||||
const { value, done } = await reader.read();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
out = out.concat(Array.from(value));
|
||||
} catch (e) {
|
||||
return { result: 'error' };
|
||||
}
|
||||
}
|
||||
const expectedOutput = 'expected output';
|
||||
if (out.length !== expectedOutput.length) {
|
||||
return { result: 'corrupt' };
|
||||
}
|
||||
for (let i = 0; i < out.length; ++i) {
|
||||
if (out[i] !== expectedOutput.charCodeAt(i)) {
|
||||
return { result: 'corrupt' };
|
||||
}
|
||||
}
|
||||
return { result: 'success' };
|
||||
}
|
||||
|
||||
function corruptInput(input, offset, length, value) {
|
||||
const output = new Uint8Array(input);
|
||||
if (offset < 0) {
|
||||
offset += input.length;
|
||||
}
|
||||
for (let i = offset; i < offset + length; ++i) {
|
||||
output[i] = value;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
for (const { format, baseInput, fields } of expectations) {
|
||||
promise_test(async () => {
|
||||
const { result } = await tryDecompress(new Uint8Array(baseInput), format);
|
||||
assert_equals(result, 'success', 'decompression should succeed');
|
||||
}, `the unchanged input for '${format}' should decompress successfully`);
|
||||
|
||||
promise_test(async () => {
|
||||
const truncatedInput = new Uint8Array(baseInput.slice(0, -1));
|
||||
const { result } = await tryDecompress(truncatedInput, format);
|
||||
assert_equals(result, 'error', 'decompression should fail');
|
||||
}, `truncating the input for '${format}' should give an error`);
|
||||
|
||||
promise_test(async () => {
|
||||
const extendedInput = new Uint8Array(baseInput.concat([0]));
|
||||
const { result } = await tryDecompress(extendedInput, format);
|
||||
assert_equals(result, 'error', 'decompression should fail');
|
||||
}, `trailing junk for '${format}' should give an error`);
|
||||
|
||||
for (const { name, offset, length, cases } of fields) {
|
||||
for (const { value, result } of cases) {
|
||||
promise_test(async () => {
|
||||
const corruptedInput = corruptInput(baseInput, offset, length, value);
|
||||
const { result: actual } =
|
||||
await tryDecompress(corruptedInput, format);
|
||||
assert_equals(actual, result, 'result should match');
|
||||
}, `format '${format}' field ${name} should be ${result} for ${value}`);
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче