diff mbox series

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

Message ID 20260902131640.379588-15-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>
---
 ...-harden-mach-o-section-validation-96.patch | 149 ++++++++++++++++++
 package/clamav/clamav.mk                      |   3 +
 2 files changed, 152 insertions(+)
 create mode 100644 package/clamav/0013-libclamav-harden-mach-o-section-validation-96.patch
diff mbox series

Patch

diff --git a/package/clamav/0013-libclamav-harden-mach-o-section-validation-96.patch b/package/clamav/0013-libclamav-harden-mach-o-section-validation-96.patch
new file mode 100644
index 0000000000..b28d03b7b8
--- /dev/null
+++ b/package/clamav/0013-libclamav-harden-mach-o-section-validation-96.patch
@@ -0,0 +1,149 @@ 
+From: "Val S." <valsnyde@cisco.com>
+Date: Tue, 28 Jul 2026 09:06:22 -0400
+Subject: Libclamav: harden Mach-O section validation (#96)
+
+Mach-O encodes a section's alignment as a base-2 exponent. A malformed
+exponent can trigger undefined behavior in the signed shift used to
+compute the alignment, and unchecked rounding can overflow the 32-bit
+raw-size field.
+
+Use a shared helper for 32- and 64-bit sections. Reject exponents above
+31, perform the shift in uint64_t so exponent 31 remains valid, and
+reject file-backed section sizes or rounded sizes that exceed
+UINT32_MAX before narrowing them into cli_exe_section.
+
+Review also identified valid virtual sections that must not be subject
+to file-backed raw-size limits. Treat S_ZEROFILL, S_GB_ZEROFILL, and
+S_THREAD_LOCAL_ZEROFILL as occupying no file bytes while continuing to
+validate their alignment.
+
+The original alignment issue was reported by Tristan (@TristanInSec).
+
+CLAM-3002
+
+---
+Upstream: https://github.com/Cisco-Talos/clamav/commit/617a2f51b0eddf961766164cba726df0ba574df8
+CVE: CVE-2026-20347
+Signed-off-by: Titouan Christophe <titouan.christophe@mind.be>
+---
+ libclamav/macho.c | 75 ++++++++++++++++++++++++++++++++++++++++++-----
+ 1 file changed, 68 insertions(+), 7 deletions(-)
+
+diff --git a/libclamav/macho.c b/libclamav/macho.c
+index 25f70554fc..52f56ab43f 100644
+--- a/libclamav/macho.c
++++ b/libclamav/macho.c
+@@ -46,6 +46,11 @@
+ #define EC32(v, conv) (conv ? cbswap32(v) : v)
+ #define EC64(v, conv) (conv ? cbswap64(v) : v)
+ 
++#define MACHO_SECTION_TYPE_MASK 0x000000ff
++#define MACHO_S_ZEROFILL              0x1
++#define MACHO_S_GB_ZEROFILL           0xc
++#define MACHO_S_THREAD_LOCAL_ZEROFILL 0x12
++
+ struct macho_hdr {
+     uint32_t magic;
+     uint32_t cpu_type;
+@@ -195,6 +200,52 @@ static uint32_t cli_rawaddr(uint32_t vaddr, struct cli_exe_section *sects, uint1
+     return vaddr - sects[i].rva + sects[i].raw;
+ }
+ 
++/**
++ * Calculate the raw section size implied by a Mach-O alignment exponent.
++ *
++ * Mach-O section alignment is encoded as log2(bytes). Reject malformed
++ * exponents and rounded sizes that cannot fit in cli_exe_section.rsz.
++ * Zero-fill sections do not occupy file bytes, so they have no raw size.
++ */
++static bool cli_macho_section_raw_size(uint64_t virtual_size,
++                                       uint32_t align_exponent,
++                                       uint32_t section_flags,
++                                       uint32_t *raw_size)
++{
++    uint64_t alignment;
++    uint64_t remainder;
++    uint64_t padding;
++    uint64_t rounded_size;
++    uint32_t section_type = section_flags & MACHO_SECTION_TYPE_MASK;
++
++    if (align_exponent > 31) {
++        return false;
++    }
++
++    if (section_type == MACHO_S_ZEROFILL ||
++        section_type == MACHO_S_GB_ZEROFILL ||
++        section_type == MACHO_S_THREAD_LOCAL_ZEROFILL) {
++        *raw_size = 0;
++        return true;
++    }
++
++    if (virtual_size > UINT32_MAX) {
++        return false;
++    }
++
++    alignment = (uint64_t)1 << align_exponent;
++    remainder    = virtual_size % alignment;
++    padding      = (alignment - remainder) % alignment;
++    rounded_size = virtual_size + padding;
++
++    if (rounded_size > UINT32_MAX) {
++        return false;
++    }
++
++    *raw_size = (uint32_t)rounded_size;
++    return true;
++}
++
+ cl_error_t cli_scanmacho(cli_ctx *ctx, struct cli_exe_info *fileinfo)
+ {
+     struct macho_hdr hdr;
+@@ -383,17 +434,26 @@ cl_error_t cli_scanmacho(cli_ctx *ctx, struct cli_exe_info *fileinfo)
+ 
+             for (j = 0; j < nsects; j++) {
+                 if (m64) {
++                    uint64_t section_size;
++
+                     if (fmap_readn(map, &section64, at, sizeof(section64)) != sizeof(section64)) {
+                         cli_dbgmsg("cli_scanmacho: Can't read section\n");
+                         free(sections);
+                         RETURN_BROKEN;
+                     }
+                     at += sizeof(section64);
++                    section_size = EC64(section64.size, conv);
+                     sections[sect].rva = EC64(section64.addr, conv);
+-                    sections[sect].vsz = EC64(section64.size, conv);
+                     sections[sect].raw = EC32(section64.offset, conv);
+-                    section64.align    = 1 << EC32(section64.align, conv);
+-                    sections[sect].rsz = sections[sect].vsz + (section64.align - (sections[sect].vsz % section64.align)) % section64.align; /* most likely we can assume it's the same as .vsz */
++                    if (!cli_macho_section_raw_size(section_size,
++                                                    EC32(section64.align, conv),
++                                                    EC32(section64.flags, conv),
++                                                    &sections[sect].rsz)) {
++                        cli_dbgmsg("cli_scanmacho: Section alignment or size is malformed\n");
++                        free(sections);
++                        RETURN_BROKEN;
++                    }
++                    sections[sect].vsz = (uint32_t)section_size;
+                     strncpy(name, section64.sectname, sizeof(name));
+                     name[sizeof(name) - 1] = '\0';
+                 } else {
+@@ -406,13 +466,14 @@ cl_error_t cli_scanmacho(cli_ctx *ctx, struct cli_exe_info *fileinfo)
+                     sections[sect].rva = EC32(section.addr, conv);
+                     sections[sect].vsz = EC32(section.size, conv);
+                     sections[sect].raw = EC32(section.offset, conv);
+-                    if (EC32(section.align, conv) >= 32) {
+-                        cli_dbgmsg("cli_scanmacho: Section aligned is malformed\n");
++                    if (!cli_macho_section_raw_size(sections[sect].vsz,
++                                                    EC32(section.align, conv),
++                                                    EC32(section.flags, conv),
++                                                    &sections[sect].rsz)) {
++                        cli_dbgmsg("cli_scanmacho: Section alignment or size is malformed\n");
+                         free(sections);
+                         RETURN_BROKEN;
+                     }
+-                    section.align      = 1 << EC32(section.align, conv);
+-                    sections[sect].rsz = sections[sect].vsz + (section.align - (sections[sect].vsz % section.align)) % section.align;
+                     strncpy(name, section.sectname, sizeof(name));
+                     name[sizeof(name) - 1] = '\0';
+                 }
diff --git a/package/clamav/clamav.mk b/package/clamav/clamav.mk
index aa63dbc23b..a9bc1243c1 100644
--- a/package/clamav/clamav.mk
+++ b/package/clamav/clamav.mk
@@ -57,6 +57,9 @@  CLAMAV_IGNORE_CVES += CVE-2026-20339
 # 0012-libclamav-guard-pdf-hex-string-newline-skip-98.patch
 CLAMAV_IGNORE_CVES += CVE-2026-20346
 
+# 0013-libclamav-harden-mach-o-section-validation-96.patch
+CLAMAV_IGNORE_CVES += CVE-2026-20347
+
 CLAMAV_DEPENDENCIES = \
 	bzip2 \
 	host-pkgconf \