Merge pull request #6724 from brendandahl/callsubr

Add validation for callsubr and callgsubr for type 2 charstrings.
This commit is contained in:
Yury Delendik 2016-01-05 13:46:14 -06:00
commit 62ade3e373
6 changed files with 235 additions and 115 deletions

View File

@ -80,6 +80,9 @@ var HINTING_ENABLED = false;
// to control analysis of seac charstrings.
var SEAC_ANALYSIS_ENABLED = false;
// Maximum subroutine call depth of type 2 chartrings. Matches OTS.
var MAX_SUBR_NESTING = 10;
var FontFlags = {
FixedPitch: 1,
Serif: 2,
@ -6109,10 +6112,7 @@ var CFFParser = (function CFFParserClosure() {
cff.isCIDFont = topDict.hasName('ROS');
var charStringOffset = topDict.getByName('CharStrings');
var charStringsAndSeacs = this.parseCharStrings(charStringOffset);
cff.charStrings = charStringsAndSeacs.charStrings;
cff.seacs = charStringsAndSeacs.seacs;
cff.widths = charStringsAndSeacs.widths;
var charStringIndex = this.parseIndex(charStringOffset).obj;
var fontMatrix = topDict.getByName('FontMatrix');
if (fontMatrix) {
@ -6140,19 +6140,30 @@ var CFFParser = (function CFFParserClosure() {
// cid fonts don't have an encoding
encoding = null;
charset = this.parseCharsets(topDict.getByName('charset'),
cff.charStrings.count, cff.strings, true);
charStringIndex.count, cff.strings, true);
cff.fdSelect = this.parseFDSelect(topDict.getByName('FDSelect'),
cff.charStrings.count);
charStringIndex.count);
} else {
charset = this.parseCharsets(topDict.getByName('charset'),
cff.charStrings.count, cff.strings, false);
charStringIndex.count, cff.strings, false);
encoding = this.parseEncoding(topDict.getByName('Encoding'),
properties,
cff.strings, charset.charset);
}
cff.charset = charset;
cff.encoding = encoding;
var charStringsAndSeacs = this.parseCharStrings(
charStringIndex,
topDict.privateDict.subrsIndex,
globalSubrIndex.obj,
cff.fdSelect,
cff.fdArray);
cff.charStrings = charStringsAndSeacs.charStrings;
cff.seacs = charStringsAndSeacs.seacs;
cff.widths = charStringsAndSeacs.widths;
return cff;
},
parseHeader: function CFFParser_parseHeader() {
@ -6327,118 +6338,201 @@ var CFFParser = (function CFFParserClosure() {
}
return cffDict;
},
parseCharStrings: function CFFParser_parseCharStrings(charStringOffset) {
var charStrings = this.parseIndex(charStringOffset).obj;
parseCharString: function CFFParser_parseCharString(state, data,
localSubrIndex,
globalSubrIndex) {
if (state.callDepth > MAX_SUBR_NESTING) {
return false;
}
var stackSize = state.stackSize;
var stack = state.stack;
var length = data.length;
for (var j = 0; j < length;) {
var value = data[j++];
var validationCommand = null;
if (value === 12) {
var q = data[j++];
if (q === 0) {
// The CFF specification state that the 'dotsection' command
// (12, 0) is deprecated and treated as a no-op, but all Type2
// charstrings processors should support them. Unfortunately
// the font sanitizer don't. As a workaround the sequence (12, 0)
// is replaced by a useless (0, hmoveto).
data[j - 2] = 139;
data[j - 1] = 22;
stackSize = 0;
} else {
validationCommand = CharstringValidationData12[q];
}
} else if (value === 28) { // number (16 bit)
stack[stackSize] = ((data[j] << 24) | (data[j + 1] << 16)) >> 16;
j += 2;
stackSize++;
} else if (value === 14) {
if (stackSize >= 4) {
stackSize -= 4;
if (SEAC_ANALYSIS_ENABLED) {
state.seac = stack.slice(stackSize, stackSize + 4);
return false;
}
}
validationCommand = CharstringValidationData[value];
} else if (value >= 32 && value <= 246) { // number
stack[stackSize] = value - 139;
stackSize++;
} else if (value >= 247 && value <= 254) { // number (+1 bytes)
stack[stackSize] = (value < 251 ?
((value - 247) << 8) + data[j] + 108 :
-((value - 251) << 8) - data[j] - 108);
j++;
stackSize++;
} else if (value === 255) { // number (32 bit)
stack[stackSize] = ((data[j] << 24) | (data[j + 1] << 16) |
(data[j + 2] << 8) | data[j + 3]) / 65536;
j += 4;
stackSize++;
} else if (value === 19 || value === 20) {
state.hints += stackSize >> 1;
// skipping right amount of hints flag data
j += (state.hints + 7) >> 3;
stackSize %= 2;
validationCommand = CharstringValidationData[value];
} else if (value === 10 || value === 29) {
var subrsIndex;
if (value === 10) {
subrsIndex = localSubrIndex;
} else {
subrsIndex = globalSubrIndex;
}
if (!subrsIndex) {
validationCommand = CharstringValidationData[value];
warn('Missing subrsIndex for ' + validationCommand.id);
return false;
}
var bias = 32768;
if (subrsIndex.count < 1240) {
bias = 107;
} else if (subrsIndex.count < 33900) {
bias = 1131;
}
var subrNumber = stack[--stackSize] + bias;
if (subrNumber < 0 || subrNumber >= subrsIndex.count) {
validationCommand = CharstringValidationData[value];
warn('Out of bounds subrIndex for ' + validationCommand.id);
return false;
}
state.stackSize = stackSize;
state.callDepth++;
var valid = this.parseCharString(state, subrsIndex.get(subrNumber),
localSubrIndex, globalSubrIndex);
if (!valid) {
return false;
}
state.callDepth--;
stackSize = state.stackSize;
continue;
} else if (value === 11) {
state.stackSize = stackSize;
return true;
} else {
validationCommand = CharstringValidationData[value];
}
if (validationCommand) {
if (validationCommand.stem) {
state.hints += stackSize >> 1;
}
if ('min' in validationCommand) {
if (!state.undefStack && stackSize < validationCommand.min) {
warn('Not enough parameters for ' + validationCommand.id +
'; actual: ' + stackSize +
', expected: ' + validationCommand.min);
return false;
}
}
if (state.firstStackClearing && validationCommand.stackClearing) {
state.firstStackClearing = false;
// the optional character width can be found before the first
// stack-clearing command arguments
stackSize -= validationCommand.min;
if (stackSize >= 2 && validationCommand.stem) {
// there are even amount of arguments for stem commands
stackSize %= 2;
} else if (stackSize > 1) {
warn('Found too many parameters for stack-clearing command');
}
if (stackSize > 0 && stack[stackSize - 1] >= 0) {
state.width = stack[stackSize - 1];
}
}
if ('stackDelta' in validationCommand) {
if ('stackFn' in validationCommand) {
validationCommand.stackFn(stack, stackSize);
}
stackSize += validationCommand.stackDelta;
} else if (validationCommand.stackClearing) {
stackSize = 0;
} else if (validationCommand.resetStack) {
stackSize = 0;
state.undefStack = false;
} else if (validationCommand.undefStack) {
stackSize = 0;
state.undefStack = true;
state.firstStackClearing = false;
}
}
}
state.stackSize = stackSize;
return true;
},
parseCharStrings: function CFFParser_parseCharStrings(charStrings,
localSubrIndex,
globalSubrIndex,
fdSelect,
fdArray) {
var seacs = [];
var widths = [];
var count = charStrings.count;
for (var i = 0; i < count; i++) {
var charstring = charStrings.get(i);
var stackSize = 0;
var stack = [];
var undefStack = true;
var hints = 0;
var state = {
callDepth: 0,
stackSize: 0,
stack: [],
undefStack: true,
hints: 0,
firstStackClearing: true,
seac: null,
width: null
};
var valid = true;
var data = charstring;
var length = data.length;
var firstStackClearing = true;
for (var j = 0; j < length;) {
var value = data[j++];
var validationCommand = null;
if (value === 12) {
var q = data[j++];
if (q === 0) {
// The CFF specification state that the 'dotsection' command
// (12, 0) is deprecated and treated as a no-op, but all Type2
// charstrings processors should support them. Unfortunately
// the font sanitizer don't. As a workaround the sequence (12, 0)
// is replaced by a useless (0, hmoveto).
data[j - 2] = 139;
data[j - 1] = 22;
stackSize = 0;
} else {
validationCommand = CharstringValidationData12[q];
}
} else if (value === 28) { // number (16 bit)
stack[stackSize] = ((data[j] << 24) | (data[j + 1] << 16)) >> 16;
j += 2;
stackSize++;
} else if (value === 14) {
if (stackSize >= 4) {
stackSize -= 4;
if (SEAC_ANALYSIS_ENABLED) {
seacs[i] = stack.slice(stackSize, stackSize + 4);
valid = false;
}
}
validationCommand = CharstringValidationData[value];
} else if (value >= 32 && value <= 246) { // number
stack[stackSize] = value - 139;
stackSize++;
} else if (value >= 247 && value <= 254) { // number (+1 bytes)
stack[stackSize] = (value < 251 ?
((value - 247) << 8) + data[j] + 108 :
-((value - 251) << 8) - data[j] - 108);
j++;
stackSize++;
} else if (value === 255) { // number (32 bit)
stack[stackSize] = ((data[j] << 24) | (data[j + 1] << 16) |
(data[j + 2] << 8) | data[j + 3]) / 65536;
j += 4;
stackSize++;
} else if (value === 19 || value === 20) {
hints += stackSize >> 1;
j += (hints + 7) >> 3; // skipping right amount of hints flag data
stackSize %= 2;
validationCommand = CharstringValidationData[value];
} else {
validationCommand = CharstringValidationData[value];
var localSubrToUse = null;
if (fdSelect && fdArray.length) {
var fdIndex = fdSelect.getFDIndex(i);
if (fdIndex === -1) {
warn('Glyph index is not in fd select.');
valid = false;
}
if (validationCommand) {
if (validationCommand.stem) {
hints += stackSize >> 1;
}
if ('min' in validationCommand) {
if (!undefStack && stackSize < validationCommand.min) {
warn('Not enough parameters for ' + validationCommand.id +
'; actual: ' + stackSize +
', expected: ' + validationCommand.min);
valid = false;
break;
}
}
if (firstStackClearing && validationCommand.stackClearing) {
firstStackClearing = false;
// the optional character width can be found before the first
// stack-clearing command arguments
stackSize -= validationCommand.min;
if (stackSize >= 2 && validationCommand.stem) {
// there are even amount of arguments for stem commands
stackSize %= 2;
} else if (stackSize > 1) {
warn('Found too many parameters for stack-clearing command');
}
if (stackSize > 0 && stack[stackSize - 1] >= 0) {
widths[i] = stack[stackSize - 1];
}
}
if ('stackDelta' in validationCommand) {
if ('stackFn' in validationCommand) {
validationCommand.stackFn(stack, stackSize);
}
stackSize += validationCommand.stackDelta;
} else if (validationCommand.stackClearing) {
stackSize = 0;
} else if (validationCommand.resetStack) {
stackSize = 0;
undefStack = false;
} else if (validationCommand.undefStack) {
stackSize = 0;
undefStack = true;
firstStackClearing = false;
}
if (fdIndex >= fdArray.length) {
warn('Invalid fd index for glyph index.');
valid = false;
}
if (valid) {
localSubrToUse = fdArray[fdIndex].privateDict.subrsIndex;
}
} else if (localSubrIndex) {
localSubrToUse = localSubrIndex;
}
if (valid) {
valid = this.parseCharString(state, charstring, localSubrToUse,
globalSubrIndex);
}
if (state.width !== null) {
widths[i] = state.width;
}
if (state.seac !== null) {
seacs[i] = state.seac;
}
if (!valid) {
// resetting invalid charstring to single 'endchar'
@ -6933,6 +7027,14 @@ var CFFFDSelect = (function CFFFDSelectClosure() {
this.fdSelect = fdSelect;
this.raw = raw;
}
CFFFDSelect.prototype = {
getFDIndex: function CFFFDSelect_get(glyphIndex) {
if (glyphIndex < 0 || glyphIndex >= this.fdSelect.length) {
return -1;
}
return this.fdSelect[glyphIndex];
}
};
return CFFFDSelect;
})();

View File

@ -26,6 +26,7 @@
!issue5564_reduced.pdf
!canvas.pdf
!complex_ttf_font.pdf
!issue3694_reduced.pdf
!extgstate.pdf
!rotation.pdf
!simpletype3font.pdf
@ -38,6 +39,7 @@
!close-path-bug.pdf
!issue6019.pdf
!issue6621.pdf
!issue6286.pdf
!issue1055r.pdf
!issue1293r.pdf
!issue1655r.pdf

Binary file not shown.

BIN
test/pdfs/issue6286.pdf Normal file

Binary file not shown.

View File

@ -376,6 +376,18 @@
"rounds": 1,
"type": "load"
},
{ "id": "issue6286",
"file": "pdfs/issue6286.pdf",
"md5": "d13fd1b98fb1c9980356314fd1d3a91b",
"rounds": 1,
"type": "eq"
},
{ "id": "issue3694_reduced",
"file": "pdfs/issue_3694_reduced.pdf",
"md5": "c1438c7bad12d70c4cd684f8ce04448f",
"rounds": 1,
"type": "eq"
},
{ "id": "bug847420",
"file": "pdfs/bug847420.pdf",
"md5": "0decd96fec4ef858c2c663a6de24e887",

View File

@ -101,7 +101,8 @@ describe('font', function() {
14 // endchar
]);
parser.bytes = bytes;
var charStrings = parser.parseCharStrings(0).charStrings;
var charStringsIndex = parser.parseIndex(0).obj;
var charStrings = parser.parseCharStrings(charStringsIndex).charStrings;
expect(charStrings.count).toEqual(1);
// shoudn't be sanitized
expect(charStrings.get(0).length).toEqual(38);
@ -116,7 +117,8 @@ describe('font', function() {
0, // offset[0]
237, 247, 22, 247, 72, 204, 247, 86, 14]);
parser.bytes = bytes;
var result = parser.parseCharStrings(0);
var charStringsIndex = parser.parseIndex(0).obj;
var result = parser.parseCharStrings(charStringsIndex);
expect(result.charStrings.count).toEqual(1);
expect(result.charStrings.get(0).length).toEqual(1);
expect(result.seacs.length).toEqual(1);
@ -139,7 +141,8 @@ describe('font', function() {
0, // offset[0]
237, 247, 22, 247, 72, 204, 247, 86, 14]);
parser.bytes = bytes;
var result = parser.parseCharStrings(0);
var charStringsIndex = parser.parseIndex(0).obj;
var result = parser.parseCharStrings(charStringsIndex);
expect(result.charStrings.count).toEqual(1);
expect(result.charStrings.get(0).length).toEqual(9);
expect(result.seacs.length).toEqual(0);
@ -154,7 +157,8 @@ describe('font', function() {
0, // offset[0]
14]);
parser.bytes = bytes;
var result = parser.parseCharStrings(0);
var charStringsIndex = parser.parseIndex(0).obj;
var result = parser.parseCharStrings(charStringsIndex);
expect(result.charStrings.count).toEqual(1);
expect(result.charStrings.get(0)[0]).toEqual(14);
expect(result.seacs.length).toEqual(0);