CVE-2023-46228: Integer Overflow or Wraparound (CWE-190)
value: zchunk before 1.3.2 has multiple integer overflows via malformed zchunk files to lib/comp/comp.c, lib/comp/zstd/zstd.c, lib/dl/multipart.c, or lib/header.c.
CWE description: The product performs a calculation that can
         produce an integer overflow or wraparound when the logic
         assumes that the resulting value will always be larger than
         the original value. This occurs when an integer value is
         incremented to a value that is too large to store in the
         associated representation. When this occurs, the value may
         become a very small or negative number.
CVSS v3 base score: 7.8
Published: 2023-10-19T05:15Z
Repository: https://github.com/zchunk/zchunk
Language: C
Files changed: src/lib/comp/comp.c, src/lib/comp/zstd/zstd.c, src/lib/dl/multipart.c, src/lib/header.c
Commit date: 2023-10-05 19:52:18 +0100
Commit message: Handle overflow errors in malformed zchunk files

Thanks to Agostino Sarubbo of Gentoo for the heads up!

Signed-off-by: Jonathan Dieter <jdieter@gmail.com>

Diff: diff --git a/src/lib/comp/comp.c b/src/lib/comp/comp.c
index 13cb491..414727d 100644
--- a/src/lib/comp/comp.c
+++ b/src/lib/comp/comp.c
@@ -113,10 +113,16 @@ static bool comp_add_to_data(zckCtx *zck, zckComp *comp, const char *src,
                              size_t src_size) {
     VALIDATE_BOOL(zck);
     ALLOCD_BOOL(zck, comp);
     ALLOCD_BOOL(zck, src);
 
+    if((comp->data_size > comp->data_size + src_size) ||
+       (src_size > comp->data_size + src_size)) {
+        zck_log(ZCK_LOG_ERROR, "Integer overflow when reading data");
+        return false;
+    }
+
     comp->data = zrealloc(comp->data, comp->data_size + src_size);
     if (!comp->data) {
         zck_log(ZCK_LOG_ERROR, "OOM in %s", __func__);
         return false;
     }
diff --git a/src/lib/comp/zstd/zstd.c b/src/lib/comp/zstd/zstd.c
index a12ddfe..5b68b6a 100644
--- a/src/lib/comp/zstd/zstd.c
+++ b/src/lib/comp/zstd/zstd.c
@@ -115,10 +115,16 @@ static ssize_t compress(zckCtx *zck, zckComp *comp, const char *src,
     ALLOCD_INT(zck, dst);
     ALLOCD_INT(zck, src);
     ALLOCD_INT(zck, dst_size);
     ALLOCD_INT(zck, comp);
 
+    if((comp->dc_data_size > comp->dc_data_size + src_size) ||
+       (src_size > comp->dc_data_size + src_size)) {
+        zck_log(ZCK_LOG_ERROR, "Integer overflow when reading decompressed data");
+        return false;
+    }
+
     comp->dc_data = zrealloc(comp->dc_data, comp->dc_data_size + src_size);
     if (!comp->dc_data) {
         zck_log(ZCK_LOG_ERROR, "OOM in %s", __func__);
         return -1;
     }
diff --git a/src/lib/dl/multipart.c b/src/lib/dl/multipart.c
index 380e451..01cca1d 100644
--- a/src/lib/dl/multipart.c
+++ b/src/lib/dl/multipart.c
@@ -117,10 +117,16 @@ size_t multipart_extract(zckDL *dl, char *b, size_t l) {
     char *buf = b;
     bool alloc_buf = false;
 
     /* Add new data to stored buffer */
     if(mp->buffer) {
+        if((mp->buffer_len > mp->buffer_len + l) ||
+           (l > mp->buffer_len + l)) {
+            zck_log(ZCK_LOG_ERROR, "Integer overflow when extracting multipart data");
+            return 0;
+        }
+
         buf = zrealloc(mp->buffer, mp->buffer_len + l);
         if (!buf) {
             zck_log(ZCK_LOG_ERROR, "OOM in %s", __func__);
             return 0;
         }
diff --git a/src/lib/header.c b/src/lib/header.c
index 7253207..8fdfe89 100644
--- a/src/lib/header.c
+++ b/src/lib/header.c
@@ -72,15 +72,20 @@ static bool read_optional_element(zckCtx *zck, size_t id, size_t data_size,
     zck_log(ZCK_LOG_WARNING, "Unknown optional element id %i set", id);
     return true;
 }
 
 static bool read_header_from_file(zckCtx *zck) {
-    /* Verify that lead_size and header_length have been set */
+    /* Verify that lead_size and header_length have been set and are legit */
     if(zck->lead_size == 0 || zck->header_length == 0) {
         set_error(zck, "Lead and header sizes are both 0.  Have you run zck_read_lead() yet?");
         return false;
     }
+    if((zck->lead_size > zck->lead_size + zck->header_length) ||
+       (zck->header_length > zck->lead_size + zck->header_length)) {
+        zck_log(ZCK_LOG_ERROR, "Integer overflow when reading header");
+        return false;
+    }
 
     /* Allocate header and store any extra bytes at beginning of header */
     zck->header = zrealloc(zck->header, zck->lead_size + zck->header_length);
     if (!zck->header) {
         zck_log(ZCK_LOG_ERROR, "OOM in %s", __func__);
@@ -531,10 +536,16 @@ static bool read_lead(zckCtx *zck) {
     zck->header_length = header_length;
 
     /* Set header digest location */
     zck->hdr_digest_loc = length;
 
+    /* Verify that we're not going to overflow */
+    if(length > length + zck->hash_type.digest_size) {
+        zck_log(ZCK_LOG_ERROR, "Integer overflow when reading lead");
+        return false;
+    }
+
     /* Read header digest */
     zck_log(ZCK_LOG_DEBUG, "Reading header digest");
     header = zrealloc(header, length + zck->hash_type.digest_size);
     if (!header) {
         zck_log(ZCK_LOG_ERROR, "OOM in %s", __func__);

Vulnerable code:     /* Verify that lead_size and header_length have been set */
Fixed code:     if((comp->data_size > comp->data_size + src_size) ||
       (src_size > comp->data_size + src_size)) {
        zck_log(ZCK_LOG_ERROR, "Integer overflow when reading data");
        return false;
    }

    if((comp->dc_data_size > comp->dc_data_size + src_size) ||
       (src_size > comp->dc_data_size + src_size)) {
        zck_log(ZCK_LOG_ERROR, "Integer overflow when reading decompressed data");
        return false;
    }

        if((mp->buffer_len > mp->buffer_len + l) ||
           (l > mp->buffer_len + l)) {
            zck_log(ZCK_LOG_ERROR, "Integer overflow when extracting multipart data");
            return 0;
        }

    /* Verify that lead_size and header_length have been set and are legit */
    if((zck->lead_size > zck->lead_size + zck->header_length) ||
       (zck->header_length > zck->lead_size + zck->header_length)) {
        zck_log(ZCK_LOG_ERROR, "Integer overflow when reading header");
        return false;
    }
    /* Verify that we're not going to overflow */
    if(length > length + zck->hash_type.digest_size) {
        zck_log(ZCK_LOG_ERROR, "Integer overflow when reading lead");
        return false;
    }