From 3e77cf6b32188bfe433fa48567418576dcdb7700 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Fri, 23 Sep 2016 14:10:27 +0200 Subject: [PATCH] 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; },