[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