]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
[Fix] images/archives: harden parsers against malformed inputs
authorVsevolod Stakhov <vsevolod@rspamd.com>
Sat, 16 May 2026 14:44:50 +0000 (15:44 +0100)
committerVsevolod Stakhov <vsevolod@rspamd.com>
Sat, 16 May 2026 14:44:50 +0000 (15:44 +0100)
- images.c: guard Content-Id image linking against NULL rh->decoded.
- archives.c (zip): require >= 22 bytes for the EOCD scan to avoid a
  pointer-below-start computation; widen cd_offset + cd_size to uint64_t
  so a 32-bit wrap can no longer bypass the bounds check and let cd land
  outside the buffer.
- archives.c (rar v5): replace pointer-arithmetic bound on the file
  extra-field with a size-based check so an attacker-controlled 64-bit
  extra_sz cannot wrap p + fname_len + extra_sz and trigger an OOB read.
- archives.c (7z): same fix in rspamd_7zip_read_archive_props for proplen.
- archives.c: two return NULL from a bool-returning function changed to
  return false (cosmetic).

src/libmime/archives.c
src/libmime/images.c

index d63f0052561cfb697d6b44d8598853265c5270ab..fb7688b2de2227721e50e0b6b05cf6e26e6663bb 100644 (file)
@@ -114,7 +114,7 @@ rspamd_archive_file_try_utf(struct rspamd_task *task,
                        fentry->flags |= RSPAMD_ARCHIVE_FILE_OBFUSCATED;
                        fentry->fname = g_string_new_len(in, inlen);
 
-                       return NULL;
+                       return false;
                }
 
                int i = 0;
@@ -146,7 +146,7 @@ rspamd_archive_file_try_utf(struct rspamd_task *task,
                        fentry->flags |= RSPAMD_ARCHIVE_FILE_OBFUSCATED;
                        fentry->fname = g_string_new_len(in, inlen);
 
-                       return NULL;
+                       return false;
                }
 
                g_free(tmp);
@@ -200,6 +200,14 @@ rspamd_archive_process_zip(struct rspamd_task *task,
        struct rspamd_archive *arch;
        struct rspamd_archive_file *f = NULL;
 
+       /*
+        * Need at least 22 bytes for a minimal EOCD record; otherwise the
+        * scan below would compute a pointer below parsed_data.begin (UB).
+        */
+       if (part->parsed_data.len < 22) {
+               return;
+       }
+
        /* Zip files have interesting data at the end of archive */
        p = part->parsed_data.begin + part->parsed_data.len - 1;
        start = part->parsed_data.begin;
@@ -250,8 +258,11 @@ rspamd_archive_process_zip(struct rspamd_task *task,
        memcpy(&cd_offset, eocd + 16, sizeof(cd_offset));
        cd_offset = GUINT32_FROM_LE(cd_offset);
 
-       /* We need to check sanity as well */
-       if (cd_offset + cd_size > (unsigned int) (eocd - start)) {
+       /*
+        * We need to check sanity as well. Use a 64-bit add to avoid
+        * a 32-bit wrap that would let a malicious EOCD bypass the check.
+        */
+       if ((uint64_t) cd_offset + (uint64_t) cd_size > (uint64_t) (eocd - start)) {
                msg_info_task("zip archive is invalid (bad size/offset for CD)");
 
                return;
@@ -779,8 +790,14 @@ rspamd_archive_process_rar(struct rspamd_task *task,
                                        f = NULL;
                                }
 
+                               /*
+                                * Bound the extra-field block using sizes rather than
+                                * pointer arithmetic: extra_sz is a 64-bit varint and
+                                * "p + fname_len + extra_sz" could otherwise wrap.
+                                */
                                if (f && has_extra && extra_sz > 0 &&
-                                       p + fname_len + extra_sz < end) {
+                                       fname_len <= (uint64_t) (end - p) &&
+                                       extra_sz <= (uint64_t) (end - p) - fname_len) {
                                        /* Try to find encryption record in extra field */
                                        const unsigned char *ex = p + fname_len;
                                        const unsigned char *extra_end = ex + extra_sz;
@@ -1514,7 +1531,11 @@ rspamd_7zip_read_archive_props(struct rspamd_task *task,
                while (proptype != 0) {
                        SZ_READ_VINT(proplen);
 
-                       if (p + proplen < end) {
+                       /*
+                        * proplen is a 64-bit varint; compare sizes to avoid
+                        * a pointer-arithmetic wrap (p + proplen < end).
+                        */
+                       if (proplen < (uint64_t) (end - p)) {
                                p += proplen;
                        }
                        else {
index 087834e4f8ef7217324a56ca25387a452afd96d8..fd3f5cf3158369f252cf7c4e950a6d2a72c839c7 100644 (file)
@@ -690,7 +690,7 @@ rspamd_image_process_part(struct rspamd_task *task, struct rspamd_mime_part *par
                rh = rspamd_message_get_header_from_hash(part->raw_headers,
                                                                                                 "Content-Id", FALSE);
 
-               if (rh) {
+               if (rh && rh->decoded) {
                        cid = rh->decoded;
 
                        if (*cid == '<') {