]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
[Fix] archives: bounds guards for RAR/ZIP/7-zip parsing
authorVsevolod Stakhov <vsevolod@rspamd.com>
Sun, 31 May 2026 13:45:56 +0000 (14:45 +0100)
committerVsevolod Stakhov <vsevolod@rspamd.com>
Sun, 31 May 2026 13:45:56 +0000 (14:45 +0100)
Several archive parsers could read slightly past the input buffer on
crafted (attacker-controlled) attachments:

- RAR v4 file header: fname_len was validated against the remaining
  buffer, but p then advanced past the attrs and optional
  HIGH_PACK_SIZE/HIGH_UNP_SIZE fields (4-12 bytes) before the filename
  was read, allowing an over-read of up to 12 bytes. Re-validate
  fname_len at the point of use.

- 7-zip: rspamd_7zip_read_next_section, _read_digest and _read_bits
  dereferenced *p before any bounds check; a section/type byte landing
  on the last byte of the buffer (e.g. a trailing kCRC or kHeader) led
  to a one-byte over-read. Guard p < end before the dereference.
  rspamd_7zip_read_archive_props guarded only p != NULL; also require
  p < end.

- ZIP central-directory extra-field loop advanced p by an
  attacker-controlled hlen without checking it against the remaining
  extra-field length, producing a past-the-end pointer. Clamp the
  advance and stop on a truncated field.

All reads, no writes; impact is a potential crash on malformed input.

src/libmime/archives.c

index c0b69b83911adb64b1252e09406b7271ed53ff74..e9ffa3cac19c32676969918d4af79818c85be73f 100644 (file)
@@ -349,6 +349,10 @@ rspamd_archive_process_zip(struct rspamd_task *task,
                                f->flags |= RSPAMD_ARCHIVE_FILE_ENCRYPTED;
                        }
 
+                       if (hlen + sizeof(uint16_t) * 2 > (gsize) (extra + extra_len - p)) {
+                               /* Truncated or malformed extra field */
+                               break;
+                       }
                        p += hlen + sizeof(uint16_t) * 2;
                }
 
@@ -544,6 +548,17 @@ rspamd_archive_process_rar_v4(struct rspamd_task *task, const unsigned char *sta
                                uncomp_sz += tmp;
                        }
 
+                       /*
+                        * p advanced past the attrs and optional HIGH_*_SIZE fields
+                        * after fname_len was validated above, so re-check it against
+                        * the remaining buffer before reading the filename.
+                        */
+                       if (fname_len > (gsize) (end - p)) {
+                               msg_debug_archive("rar archive is invalid (truncated filename)");
+
+                               return;
+                       }
+
                        f = g_malloc0(sizeof(*f));
 
                        if (flags & 0x200) {
@@ -1008,6 +1023,10 @@ rspamd_7zip_read_bits(struct rspamd_task *task,
 
        for (i = 0; i < nbits; i++) {
                if (mask == 0) {
+                       if (p >= end) {
+                               return NULL;
+                       }
+
                        avail = *p;
                        SZ_SKIP_BYTES(1);
                        mask = 0x80;
@@ -1032,6 +1051,10 @@ rspamd_7zip_read_digest(struct rspamd_task *task,
                                                uint64_t num_streams,
                                                unsigned int *pdigest_read)
 {
+       if (p >= end) {
+               return NULL;
+       }
+
        unsigned char all_defined = *p;
        uint64_t i;
        unsigned int num_defined = 0;
@@ -1526,7 +1549,7 @@ rspamd_7zip_read_archive_props(struct rspamd_task *task,
         * }
         */
 
-       if (p != NULL) {
+       if (p != NULL && p < end) {
                proptype = *p;
                SZ_SKIP_BYTES(1);
 
@@ -1702,6 +1725,10 @@ rspamd_7zip_read_next_section(struct rspamd_task *task,
                                                          struct rspamd_archive *arch,
                                                          struct rspamd_mime_part *part)
 {
+       if (p >= end) {
+               return NULL;
+       }
+
        unsigned char t = *p;
 
        SZ_SKIP_BYTES(1);