CVE-2023-2241: Out-of-bounds Write (CWE-787)
value: A vulnerability, which was classified as critical, was found in PoDoFo 0.10.0. Affected is the function readXRefStreamEntry of the file PdfXRefStreamParserObject.cpp. The manipulation leads to heap-based buffer overflow. An attack has to be approached locally. The exploit has been disclosed to the public and may be used. The patch is identified as 535a786f124b739e3c857529cecc29e4eeb79778. It is recommended to apply a patch to fix this issue. VDB-227226 is the identifier assigned to this vulnerability.
CWE description: The product writes data past the end, or before the beginning, of the intended buffer.
CVSS v3 base score: 7.8
Published: 2023-04-22T16:15Z
Repository: https://github.com/podofo/podofo
Language: C++
Files changed: playground/CMakeLists.txt, src/podofo/main/PdfXRefStreamParserObject.cpp, test/unit/ParserTest.cpp
Commit date: 2023-04-21 16:54:24 +0200
Commit message: PdfXRefStreamParserObject: Fixed handling of invalid XRef stream entries

Should fix #69

Diff: diff --git a/playground/CMakeLists.txt b/playground/CMakeLists.txt
index a75c26bc..f72d9aec 100644
--- a/playground/CMakeLists.txt
+++ b/playground/CMakeLists.txt
@@ -6,11 +6,11 @@ endif()
 
 # Clean CMAKE_CONFIGURATION_TYPES. Only one configuration will be used
 unset(CMAKE_CONFIGURATION_TYPES CACHE) # It will also remove it from GUI
 set(CMAKE_CONFIGURATION_TYPES "${CMAKE_BUILD_TYPE}")
 
-project(PoDoFo)
+project(podofo)
 enable_testing() # Needed to enable "make test" from binary dir
 
 # An useful variable to check if we are running from playground
 set(PODOFO_PLAYGROUND TRUE)
 
diff --git a/src/podofo/main/PdfXRefStreamParserObject.cpp b/src/podofo/main/PdfXRefStreamParserObject.cpp
index 2cf9b79c..1a5bce2a 100644
--- a/src/podofo/main/PdfXRefStreamParserObject.cpp
+++ b/src/podofo/main/PdfXRefStreamParserObject.cpp
@@ -65,19 +65,19 @@ void PdfXRefStreamParserObject::ReadXRefTable()
 
     // The pdf reference states that W is always an array with 3 entries
     // all of them have to be integers
     const PdfArray* arr;
     if (!arrObj.TryGetArray(arr) || arr->size() != 3)
-        PODOFO_RAISE_ERROR_INFO(PdfErrorCode::NoXRef, "Invalid XRef stream /W array");
+        PODOFO_RAISE_ERROR_INFO(PdfErrorCode::InvalidXRefStream, "Invalid XRef stream /W array");
 
     int64_t wArray[W_ARRAY_SIZE] = { 0, 0, 0 };
     int64_t num;
     for (unsigned i = 0; i < W_ARRAY_SIZE; i++)
     {
 
         if (!(*arr)[i].TryGetNumber(num))
-            PODOFO_RAISE_ERROR_INFO(PdfErrorCode::NoXRef, "Invalid XRef stream /W array");
+            PODOFO_RAISE_ERROR_INFO(PdfErrorCode::InvalidXRefStream, "Invalid XRef stream /W array");
 
         wArray[i] = num;
     }
 
     vector<int64_t> indices;
@@ -90,16 +90,16 @@ void PdfXRefStreamParserObject::parseStream(const int64_t wArray[W_ARRAY_SIZE],
 {
     for (int64_t lengthSum = 0, i = 0; i < W_ARRAY_SIZE; i++)
     {
         if (wArray[i] < 0)
         {
-            PODOFO_RAISE_ERROR_INFO(PdfErrorCode::NoXRef,
+            PODOFO_RAISE_ERROR_INFO(PdfErrorCode::InvalidXRefStream,
                 "Negative field length in XRef stream");
         }
         if (numeric_limits<int64_t>::max() - lengthSum < wArray[i])
         {
-            PODOFO_RAISE_ERROR_INFO(PdfErrorCode::NoXRef,
+            PODOFO_RAISE_ERROR_INFO(PdfErrorCode::InvalidXRefStream,
                 "Invalid entry length in XRef stream");
         }
         else
         {
             lengthSum += wArray[i];
@@ -110,28 +110,28 @@ void PdfXRefStreamParserObject::parseStream(const int64_t wArray[W_ARRAY_SIZE],
 
     charbuff buffer;
     this->GetOrCreateStream().CopyTo(buffer);
 
     vector<int64_t>::const_iterator it = indices.begin();
-    char* cursor = buffer.data();
+    size_t offset = 0;
     while (it != indices.end())
     {
         int64_t firstObj = *it++;
         int64_t count = *it++;
 
+        if ((offset + count * entryLen) > buffer.size())
+            PODOFO_RAISE_ERROR_INFO(PdfErrorCode::InvalidXRefStream, "Invalid count in XRef stream");
+
         m_entries->Enlarge(firstObj + count);
         for (unsigned index = 0; index < (unsigned)count; index++)
         {
-            if ((size_t)(cursor - buffer.data()) >= buffer.size())
-                PODOFO_RAISE_ERROR_INFO(PdfErrorCode::NoXRef, "Invalid count in XRef stream");
-
             unsigned objIndex = (unsigned)firstObj + index;
             auto& entry = (*m_entries)[objIndex];
             if (objIndex < m_entries->GetSize() && !entry.Parsed)
-                readXRefStreamEntry(entry, cursor, wArray);
+                readXRefStreamEntry(entry, buffer.data() + offset, wArray);
 
-            cursor += entryLen;
+            offset += entryLen;
         }
     }
 }
 
 void PdfXRefStreamParserObject::getIndices(vector<int64_t>& indices, int64_t size)
@@ -147,19 +147,19 @@ void PdfXRefStreamParserObject::getIndices(vector<int64_t>& indices, int64_t siz
     }
     else
     {
         const PdfArray* arr;
         if (!indexObj->TryGetArray(arr))
-            PODOFO_RAISE_ERROR_INFO(PdfErrorCode::NoXRef, "Invalid XRef Stream /Index");
+            PODOFO_RAISE_ERROR_INFO(PdfErrorCode::InvalidXRefStream, "Invalid XRef Stream /Index");
 
         for (auto index : *arr)
             indices.push_back(index.GetNumber());
     }
 
     // indices must be a multiple of 2
     if (indices.size() % 2 != 0)
-        PODOFO_RAISE_ERROR_INFO(PdfErrorCode::NoXRef, "Invalid XRef Stream /Index");
+        PODOFO_RAISE_ERROR_INFO(PdfErrorCode::InvalidXRefStream, "Invalid XRef Stream /Index");
 }
 
 void PdfXRefStreamParserObject::readXRefStreamEntry(PdfXRefEntry& entry, char* buffer, const int64_t wArray[W_ARRAY_SIZE])
 {
     uint64_t entryRaw[W_ARRAY_SIZE];
diff --git a/test/unit/ParserTest.cpp b/test/unit/ParserTest.cpp
index 85ab5388..138fe80e 100644
--- a/test/unit/ParserTest.cpp
+++ b/test/unit/ParserTest.cpp
@@ -1058,11 +1058,11 @@ void testReadXRefSubsection()
             }
         }
     }
 }
 
-TEST_CASE("testReadXRefStreamContents")
+TEST_CASE("TestReadXRefStreamContents")
 {
     // test valid stream
     try
     {
         // generate an XRef stream with valid /W values
@@ -1155,11 +1155,11 @@ TEST_CASE("testReadXRefStreamContents")
         doc.LoadFromDevice(device);
         FAIL("Should throw exception");
     }
     catch (PdfError& error)
     {
-        REQUIRE(error.GetCode() == PdfErrorCode::NoXRef);
+        REQUIRE(error.GetCode() == PdfErrorCode::InvalidXRefStream);
     }
     catch (exception&)
     {
         FAIL("Unexpected exception type");
     }
@@ -1208,11 +1208,11 @@ TEST_CASE("testReadXRefStreamContents")
         doc.LoadFromDevice(device);
         FAIL("Should throw exception");
     }
     catch (PdfError& error)
     {
-        REQUIRE(error.GetCode() == PdfErrorCode::NoXRef);
+        REQUIRE(error.GetCode() == PdfErrorCode::InvalidXRefStream);
     }
     catch (exception&)
     {
         FAIL("Unexpected exception type");
     }
@@ -1312,11 +1312,11 @@ TEST_CASE("testReadXRefStreamContents")
         doc.LoadFromDevice(device);
         FAIL("Should throw exception");
     }
     catch (PdfError& error)
     {
-        REQUIRE(error.GetCode() == PdfErrorCode::InvalidXRefType);
+        REQUIRE(error.GetCode() == PdfErrorCode::InvalidXRefStream);
     }
     catch (exception&)
     {
         FAIL("Unexpected exception type");
     }
@@ -1361,11 +1361,11 @@ TEST_CASE("testReadXRefStreamContents")
         doc.LoadFromDevice(device);
         FAIL("Should throw exception");
     }
     catch (PdfError& error)
     {
-        REQUIRE(error.GetCode() == PdfErrorCode::NoXRef);
+        REQUIRE(error.GetCode() == PdfErrorCode::InvalidXRefStream);
     }
     catch (exception&)
     {
         FAIL("Unexpected exception type");
     }
@@ -1720,11 +1720,11 @@ TEST_CASE("testReadXRefStreamContents")
         doc.LoadFromDevice(device);
         FAIL("Should throw exception");
     }
     catch (PdfError& error)
     {
-        REQUIRE(error.GetCode() == PdfErrorCode::NoXRef);
+        REQUIRE(error.GetCode() == PdfErrorCode::InvalidXRefStream);
     }
     catch (exception&)
     {
         FAIL("Unexpected exception type");
     }
@@ -1770,11 +1770,11 @@ TEST_CASE("testReadXRefStreamContents")
         doc.LoadFromDevice(device);
         FAIL("Should throw exception");
     }
     catch (PdfError& error)
     {
-        REQUIRE(error.GetCode() == PdfErrorCode::NoXRef);
+        REQUIRE(error.GetCode() == PdfErrorCode::InvalidXRefStream);
     }
     catch (exception&)
     {
         FAIL("Unexpected exception type");
     }

Vulnerable code: project(PoDoFo)
        PODOFO_RAISE_ERROR_INFO(PdfErrorCode::NoXRef, "Invalid XRef stream /W array");
            PODOFO_RAISE_ERROR_INFO(PdfErrorCode::NoXRef, "Invalid XRef stream /W array");
            PODOFO_RAISE_ERROR_INFO(PdfErrorCode::NoXRef,
            PODOFO_RAISE_ERROR_INFO(PdfErrorCode::NoXRef,
    char* cursor = buffer.data();
            if ((size_t)(cursor - buffer.data()) >= buffer.size())
                PODOFO_RAISE_ERROR_INFO(PdfErrorCode::NoXRef, "Invalid count in XRef stream");

                readXRefStreamEntry(entry, cursor, wArray);
            cursor += entryLen;
            PODOFO_RAISE_ERROR_INFO(PdfErrorCode::NoXRef, "Invalid XRef Stream /Index");
        PODOFO_RAISE_ERROR_INFO(PdfErrorCode::NoXRef, "Invalid XRef Stream /Index");
TEST_CASE("testReadXRefStreamContents")
        REQUIRE(error.GetCode() == PdfErrorCode::NoXRef);
        REQUIRE(error.GetCode() == PdfErrorCode::NoXRef);
        REQUIRE(error.GetCode() == PdfErrorCode::InvalidXRefType);
        REQUIRE(error.GetCode() == PdfErrorCode::NoXRef);
        REQUIRE(error.GetCode() == PdfErrorCode::NoXRef);
        REQUIRE(error.GetCode() == PdfErrorCode::NoXRef);
Fixed code: project(podofo)
        PODOFO_RAISE_ERROR_INFO(PdfErrorCode::InvalidXRefStream, "Invalid XRef stream /W array");
            PODOFO_RAISE_ERROR_INFO(PdfErrorCode::InvalidXRefStream, "Invalid XRef stream /W array");
            PODOFO_RAISE_ERROR_INFO(PdfErrorCode::InvalidXRefStream,
            PODOFO_RAISE_ERROR_INFO(PdfErrorCode::InvalidXRefStream,
    size_t offset = 0;
        if ((offset + count * entryLen) > buffer.size())
            PODOFO_RAISE_ERROR_INFO(PdfErrorCode::InvalidXRefStream, "Invalid count in XRef stream");

                readXRefStreamEntry(entry, buffer.data() + offset, wArray);
            offset += entryLen;
            PODOFO_RAISE_ERROR_INFO(PdfErrorCode::InvalidXRefStream, "Invalid XRef Stream /Index");
        PODOFO_RAISE_ERROR_INFO(PdfErrorCode::InvalidXRefStream, "Invalid XRef Stream /Index");
TEST_CASE("TestReadXRefStreamContents")
        REQUIRE(error.GetCode() == PdfErrorCode::InvalidXRefStream);
        REQUIRE(error.GetCode() == PdfErrorCode::InvalidXRefStream);
        REQUIRE(error.GetCode() == PdfErrorCode::InvalidXRefStream);
        REQUIRE(error.GetCode() == PdfErrorCode::InvalidXRefStream);
        REQUIRE(error.GetCode() == PdfErrorCode::InvalidXRefStream);
        REQUIRE(error.GetCode() == PdfErrorCode::InvalidXRefStream);