Merge pull request #10010 from Snuffleupagus/issue-10004

Attempt to find truncated endstream commands, in the fallback code-path, in `Parser.makeStream` (issue 10004)
This commit is contained in:
Tim van der Meij 2018-09-01 18:44:08 +02:00 committed by GitHub
commit 66bd088948
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 76 additions and 39 deletions

View File

@ -18,8 +18,8 @@ import {
PredictorStream, RunLengthStream PredictorStream, RunLengthStream
} from './stream'; } from './stream';
import { import {
assert, FormatError, info, isNum, isSpace, isString, MissingDataException, assert, bytesToString, FormatError, info, isNum, isSpace, isString,
StreamType, warn MissingDataException, StreamType, warn
} from '../shared/util'; } from '../shared/util';
import { import {
Cmd, Dict, EOF, isCmd, isDict, isEOF, isName, Name, Ref Cmd, Dict, EOF, isCmd, isDict, isEOF, isName, Name, Ref
@ -471,13 +471,45 @@ var Parser = (function ParserClosure() {
return imageStream; return imageStream;
}, },
_findStreamLength(startPos, signature) {
const { stream, } = this.lexer;
stream.pos = startPos;
const SCAN_BLOCK_LENGTH = 2048;
const signatureLength = signature.length;
while (stream.pos < stream.end) {
const scanBytes = stream.peekBytes(SCAN_BLOCK_LENGTH);
const scanLength = scanBytes.length - signatureLength;
if (scanLength <= 0) {
break;
}
let pos = 0;
while (pos < scanLength) {
let j = 0;
while (j < signatureLength && scanBytes[pos + j] === signature[j]) {
j++;
}
if (j >= signatureLength) { // `signature` found.
stream.pos += pos;
return (stream.pos - startPos);
}
pos++;
}
stream.pos += scanLength;
}
return -1;
},
makeStream: function Parser_makeStream(dict, cipherTransform) { makeStream: function Parser_makeStream(dict, cipherTransform) {
var lexer = this.lexer; var lexer = this.lexer;
var stream = lexer.stream; var stream = lexer.stream;
// get stream start position // get stream start position
lexer.skipToNextLine(); lexer.skipToNextLine();
var pos = stream.pos - 1; const startPos = stream.pos - 1;
// get length // get length
var length = dict.get('Length'); var length = dict.get('Length');
@ -487,52 +519,49 @@ var Parser = (function ParserClosure() {
} }
// skip over the stream data // skip over the stream data
stream.pos = pos + length; stream.pos = startPos + length;
lexer.nextChar(); lexer.nextChar();
// Shift '>>' and check whether the new object marks the end of the stream // Shift '>>' and check whether the new object marks the end of the stream
if (this.tryShift() && isCmd(this.buf2, 'endstream')) { if (this.tryShift() && isCmd(this.buf2, 'endstream')) {
this.shift(); // 'stream' this.shift(); // 'stream'
} else { } else {
// bad stream length, scanning for endstream // Bad stream length, scanning for endstream command.
stream.pos = pos; const ENDSTREAM_SIGNATURE = new Uint8Array([
var SCAN_BLOCK_SIZE = 2048; 0x65, 0x6E, 0x64, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6D]);
var ENDSTREAM_SIGNATURE_LENGTH = 9; let actualLength = this._findStreamLength(startPos,
var ENDSTREAM_SIGNATURE = [0x65, 0x6E, 0x64, 0x73, 0x74, 0x72, 0x65, ENDSTREAM_SIGNATURE);
0x61, 0x6D]; if (actualLength < 0) {
var skipped = 0, found = false, i, j; // Only allow limited truncation of the endstream signature,
while (stream.pos < stream.end) { // to prevent false positives.
var scanBytes = stream.peekBytes(SCAN_BLOCK_SIZE); const MAX_TRUNCATION = 1;
var scanLength = scanBytes.length - ENDSTREAM_SIGNATURE_LENGTH; // Check if the PDF generator included truncated endstream commands,
if (scanLength <= 0) { // such as e.g. "endstrea" (fixes issue10004.pdf).
break; for (let i = 1; i <= MAX_TRUNCATION; i++) {
} const end = ENDSTREAM_SIGNATURE.length - i;
found = false; const TRUNCATED_SIGNATURE = ENDSTREAM_SIGNATURE.slice(0, end);
i = 0;
while (i < scanLength) { let maybeLength = this._findStreamLength(startPos,
j = 0; TRUNCATED_SIGNATURE);
while (j < ENDSTREAM_SIGNATURE_LENGTH && if (maybeLength >= 0) {
scanBytes[i + j] === ENDSTREAM_SIGNATURE[j]) { // Ensure that the byte immediately following the truncated
j++; // endstream command is a space, to prevent false positives.
} const lastByte = stream.peekBytes(end + 1)[end];
if (j >= ENDSTREAM_SIGNATURE_LENGTH) { if (!isSpace(lastByte)) {
found = true; break;
}
info(`Found "${bytesToString(TRUNCATED_SIGNATURE)}" when ` +
'searching for endstream command.');
actualLength = maybeLength;
break; break;
} }
i++;
} }
if (found) {
skipped += i; if (actualLength < 0) {
stream.pos += i; throw new FormatError('Missing endstream command.');
break;
} }
skipped += scanLength;
stream.pos += scanLength;
} }
if (!found) { length = actualLength;
throw new FormatError('Missing endstream');
}
length = skipped;
lexer.nextChar(); lexer.nextChar();
this.shift(); this.shift();
@ -540,7 +569,7 @@ var Parser = (function ParserClosure() {
} }
this.shift(); // 'endstream' this.shift(); // 'endstream'
stream = stream.makeSubStream(pos, length, dict); stream = stream.makeSubStream(startPos, length, dict);
if (cipherTransform) { if (cipherTransform) {
stream = cipherTransform.createStream(stream, length); stream = cipherTransform.createStream(stream, length);
} }

View File

@ -0,0 +1 @@
https://github.com/mozilla/pdf.js/files/2315390/2371410.pdf

View File

@ -726,6 +726,13 @@
"link": false, "link": false,
"type": "load" "type": "load"
}, },
{ "id": "issue10004",
"file": "pdfs/issue10004.pdf",
"md5": "64d1853060cefe3be50e5c4617dd0505",
"rounds": 1,
"link": true,
"type": "load"
},
{ "id": "issue7507", { "id": "issue7507",
"file": "pdfs/issue7507.pdf", "file": "pdfs/issue7507.pdf",
"md5": "f7aeaafe0c89b94436e94eaa63307303", "md5": "f7aeaafe0c89b94436e94eaa63307303",