[Pkg-javascript-commits] [pdf.js] 65/414: Add validation for callsubr and callgsubr for type 2 charstrings.
David Prévot
taffit at moszumanska.debian.org
Tue Jun 28 17:12:06 UTC 2016
This is an automated email from the git hooks/post-receive script.
taffit pushed a commit to branch master
in repository pdf.js.
commit eb7c36beb6c69c27e458e187f5cc8af81f810a0b
Author: Brendan Dahl <brendan.dahl at gmail.com>
Date: Tue Jan 5 09:53:31 2016 -0800
Add validation for callsubr and callgsubr for type 2 charstrings.
---
src/core/fonts.js | 322 ++++++++++++++++++++++++++--------------
test/pdfs/.gitignore | 2 +
test/pdfs/issue3694_reduced.pdf | Bin 0 -> 24550 bytes
test/pdfs/issue6286.pdf | Bin 0 -> 1909445 bytes
test/test_manifest.json | 12 ++
test/unit/font_spec.js | 12 +-
6 files changed, 234 insertions(+), 114 deletions(-)
diff --git a/src/core/fonts.js b/src/core/fonts.js
index b67a0c6..95ee7fd 100644
--- a/src/core/fonts.js
+++ b/src/core/fonts.js
@@ -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;
- 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 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;
- }
+ 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) {
- hints += stackSize >> 1;
- j += (hints + 7) >> 3; // skipping right amount of hints flag data
- stackSize %= 2;
- validationCommand = CharstringValidationData[value];
+ }
+ 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;
}
- if (validationCommand) {
- if (validationCommand.stem) {
- hints += stackSize >> 1;
+ 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 ('min' in validationCommand) {
- if (!undefStack && stackSize < validationCommand.min) {
- warn('Not enough parameters for ' + validationCommand.id +
- '; actual: ' + stackSize +
- ', expected: ' + validationCommand.min);
- valid = false;
- break;
- }
+ }
+ 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 (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 (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;
- undefStack = false;
- } else if (validationCommand.undefStack) {
- stackSize = 0;
- undefStack = true;
- firstStackClearing = false;
+ }
+ 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 state = {
+ callDepth: 0,
+ stackSize: 0,
+ stack: [],
+ undefStack: true,
+ hints: 0,
+ firstStackClearing: true,
+ seac: null,
+ width: null
+ };
+ var valid = true;
+ 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 (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;
})();
diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore
index 6237b25..74aa953 100644
--- a/test/pdfs/.gitignore
+++ b/test/pdfs/.gitignore
@@ -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
diff --git a/test/pdfs/issue3694_reduced.pdf b/test/pdfs/issue3694_reduced.pdf
new file mode 100644
index 0000000..37dfd5d
Binary files /dev/null and b/test/pdfs/issue3694_reduced.pdf differ
diff --git a/test/pdfs/issue6286.pdf b/test/pdfs/issue6286.pdf
new file mode 100644
index 0000000..d5e9b47
Binary files /dev/null and b/test/pdfs/issue6286.pdf differ
diff --git a/test/test_manifest.json b/test/test_manifest.json
index cbd1300..4b43559 100644
--- a/test/test_manifest.json
+++ b/test/test_manifest.json
@@ -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",
diff --git a/test/unit/font_spec.js b/test/unit/font_spec.js
index 5b05f90..66c239b 100644
--- a/test/unit/font_spec.js
+++ b/test/unit/font_spec.js
@@ -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);
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/pdf.js.git
More information about the Pkg-javascript-commits
mailing list