CVE-2017-18184: Out-of-bounds Read (CWE-125)
value: 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.
CWE description: The product reads data past the end, or before the beginning, of the intended buffer.
CVSS v3 base score: 5.5
Published: 2018-02-13T19:29Z
Repository: https://github.com/qpdf/qpdf
Language: C++
Files changed: libqpdf/QPDF_encryption.cc
Commit date: 2017-08-26 21:35:59 -0400
Commit message: Pad keys to avoid memory errors (fixes #147)

Diff: diff --git a/libqpdf/QPDF_encryption.cc b/libqpdf/QPDF_encryption.cc
index 81325591..a2445b61 100644
--- a/libqpdf/QPDF_encryption.cc
+++ b/libqpdf/QPDF_encryption.cc
@@ -477,10 +477,12 @@ compute_O_value(std::string const& user_password,
     unsigned char O_key[OU_key_bytes_V4];
     compute_O_rc4_key(user_password, owner_password, data, O_key);
 
     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);
     return std::string(upass, key_bytes);
 }
@@ -493,10 +495,11 @@ compute_U_value_R2(std::string const& user_password,
     // Algorithm 3.4 from the PDF 1.7 Reference Manual
 
     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);
     return std::string(udata, key_bytes);
 }
@@ -514,10 +517,11 @@ compute_U_value_R3(std::string const& user_password,
 	pad_or_truncate_password_V4("").c_str(), key_bytes);
     md5.encodeDataIncrementally(data.getId1().c_str(),
                                 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);
     char result[key_bytes];
     memcpy(result, digest, sizeof(MD5::Digest));
@@ -589,11 +593,14 @@ check_owner_password_V4(std::string& user_password,
 
     unsigned char key[OU_key_bytes_V4];
     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);
     bool result = false;
     if (check_user_password(new_user_password, data))
@@ -884,10 +891,11 @@ QPDF::initializeEncryption()
     std::string UE;
     std::string Perms;
 
     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)))
         {
             throw QPDFExc(qpdf_e_damaged_pdf, this->m->file->getName(),
@@ -911,28 +919,16 @@ QPDF::initializeEncryption()
         }
         OE = encryption_dict.getKey("/OE").getStringValue();
         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;
     if (encryption_dict.getKey("/Length").isInteger())
     {

Vulnerable code:     iterate_rc4(O_data, key_bytes, key, data.getLengthBytes(),
        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");
        }
Fixed code:     std::string k1(reinterpret_cast<char*>(O_key), OU_key_bytes_V4);
    pad_short_parameter(k1, data.getLengthBytes());
    pad_short_parameter(k1, data.getLengthBytes());
    pad_short_parameter(k1, 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(),
        // These must be exactly the right number of bytes.
        // These may be longer than the minimum number of bytes.