diff mbox series

[v2,for,2025.02.x,12/15] package/clamav: add patch for CVE-2026-20339

Message ID 20260902131640.379588-13-titouan.christophe@mind.be
State New
Headers show
Series package/clamav: update to upstream LTS | expand

Commit Message

Titouan Christophe Sept. 2, 2026, 1:16 p.m. UTC
Signed-off-by: Titouan Christophe <titouan.christophe@mind.be>
---
 ...in-rebuilt-section-size-overflow-101.patch | 224 ++++++++++++++++++
 package/clamav/clamav.mk                      |   3 +
 2 files changed, 227 insertions(+)
 create mode 100644 package/clamav/0011-libclamav-fix-pespin-rebuilt-section-size-overflow-101.patch
diff mbox series

Patch

diff --git a/package/clamav/0011-libclamav-fix-pespin-rebuilt-section-size-overflow-101.patch b/package/clamav/0011-libclamav-fix-pespin-rebuilt-section-size-overflow-101.patch
new file mode 100644
index 0000000000..0746385ac7
--- /dev/null
+++ b/package/clamav/0011-libclamav-fix-pespin-rebuilt-section-size-overflow-101.patch
@@ -0,0 +1,224 @@ 
+From: "Val S." <valsnyde@cisco.com>
+Date: Tue, 28 Jul 2026 09:04:39 -0400
+Subject: Libclamav: fix PESpin rebuilt section size overflow (#101)
+
+The PESpin unpacker accumulated rebuilt section sizes in a signed int
+before allocating the intermediate rebuilt image buffer. A crafted PE with
+many sections could wrap the total to a small value and then copy full
+section contents into that undersized allocation.
+
+Track the rebuilt size in a 64-bit accumulator and reject additions that
+would exceed CLI_MAX_ALLOCATION before allocating. Also avoid related
+undefined behavior in the PESpin checksum and resource bitmap paths, and
+share bitmap-based cleanup through one helper.
+
+Reported-by: Feng Xue
+Also independently reported by: Yazdan Soltani
+
+CLAM-3008
+
+---
+Upstream: https://github.com/Cisco-Talos/clamav/commit/d442da6418391b84c6124717e0153237187972dd
+CVE: CVE-2026-20339
+Signed-off-by: Titouan Christophe <titouan.christophe@mind.be>
+---
+ libclamav/spin.c | 112 +++++++++++++++++++++++++++++++++++++----------
+ 1 file changed, 89 insertions(+), 23 deletions(-)
+
+diff --git a/libclamav/spin.c b/libclamav/spin.c
+index d0470b317a..a7e3a2d01d 100644
+--- a/libclamav/spin.c
++++ b/libclamav/spin.c
+@@ -62,6 +62,64 @@
+ #include "packlibs.h"
+ #include "spin.h"
+ 
++/**
++ * @brief Add a section size to the rebuilt PESpin image size.
++ *
++ * @return 0 if the size was added, 1 if the rebuilt image would exceed
++ * CLI_MAX_ALLOCATION.
++ */
++static int spin_add_blob_size(uint64_t *blobsz, uint32_t size)
++{
++    if (*blobsz > (uint64_t)CLI_MAX_ALLOCATION ||
++        (uint64_t)size > (uint64_t)CLI_MAX_ALLOCATION - *blobsz) {
++        cli_dbgmsg("spin: rebuilt section data exceeds allocation limit\n");
++        return 1;
++    }
++
++    *blobsz += size;
++    return 0;
++}
++
++/**
++ * @brief Replace an already-counted section size in the rebuilt PESpin image.
++ *
++ * @return 0 if the size was replaced, 1 if the previous size was not counted
++ * or the rebuilt image would exceed CLI_MAX_ALLOCATION.
++ */
++static int spin_replace_blob_size(uint64_t *blobsz, uint32_t old_size, uint32_t new_size)
++{
++    uint64_t adjusted_blobsz;
++
++    if (*blobsz < old_size) {
++        cli_dbgmsg("spin: rebuilt section data accounting underflow\n");
++        return 1;
++    }
++
++    adjusted_blobsz = *blobsz - old_size;
++    if (adjusted_blobsz > (uint64_t)CLI_MAX_ALLOCATION ||
++        (uint64_t)new_size > (uint64_t)CLI_MAX_ALLOCATION - adjusted_blobsz) {
++        cli_dbgmsg("spin: rebuilt section data exceeds allocation limit\n");
++        return 1;
++    }
++
++    *blobsz = adjusted_blobsz + new_size;
++    return 0;
++}
++
++/**
++ * @brief Free PESpin section buffers that are marked as grown in the bitmap.
++ */
++static void spin_free_grown_sections(char **sects, int sectcnt, uint32_t bitmap)
++{
++    int j;
++
++    for (j = 0; j < sectcnt; j++) {
++        if (bitmap & 1)
++            free(sects[j]);
++        bitmap >>= 1;
++    }
++}
++
+ static char exec86(uint8_t aelle, uint8_t cielle, char *curremu, int *retval)
+ {
+     int len = 0;
+@@ -143,7 +201,7 @@ static uint32_t summit(char *src, int size)
+     int i;
+ 
+     while (size) {
+-        eax ^= *src++ << 8 & 0xff00;
++        eax ^= ((uint32_t)(uint8_t)*src++ << 8) & 0xff00;
+         eax = eax >> 3 & 0x1fffffff;
+         for (i = 0; i < 4; i++) {
+             uint32_t swap;
+@@ -164,7 +222,8 @@ int unspin(char *src, int ssize, struct cli_exe_section *sections, int sectcnt,
+ {
+     char *curr, *emu, *ep, *spinned;
+     char **sects;
+-    int blobsz = 0, j;
++    uint64_t blobsz = 0;
++    int j;
+     uint32_t key32, bitmap, bitman;
+     uint32_t len;
+     uint8_t key8;
+@@ -402,12 +461,16 @@ int unspin(char *src, int ssize, struct cli_exe_section *sections, int sectcnt,
+     len = 0;
+     for (j = 0; j < sectcnt; j++) {
+         if (bitmap & 1) {
++            if (spin_add_blob_size(&blobsz, sections[j].vsz)) {
++                len = 1;
++                break;
++            }
++
+             if ((sects[j] = (char *)cli_max_malloc(sections[j].vsz)) == NULL) {
+                 cli_dbgmsg("spin: malloc(%u) failed\n", sections[j].vsz);
+                 len = 1;
+                 break;
+             }
+-            blobsz += sections[j].vsz;
+             memset(sects[j], 0, sections[j].vsz);
+             cli_dbgmsg("spin: Growing sect%d: was %x will be %x\n", j, sections[j].rsz, sections[j].vsz);
+             if (cli_unfsg(src + sections[j].raw, sects[j], sections[j].rsz, sections[j].vsz, NULL, NULL) == -1) {
+@@ -415,7 +478,11 @@ int unspin(char *src, int ssize, struct cli_exe_section *sections, int sectcnt,
+                 cli_dbgmsg("spin: Unpack failure\n");
+             }
+         } else {
+-            blobsz += sections[j].rsz;
++            if (spin_add_blob_size(&blobsz, sections[j].rsz)) {
++                len = 1;
++                break;
++            }
++
+             sects[j] = src + sections[j].raw;
+             cli_dbgmsg("spin: Not growing sect%d\n", j);
+         }
+@@ -425,12 +492,7 @@ int unspin(char *src, int ssize, struct cli_exe_section *sections, int sectcnt,
+     cli_dbgmsg("spin: decompression complete\n");
+ 
+     if (len) {
+-        int t;
+-        for (t = 0; t < j; t++) {
+-            if (bitman & 1)
+-                free(sects[t]);
+-            bitman = bitman >> 1 & 0x7fffffff;
+-        }
++        spin_free_grown_sections(sects, j, bitman);
+         free(sects);
+         return 1;
+     }
+@@ -444,7 +506,7 @@ int unspin(char *src, int ssize, struct cli_exe_section *sections, int sectcnt,
+                 break;
+         }
+ 
+-        if (j != sectcnt && ((bitman & (1 << j)) == 0)) { /* FIXME: not really sure either the res sect is lamed or just compressed, but this'll save some major headaches */
++        if (j != sectcnt && j < 32 && ((bitman & ((uint32_t)1 << j)) == 0)) { /* FIXME: not really sure either the res sect is lamed or just compressed, but this'll save some major headaches */
+             cli_dbgmsg("spin: Resources (sect%d) appear to be compressed\n\tuncompressed offset %x, len %x\n\tcompressed offset %x, len %x\n", j, sections[j].rva, key32 - sections[j].rva, key32, sections[j].vsz - (key32 - sections[j].rva));
+ 
+             if ((curr = (char *)cli_max_malloc(sections[j].vsz)) != NULL) {
+@@ -454,26 +516,34 @@ int unspin(char *src, int ssize, struct cli_exe_section *sections, int sectcnt,
+ 
+                     free(curr);
+                     cli_dbgmsg("spin: Failed to grow resources, continuing anyway\n");
+-                    blobsz += sections[j].rsz;
+                 } else {
+-                    sects[j] = curr;
+-                    bitman |= 1 << j;
+-                    cli_dbgmsg("spin: Resources grown\n");
+-                    blobsz += sections[j].vsz;
++                    if (spin_replace_blob_size(&blobsz, sections[j].rsz, sections[j].vsz)) {
++                        free(curr);
++                        len = 1;
++                    } else {
++                        sects[j] = curr;
++                        bitman |= (uint32_t)1 << j;
++                        cli_dbgmsg("spin: Resources grown\n");
++                    }
+                 }
+             } else {
+                 /* malloc failed but i'm too deep into this crap to quit without leaking more :( */
+                 cli_dbgmsg("spin: memory allocation failed, continuing anyway\n");
+-                blobsz += sections[j].rsz;
+             }
+         } else {
+             cli_dbgmsg("spin: No res?!\n");
+         }
+     }
+ 
++    if (len) {
++        spin_free_grown_sections(sects, sectcnt, bitman);
++        free(sects);
++        return 1;
++    }
++
+     bitmap = bitman; /* save as a free() bitmap */
+ 
+-    if ((ep = (char *)cli_max_malloc(blobsz)) != NULL) {
++    if ((ep = (char *)cli_max_malloc((size_t)blobsz)) != NULL) {
+         struct cli_exe_section *rebhlp;
+         if ((rebhlp = (struct cli_exe_section *)cli_max_malloc(sizeof(struct cli_exe_section) * (sectcnt))) != NULL) {
+             char *to   = ep;
+@@ -505,11 +575,7 @@ int unspin(char *src, int ssize, struct cli_exe_section *sections, int sectcnt,
+     }
+ 
+     cli_dbgmsg("spin: free bitmap is %x\n", bitman);
+-    for (j = 0; j < sectcnt; j++) {
+-        if (bitmap & 1)
+-            free(sects[j]);
+-        bitmap = bitmap >> 1 & 0x7fffffff;
+-    }
++    spin_free_grown_sections(sects, sectcnt, bitmap);
+     free(sects);
+     return 1; /* :( */
+ }
diff --git a/package/clamav/clamav.mk b/package/clamav/clamav.mk
index 787b2955c2..a70ac71b02 100644
--- a/package/clamav/clamav.mk
+++ b/package/clamav/clamav.mk
@@ -51,6 +51,9 @@  CLAMAV_IGNORE_CVES += CVE-2026-20244
 # 0010-libclamav-fix-gpt-partition-name-conversion-index-103.patch
 CLAMAV_IGNORE_CVES += CVE-2026-20345
 
+# 0011-libclamav-fix-pespin-rebuilt-section-size-overflow-101.patch
+CLAMAV_IGNORE_CVES += CVE-2026-20339
+
 CLAMAV_DEPENDENCIES = \
 	bzip2 \
 	host-pkgconf \