Vulnerability: An issue was discovered in QPDF before 7.0.0. There is a stack-based out-of-bounds read in the function iterate_rc4 in QPDF_encryption.cc.
Vulnerability ID: CVE-2017-18184
CWE: CWE-125
Severity score: 2.9
Project: https://github.com/qpdf/qpdf
Published: 2018-02-13
File: libqpdf/QPDF_encryption.cc
Commit message: Pad keys to avoid memory errors (fixes #147)
Commit author: Jay Berkenbilt

Vulnerable version (before fix):
 compute_O_value(std::string const& user_password,      char upass[key_bytes];     pad_or_truncate_password_V4(user_password, upass);     iterate_rc4(QUtil::unsigned_char_pointer(upass), key_bytes, 		O_key, data.getLengthBytes(),                 (data.getR() >= 3) ? 20 : 1, false); compute_U_value_R2(std::string const& user_password,     std::string k1 = QPDF::compute_encryption_key(user_password, data);     char udata[key_bytes];     pad_or_truncate_password_V4("", udata);     iterate_rc4(QUtil::unsigned_char_pointer(udata), key_bytes, 		QUtil::unsigned_char_pointer(k1),                 data.getLengthBytes(), 1, false); compute_U_value_R3(std::string const& user_password,                                 data.getId1().length());     MD5::Digest digest;     md5.digest(digest);     iterate_rc4(digest, sizeof(MD5::Digest), 		QUtil::unsigned_char_pointer(k1),                 data.getLengthBytes(), 20, false); check_owner_password_V4(std::string& user_password,     compute_O_rc4_key(user_password, owner_password, data, key);     unsigned char O_data[key_bytes];     memcpy(O_data, QUtil::unsigned_char_pointer(data.getO()), key_bytes);     iterate_rc4(O_data, key_bytes, key, data.getLengthBytes(),                 (data.getR() >= 3) ? 20 : 1, true);     std::string new_user_password =         std::string(reinterpret_cast<char*>(O_data), key_bytes); QPDF::initializeEncryption()      if (V < 5)     {         pad_short_parameter(O, key_bytes);         pad_short_parameter(U, key_bytes);         if (! ((O.length() == key_bytes) && (U.length() == key_bytes))) QPDF::initializeEncryption()         UE = encryption_dict.getKey("/UE").getStringValue();         Perms = encryption_dict.getKey("/Perms").getStringValue();          pad_short_parameter(O, OU_key_bytes_V5);         pad_short_parameter(U, OU_key_bytes_V5);         pad_short_parameter(OE, OUE_key_bytes_V5);         pad_short_parameter(UE, OUE_key_bytes_V5);         pad_short_parameter(Perms, Perms_key_bytes_V5);         if ((O.length() < OU_key_bytes_V5) ||             (U.length() < OU_key_bytes_V5) ||             (OE.length() < OUE_key_bytes_V5) ||             (UE.length() < OUE_key_bytes_V5) ||             (Perms.length() < Perms_key_bytes_V5))         {             throw QPDFExc(qpdf_e_damaged_pdf, this->m->file->getName(),                           "encryption dictionary",                           this->m->file->getLastOffset(),                           "incorrect length for some of"                           " /O, /U, /OE, /UE, or /Perms in"                           " encryption dictionary");         }     }      int Length = 40;

Fixed version (after fix):
 compute_O_value(std::string const& user_password,      char upass[key_bytes];     pad_or_truncate_password_V4(user_password, upass);     std::string k1(reinterpret_cast<char*>(O_key), OU_key_bytes_V4);     pad_short_parameter(k1, data.getLengthBytes());     iterate_rc4(QUtil::unsigned_char_pointer(upass), key_bytes, 		O_key, data.getLengthBytes(),                 (data.getR() >= 3) ? 20 : 1, false); compute_U_value_R2(std::string const& user_password,     std::string k1 = QPDF::compute_encryption_key(user_password, data);     char udata[key_bytes];     pad_or_truncate_password_V4("", udata);     pad_short_parameter(k1, data.getLengthBytes());     iterate_rc4(QUtil::unsigned_char_pointer(udata), key_bytes, 		QUtil::unsigned_char_pointer(k1),                 data.getLengthBytes(), 1, false); compute_U_value_R3(std::string const& user_password,                                 data.getId1().length());     MD5::Digest digest;     md5.digest(digest);     pad_short_parameter(k1, data.getLengthBytes());     iterate_rc4(digest, sizeof(MD5::Digest), 		QUtil::unsigned_char_pointer(k1),                 data.getLengthBytes(), 20, false); check_owner_password_V4(std::string& user_password,     compute_O_rc4_key(user_password, owner_password, data, key);     unsigned char O_data[key_bytes];     memcpy(O_data, QUtil::unsigned_char_pointer(data.getO()), key_bytes);     std::string k1(reinterpret_cast<char*>(key), OU_key_bytes_V4);     pad_short_parameter(k1, data.getLengthBytes());     iterate_rc4(O_data, key_bytes, QUtil::unsigned_char_pointer(k1),                 data.getLengthBytes(),                 (data.getR() >= 3) ? 20 : 1, true);     std::string new_user_password =         std::string(reinterpret_cast<char*>(O_data), key_bytes); QPDF::initializeEncryption()      if (V < 5)     {         // These must be exactly the right number of bytes.         pad_short_parameter(O, key_bytes);         pad_short_parameter(U, key_bytes);         if (! ((O.length() == key_bytes) && (U.length() == key_bytes))) QPDF::initializeEncryption()         UE = encryption_dict.getKey("/UE").getStringValue();         Perms = encryption_dict.getKey("/Perms").getStringValue();          // These may be longer than the minimum number of bytes.         pad_short_parameter(O, OU_key_bytes_V5);         pad_short_parameter(U, OU_key_bytes_V5);         pad_short_parameter(OE, OUE_key_bytes_V5);         pad_short_parameter(UE, OUE_key_bytes_V5);         pad_short_parameter(Perms, Perms_key_bytes_V5);     }      int Length = 40;

Patch:
patch: @@ -479,6 +479,8 @@ compute_O_value(std::string const& user_password,
 
     char upass[key_bytes];
     pad_or_truncate_password_V4(user_password, upass);
+    std::string k1(reinterpret_cast<char*>(O_key), OU_key_bytes_V4);
+    pad_short_parameter(k1, data.getLengthBytes());
     iterate_rc4(QUtil::unsigned_char_pointer(upass), key_bytes,
 		O_key, data.getLengthBytes(),
                 (data.getR() >= 3) ? 20 : 1, false);
@@ -495,6 +497,7 @@ compute_U_value_R2(std::string const& user_password,
     std::string k1 = QPDF::compute_encryption_key(user_password, data);
     char udata[key_bytes];
     pad_or_truncate_password_V4("", udata);
+    pad_short_parameter(k1, data.getLengthBytes());
     iterate_rc4(QUtil::unsigned_char_pointer(udata), key_bytes,
 		QUtil::unsigned_char_pointer(k1),
                 data.getLengthBytes(), 1, false);
@@ -516,6 +519,7 @@ compute_U_value_R3(std::string const& user_password,
                                 data.getId1().length());
     MD5::Digest digest;
     md5.digest(digest);
+    pad_short_parameter(k1, data.getLengthBytes());
     iterate_rc4(digest, sizeof(MD5::Digest),
 		QUtil::unsigned_char_pointer(k1),
                 data.getLengthBytes(), 20, false);
@@ -591,7 +595,10 @@ check_owner_password_V4(std::string& user_password,
     compute_O_rc4_key(user_password, owner_password, data, key);
     unsigned char O_data[key_bytes];
     memcpy(O_data, QUtil::unsigned_char_pointer(data.getO()), key_bytes);
-    iterate_rc4(O_data, key_bytes, key, data.getLengthBytes(),
+    std::string k1(reinterpret_cast<char*>(key), OU_key_bytes_V4);
+    pad_short_parameter(k1, data.getLengthBytes());
+    iterate_rc4(O_data, key_bytes, QUtil::unsigned_char_pointer(k1),
+                data.getLengthBytes(),
                 (data.getR() >= 3) ? 20 : 1, true);
     std::string new_user_password =
         std::string(reinterpret_cast<char*>(O_data), key_bytes);
@@ -886,6 +893,7 @@ QPDF::initializeEncryption()
 
     if (V < 5)
     {
+        // These must be exactly the right number of bytes.
         pad_short_parameter(O, key_bytes);
         pad_short_parameter(U, key_bytes);
         if (! ((O.length() == key_bytes) && (U.length() == key_bytes)))
@@ -913,24 +921,12 @@ QPDF::initializeEncryption()
         UE = encryption_dict.getKey("/UE").getStringValue();
         Perms = encryption_dict.getKey("/Perms").getStringValue();
 
+        // These may be longer than the minimum number of bytes.
         pad_short_parameter(O, OU_key_bytes_V5);
         pad_short_parameter(U, OU_key_bytes_V5);
         pad_short_parameter(OE, OUE_key_bytes_V5);
         pad_short_parameter(UE, OUE_key_bytes_V5);
         pad_short_parameter(Perms, Perms_key_bytes_V5);
-        if ((O.length() < OU_key_bytes_V5) ||
-            (U.length() < OU_key_bytes_V5) ||
-            (OE.length() < OUE_key_bytes_V5) ||
-            (UE.length() < OUE_key_bytes_V5) ||
-            (Perms.length() < Perms_key_bytes_V5))
-        {
-            throw QPDFExc(qpdf_e_damaged_pdf, this->m->file->getName(),
-                          "encryption dictionary",
-                          this->m->file->getLastOffset(),
-                          "incorrect length for some of"
-                          " /O, /U, /OE, /UE, or /Perms in"
-                          " encryption dictionary");
-        }
     }
 
     int Length = 40;
path: libqpdf/QPDF_encryption.cc