Commit 7283380e for libheif

commit 7283380ec21003a6812d8a1e7e4328bdda8b2026
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Thu Apr 30 15:09:42 2026 +0200

    J2K: fix integer underflow and file overread on malicious input (#1776)

diff --git a/libheif/codecs/jpeg2000_boxes.cc b/libheif/codecs/jpeg2000_boxes.cc
index d09ae507..fd88b7c2 100644
--- a/libheif/codecs/jpeg2000_boxes.cc
+++ b/libheif/codecs/jpeg2000_boxes.cc
@@ -430,7 +430,7 @@ Error JPEG2000MainHeader::parse_SIZ_segment()
                  heif_suberror_Invalid_J2K_codestream,
                  std::string("Out of range Csiz value"));
   }
-  if (cursor > headerData.size() - (3 * csiz)) {
+  if (3 * static_cast<size_t>(csiz) > headerData.size() - cursor) {
     return Error(heif_error_Invalid_input,
                  heif_suberror_Invalid_J2K_codestream);
   }
@@ -449,7 +449,8 @@ Error JPEG2000MainHeader::parse_SIZ_segment()

 Error JPEG2000MainHeader::parse_CAP_segment_body()
 {
-  if (cursor > headerData.size() - 8) {
+  // Need at least Lcap (2) + Pcap (4) = 6 bytes.
+  if (headerData.size() - cursor < 6) {
     return Error(heif_error_Invalid_input,
                  heif_suberror_Invalid_J2K_codestream);
   }
@@ -459,15 +460,25 @@ Error JPEG2000MainHeader::parse_CAP_segment_body()
                  heif_suberror_Invalid_J2K_codestream,
                  std::string("Out of range Lcap value"));
   }
+  // Lcap counts the Lcap field itself, so Lcap-2 bytes must follow it.
+  if (headerData.size() - cursor < static_cast<size_t>(lcap) - 2) {
+    return Error(heif_error_Invalid_input,
+                 heif_suberror_Invalid_J2K_codestream);
+  }
   uint32_t pcap = read32();
+  size_t segment_end = cursor + (static_cast<size_t>(lcap) - 6);
   for (uint8_t i = 2; i <= 32; i++) {
-    if (pcap & (1 << (32 - i))) {
+    if (pcap & (1u << (32 - i))) {
+      if (segment_end - cursor < 2) {
+        return Error(heif_error_Invalid_input,
+                     heif_suberror_Invalid_J2K_codestream,
+                     std::string("CAP segment Pcap inconsistent with Lcap"));
+      }
       switch (i) {
         case JPEG2000_Extension_Capability_HT::IDENT:
           parse_Ccap15();
           break;
         default:
-          std::cout << "unhandled extended capabilities value: " << (int)i << std::endl;
           read16();
       }
     }
diff --git a/libheif/codecs/jpeg2000_boxes.h b/libheif/codecs/jpeg2000_boxes.h
index c313a85c..d6b5c306 100644
--- a/libheif/codecs/jpeg2000_boxes.h
+++ b/libheif/codecs/jpeg2000_boxes.h
@@ -507,7 +507,10 @@ private:

     uint32_t read32()
     {
-        uint32_t res = (headerData[cursor] << 24) | (headerData[cursor + 1] << 16) | (headerData[cursor + 2] << 8) | headerData[cursor + 3];
+        uint32_t res = (uint32_t{headerData[cursor]} << 24) |
+                       (uint32_t{headerData[cursor + 1]} << 16) |
+                       (uint32_t{headerData[cursor + 2]} << 8) |
+                       uint32_t{headerData[cursor + 3]};
         cursor += 4;
         return res;
     }