From 3e77cf6b32188bfe433fa48567418576dcdb7700 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Fri, 23 Sep 2016 14:10:27 +0200 Subject: [PATCH 1/2] Prevent an infinite loop in `XRef_fetchUncompressed` for encrypted PDF files with indirect objects in the /Encrypt dictionary (issue 7665) --- src/core/crypto.js | 15 +++++++++++++-- src/core/obj.js | 13 +++++++++---- src/core/primitives.js | 21 +++++++++++---------- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/core/crypto.js b/src/core/crypto.js index f30d9a69aa55a..dba8f1064e00c 100644 --- a/src/core/crypto.js +++ b/src/core/crypto.js @@ -31,11 +31,12 @@ var PasswordException = sharedUtil.PasswordException; var PasswordResponses = sharedUtil.PasswordResponses; var bytesToString = sharedUtil.bytesToString; +var warn = sharedUtil.warn; var error = sharedUtil.error; +var assert = sharedUtil.assert; var isInt = sharedUtil.isInt; var stringToBytes = sharedUtil.stringToBytes; var utf8StringToString = sharedUtil.utf8StringToString; -var warn = sharedUtil.warn; var Name = corePrimitives.Name; var isName = corePrimitives.isName; var isDict = corePrimitives.isDict; @@ -1932,6 +1933,7 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { var cfDict = dict.get('CF'); var streamCryptoName = dict.get('StmF'); if (isDict(cfDict) && isName(streamCryptoName)) { + cfDict.suppressEncryption = true; // See comment below. var handlerDict = cfDict.get(streamCryptoName.name); keyLength = (handlerDict && handlerDict.get('Length')) || 128; if (keyLength < 40) { @@ -2013,7 +2015,15 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { this.encryptionKey = encryptionKey; if (algorithm >= 4) { - this.cf = dict.get('CF'); + var cf = dict.get('CF'); + if (isDict(cf)) { + // The 'CF' dictionary itself should not be encrypted, and by setting + // `suppressEncryption` we can prevent an infinite loop inside of + // `XRef_fetchUncompressed` if the dictionary contains indirect objects + // (fixes issue7665.pdf). + cf.suppressEncryption = true; + } + this.cf = cf; this.stmf = dict.get('StmF') || identityName; this.strf = dict.get('StrF') || identityName; this.eff = dict.get('EFF') || this.stmf; @@ -2041,6 +2051,7 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { } function buildCipherConstructor(cf, name, num, gen, key) { + assert(isName(name), 'Invalid crypt filter name.'); var cryptFilter = cf.get(name.name); var cfm; if (cryptFilter !== null && cryptFilter !== undefined) { diff --git a/src/core/obj.js b/src/core/obj.js index e37a0eea3542b..a839d95e38019 100644 --- a/src/core/obj.js +++ b/src/core/obj.js @@ -633,6 +633,11 @@ var XRef = (function XRefClosure() { if (encrypt) { var ids = trailerDict.get('ID'); var fileId = (ids && ids.length) ? ids[0] : ''; + // The 'Encrypt' dictionary itself should not be encrypted, and by + // setting `suppressEncryption` we can prevent an infinite loop inside + // of `XRef_fetchUncompressed` if the dictionary contains indirect + // objects (fixes issue7665.pdf). + encrypt.suppressEncryption = true; this.encrypt = new CipherTransformFactory(encrypt, fileId, this.password); } @@ -1079,11 +1084,11 @@ var XRef = (function XRefClosure() { return null; }, - fetchIfRef: function XRef_fetchIfRef(obj) { + fetchIfRef: function XRef_fetchIfRef(obj, suppressEncryption) { if (!isRef(obj)) { return obj; } - return this.fetch(obj); + return this.fetch(obj, suppressEncryption); }, fetch: function XRef_fetch(ref, suppressEncryption) { @@ -1201,11 +1206,11 @@ var XRef = (function XRefClosure() { return xrefEntry; }, - fetchIfRefAsync: function XRef_fetchIfRefAsync(obj) { + fetchIfRefAsync: function XRef_fetchIfRefAsync(obj, suppressEncryption) { if (!isRef(obj)) { return Promise.resolve(obj); } - return this.fetchAsync(obj); + return this.fetchAsync(obj, suppressEncryption); }, fetchAsync: function XRef_fetchAsync(ref, suppressEncryption) { diff --git a/src/core/primitives.js b/src/core/primitives.js index 6a17e990aab3d..b27222134b692 100644 --- a/src/core/primitives.js +++ b/src/core/primitives.js @@ -73,6 +73,7 @@ var Dict = (function DictClosure() { this.map = Object.create(null); this.xref = xref; this.objId = null; + this.suppressEncryption = false; this.__nonSerializable__ = nonSerializable; // disable cloning of the Dict } @@ -84,40 +85,40 @@ var Dict = (function DictClosure() { // automatically dereferences Ref objects get: function Dict_get(key1, key2, key3) { var value; - var xref = this.xref; + var xref = this.xref, suppressEncryption = this.suppressEncryption; if (typeof (value = this.map[key1]) !== 'undefined' || key1 in this.map || typeof key2 === 'undefined') { - return xref ? xref.fetchIfRef(value) : value; + return xref ? xref.fetchIfRef(value, suppressEncryption) : value; } if (typeof (value = this.map[key2]) !== 'undefined' || key2 in this.map || typeof key3 === 'undefined') { - return xref ? xref.fetchIfRef(value) : value; + return xref ? xref.fetchIfRef(value, suppressEncryption) : value; } value = this.map[key3] || null; - return xref ? xref.fetchIfRef(value) : value; + return xref ? xref.fetchIfRef(value, suppressEncryption) : value; }, // Same as get(), but returns a promise and uses fetchIfRefAsync(). getAsync: function Dict_getAsync(key1, key2, key3) { var value; - var xref = this.xref; + var xref = this.xref, suppressEncryption = this.suppressEncryption; if (typeof (value = this.map[key1]) !== 'undefined' || key1 in this.map || typeof key2 === 'undefined') { if (xref) { - return xref.fetchIfRefAsync(value); + return xref.fetchIfRefAsync(value, suppressEncryption); } return Promise.resolve(value); } if (typeof (value = this.map[key2]) !== 'undefined' || key2 in this.map || typeof key3 === 'undefined') { if (xref) { - return xref.fetchIfRefAsync(value); + return xref.fetchIfRefAsync(value, suppressEncryption); } return Promise.resolve(value); } value = this.map[key3] || null; if (xref) { - return xref.fetchIfRefAsync(value); + return xref.fetchIfRefAsync(value, suppressEncryption); } return Promise.resolve(value); }, @@ -125,7 +126,7 @@ var Dict = (function DictClosure() { // Same as get(), but dereferences all elements if the result is an Array. getArray: function Dict_getArray(key1, key2, key3) { var value = this.get(key1, key2, key3); - var xref = this.xref; + var xref = this.xref, suppressEncryption = this.suppressEncryption; if (!isArray(value) || !xref) { return value; } @@ -134,7 +135,7 @@ var Dict = (function DictClosure() { if (!isRef(value[i])) { continue; } - value[i] = xref.fetch(value[i]); + value[i] = xref.fetch(value[i], suppressEncryption); } return value; }, From 4d2de9b47e7a64d690f6972072344e418965b494 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Fri, 23 Sep 2016 14:11:08 +0200 Subject: [PATCH 2/2] Add a reduced `load` test for issue 7665 --- test/pdfs/.gitignore | 1 + test/pdfs/issue7665.pdf | Bin 0 -> 1472 bytes test/test_manifest.json | 8 ++++++++ 3 files changed, 9 insertions(+) create mode 100644 test/pdfs/issue7665.pdf diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index cb770788621c1..5339928e08a4d 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -39,6 +39,7 @@ !issue7492.pdf !issue7544.pdf !issue7598.pdf +!issue7665.pdf !filled-background.pdf !ArabicCIDTrueType.pdf !ThuluthFeatures.pdf diff --git a/test/pdfs/issue7665.pdf b/test/pdfs/issue7665.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c51c873a860b87d1c2424433c74a9631530b675e GIT binary patch literal 1472 zcmah}&1)QG7`NcXIY>b8DDS}>S)R7SW^k z_E1nSBIuz|DD+xAc-Olg{09V~;`7e#W~U-n zg_skR-#$NyDBrW4oz=&n&SDi(s-#|}+^bKk4iS`pScVqeL#%iGXyv?!w{}hHwQB|C zb}izIbhlzC?QVSrQ84@{#zVcB`r;v8_)V4`n8d;~F&m3qhifvqZ8eKIOku zdCIg(e=}Cc-F+lE1Sw}-Y~VSwT^vSVhu*2}nrP?Qk9S{v{Kt)r&%fRJ^wBr|*XKV? zUVnS{oySMN{Qkz9C+(Ntzk2!fi_d<3*PMTK#2^0lR`%LIKcHvyyC-+Q{_}&4zuFC8 zZ5e6FU#fFeMQTltA-`SDW4kq}=N6hYs-TFnI!5_XS?xei*MjM(>-xqdgh2cPXdh$E zv%HjIZ;VvW%mvPAXRBbFfbt~?R7jNXqk;0;2Eqa-+zT%Vr;eHa2Eoj_9Z00Zr1(ujIVYT&=Q0a+O!H3AB zazsLHU_U{CaN5ns=|2D^@MzW_chG09bMXm%^K`9@slvsL& zg_Q=NDXNHIK|rCz1PY`j0+|mCYh?sjVNz%+qJbjr0Cwt)@?fP33wn*QvIMBLUJ(!J znRNh#@Ira)uq<$lSh`3Onh;5h5atjjeckHR+r>0xOK3WEJ6l_%@0G>QX?K70AgxnA zto{B;tg?J>dU5#%LPi+u@l}~N z*JO;qxL&d2u=-bJf?x9m>aN)ltYFVq@w&#AGg$6)Moj8DomKteHZ31UWKjP85ZxOT zJ_J_a!ok28tc8=L2-@M`v5`iJ?f