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." +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 +--- + 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, §ion64, 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), ++ §ions[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), ++ §ions[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 \