Avoid crash in CPDF_SecurityHandler::GetUserPassword().

For r2 and r3 encrypted PDFs, the value for the /O key is normally a
32-byte string. CPDF_SecurityHandler::GetUserPassword() recently started
using spans, and that caught an OOB error for /O keys shorter than
32-bytes. While a hard crash is better than an OOB read, gracefully
handling the situation is best. Change GetUserPassword() to do that.

For testing, add hand-edited copies of encrypted_hello_world_r2.pdf and
encrypted_hello_world_r3.pdf, with 31-byte /O keys, and the xref offset
adjusted. Then make sure embedder tests that try to load these bad PDFs
do not crash.

Bug: pdfium:1436
Change-Id: I873eead7c1dc45c9c4bbd41044f8fa5b77d7f558
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/64170
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/core/fpdfapi/parser/cpdf_security_handler.cpp b/core/fpdfapi/parser/cpdf_security_handler.cpp
index d65ce4b..126fc6b 100644
--- a/core/fpdfapi/parser/cpdf_security_handler.cpp
+++ b/core/fpdfapi/parser/cpdf_security_handler.cpp
@@ -469,7 +469,13 @@
 
 ByteString CPDF_SecurityHandler::GetUserPassword(
     const ByteString& owner_password) const {
+  constexpr size_t kRequiredOkeyLength = 32;
   ByteString okey = m_pEncryptDict->GetStringFor("O");
+  size_t okeylen = std::min<size_t>(okey.GetLength(), kRequiredOkeyLength);
+  if (okeylen < kRequiredOkeyLength)
+    return ByteString();
+
+  DCHECK_EQ(kRequiredOkeyLength, okeylen);
   uint8_t passcode[32];
   GetPassCode(owner_password, passcode);
   uint8_t digest[16];
@@ -482,8 +488,7 @@
   size_t copy_len = std::min(m_KeyLen, sizeof(digest));
 
   memcpy(enckey, digest, copy_len);
-  size_t okeylen = std::min<size_t>(okey.GetLength(), 32);
-  uint8_t okeybuf[64] = {};
+  uint8_t okeybuf[32] = {};
   memcpy(okeybuf, okey.c_str(), okeylen);
   pdfium::span<uint8_t> okey_span(okeybuf, okeylen);
   if (m_Revision == 2) {
@@ -496,7 +501,7 @@
       CRYPT_ArcFourCryptBlock(okey_span, {tempkey, m_KeyLen});
     }
   }
-  size_t len = 32;
+  size_t len = kRequiredOkeyLength;
   while (len && kDefaultPasscode[len - 1] == okey_span[len - 1])
     len--;
 
diff --git a/core/fpdfapi/parser/cpdf_security_handler_embeddertest.cpp b/core/fpdfapi/parser/cpdf_security_handler_embeddertest.cpp
index a7a0a79..ee2a621 100644
--- a/core/fpdfapi/parser/cpdf_security_handler_embeddertest.cpp
+++ b/core/fpdfapi/parser/cpdf_security_handler_embeddertest.cpp
@@ -222,6 +222,18 @@
   EXPECT_EQ(0xFFFFFFFC, FPDF_GetDocPermissions(document()));
 }
 
+// Should not crash. https://crbug.com/pdfium/1436
+TEST_F(CPDFSecurityHandlerEmbedderTest, BadOkeyVersion2) {
+  EXPECT_FALSE(
+      OpenDocumentWithPassword("encrypted_hello_world_r2_bad_okey.pdf", "a"));
+}
+
+// Should not crash. https://crbug.com/pdfium/1436
+TEST_F(CPDFSecurityHandlerEmbedderTest, BadOkeyVersion3) {
+  EXPECT_FALSE(
+      OpenDocumentWithPassword("encrypted_hello_world_r3_bad_okey.pdf", "a"));
+}
+
 TEST_F(CPDFSecurityHandlerEmbedderTest, OwnerPasswordVersion2UTF8) {
   // The password is "age", where the 'a' has a circumflex. Encoding the
   // password as UTF-8 works.
diff --git a/testing/resources/encrypted_hello_world_r2_bad_okey.pdf b/testing/resources/encrypted_hello_world_r2_bad_okey.pdf
new file mode 100644
index 0000000..637812b
--- /dev/null
+++ b/testing/resources/encrypted_hello_world_r2_bad_okey.pdf
Binary files differ
diff --git a/testing/resources/encrypted_hello_world_r3_bad_okey.pdf b/testing/resources/encrypted_hello_world_r3_bad_okey.pdf
new file mode 100644
index 0000000..721c20b
--- /dev/null
+++ b/testing/resources/encrypted_hello_world_r3_bad_okey.pdf
Binary files differ