Skip to content

Commit

Permalink
Merge pull request mozilla#7668 from Snuffleupagus/issue-7665
Browse files Browse the repository at this point in the history
Prevent an infinite loop in `XRef_fetchUncompressed` for encrypted PDF files with indirect objects in the /Encrypt dictionary (issue 7665)
  • Loading branch information
yurydelendik authored Oct 15, 2016
2 parents f209030 + 4d2de9b commit ea5949f
Show file tree
Hide file tree
Showing 6 changed files with 42 additions and 16 deletions.
15 changes: 13 additions & 2 deletions src/core/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
13 changes: 9 additions & 4 deletions src/core/obj.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
21 changes: 11 additions & 10 deletions src/core/primitives.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -84,48 +85,48 @@ 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);
},

// 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;
}
Expand All @@ -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;
},
Expand Down
1 change: 1 addition & 0 deletions test/pdfs/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
!issue7492.pdf
!issue7544.pdf
!issue7598.pdf
!issue7665.pdf
!filled-background.pdf
!ArabicCIDTrueType.pdf
!ThuluthFeatures.pdf
Expand Down
Binary file added test/pdfs/issue7665.pdf
Binary file not shown.
8 changes: 8 additions & 0 deletions test/test_manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,14 @@
"rounds": 1,
"type": "eq"
},
{ "id": "issue7665",
"file": "pdfs/issue7665.pdf",
"md5": "f1199d16195a61e8232e2d1e742ed46b",
"link": false,
"rounds": 1,
"type": "load",
"about": "Encrypted file with indirect objects in the /Encrypt dictionary."
},
{ "id": "protectip",
"file": "pdfs/protectip.pdf",
"md5": "676e7a7b8f96d04825361832b1838a93",
Expand Down

0 comments on commit ea5949f

Please sign in to comment.