From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from smtp.gentoo.org (woodpecker.gentoo.org [140.211.166.183]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits)) (No client certificate requested) by finch.gentoo.org (Postfix) with ESMTPS id DC035158074 for ; Thu, 19 Jun 2025 01:46:27 +0000 (UTC) Received: from lists.gentoo.org (bobolink.gentoo.org [140.211.166.189]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519) (No client certificate requested) (Authenticated sender: relay-lists.gentoo.org@gentoo.org) by smtp.gentoo.org (Postfix) with ESMTPSA id C1C8E34100A for ; Thu, 19 Jun 2025 01:46:27 +0000 (UTC) Received: from bobolink.gentoo.org (localhost [127.0.0.1]) by bobolink.gentoo.org (Postfix) with ESMTP id B6AB31104D9; Thu, 19 Jun 2025 01:46:26 +0000 (UTC) Received: from smtp.gentoo.org (smtp.gentoo.org [IPv6:2001:470:ea4a:1:5054:ff:fec7:86e4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519) (No client certificate requested) by bobolink.gentoo.org (Postfix) with ESMTPS id AC4251104D9 for ; Thu, 19 Jun 2025 01:46:26 +0000 (UTC) Received: from oystercatcher.gentoo.org (oystercatcher.gentoo.org [148.251.78.52]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519) (No client certificate requested) by smtp.gentoo.org (Postfix) with ESMTPS id F2FC934100A for ; Thu, 19 Jun 2025 01:46:25 +0000 (UTC) Received: from localhost.localdomain (localhost [IPv6:::1]) by oystercatcher.gentoo.org (Postfix) with ESMTP id 93A9C29F7 for ; Thu, 19 Jun 2025 01:46:24 +0000 (UTC) From: "Michael Orlitzky" To: gentoo-commits@lists.gentoo.org Content-Transfer-Encoding: 8bit Content-type: text/plain; charset=UTF-8 Reply-To: gentoo-dev@lists.gentoo.org, "Michael Orlitzky" Message-ID: <1750297497.dab0419a1723cf871b14cd02cbef12c9c05cdbe7.mjo@gentoo> Subject: [gentoo-commits] repo/gentoo:master commit in: app-antivirus/clamav/, app-antivirus/clamav/files/ X-VCS-Repository: repo/gentoo X-VCS-Files: app-antivirus/clamav/clamav-0.103.12-r2.ebuild app-antivirus/clamav/files/clamav-0.103.12-cve-2025-20260.patch app-antivirus/clamav/files/clamav-0.103.12-fix-lzma-uaf.patch X-VCS-Directories: app-antivirus/clamav/ app-antivirus/clamav/files/ X-VCS-Committer: mjo X-VCS-Committer-Name: Michael Orlitzky X-VCS-Revision: dab0419a1723cf871b14cd02cbef12c9c05cdbe7 X-VCS-Branch: master Date: Thu, 19 Jun 2025 01:46:24 +0000 (UTC) Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-Id: Gentoo Linux mail X-BeenThere: gentoo-commits@lists.gentoo.org X-Auto-Response-Suppress: DR, RN, NRN, OOF, AutoReply X-Archives-Salt: 4e88ded8-fc89-4123-9907-05685a7cd208 X-Archives-Hash: fee4804b644c88fab45ff98733a8067e commit: dab0419a1723cf871b14cd02cbef12c9c05cdbe7 Author: Michael Orlitzky gentoo org> AuthorDate: Thu Jun 19 00:23:12 2025 +0000 Commit: Michael Orlitzky gentoo org> CommitDate: Thu Jun 19 01:44:57 2025 +0000 URL: https://gitweb.gentoo.org/repo/gentoo.git/commit/?id=dab0419a app-antivirus/clamav: backport security fixes from v1.0.9 Signed-off-by: Michael Orlitzky gentoo.org> app-antivirus/clamav/clamav-0.103.12-r2.ebuild | 239 +++++++++++++++ .../files/clamav-0.103.12-cve-2025-20260.patch | 341 +++++++++++++++++++++ .../files/clamav-0.103.12-fix-lzma-uaf.patch | 34 ++ 3 files changed, 614 insertions(+) diff --git a/app-antivirus/clamav/clamav-0.103.12-r2.ebuild b/app-antivirus/clamav/clamav-0.103.12-r2.ebuild new file mode 100644 index 000000000000..a3163edc0d0c --- /dev/null +++ b/app-antivirus/clamav/clamav-0.103.12-r2.ebuild @@ -0,0 +1,239 @@ +# Copyright 1999-2025 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 + +EAPI=8 + +inherit autotools flag-o-matic systemd tmpfiles + +DESCRIPTION="Clam Anti-Virus Scanner" +HOMEPAGE="https://www.clamav.net/" +SRC_URI="https://www.clamav.net/downloads/production/${P}.tar.gz" + +LICENSE="GPL-2 unRAR" +SLOT="0/lts" +KEYWORDS="~alpha ~amd64 ~arm ~arm64 ~hppa ~ppc ~ppc64 ~riscv ~sparc ~x86 ~amd64-linux ~x86-linux ~ppc-macos" +IUSE="bzip2 doc clamonacc clamdtop clamsubmit iconv libclamav-only milter metadata-analysis-api selinux systemd test xml" + +REQUIRED_USE="libclamav-only? ( !clamonacc !clamdtop !clamsubmit !milter !metadata-analysis-api )" + +RESTRICT="!test? ( test )" + +# Require acct-{user,group}/clamav at build time so that we can set +# the permissions on /var/lib/clamav in src_install rather than in +# pkg_postinst; calling "chown" on the live filesystem scares me. +CDEPEND="acct-group/clamav + acct-user/clamav + dev-libs/libltdl + dev-libs/libmspack + || ( dev-libs/libpcre2 >dev-libs/libpcre-6 ) + dev-libs/tomsfastmath + >=sys-libs/zlib-1.2.2:= + bzip2? ( app-arch/bzip2 ) + clamdtop? ( sys-libs/ncurses:0 ) + clamsubmit? ( net-misc/curl dev-libs/json-c:= ) + elibc_musl? ( sys-libs/fts-standalone ) + iconv? ( virtual/libiconv ) + !libclamav-only? ( net-misc/curl ) + dev-libs/openssl:0= + milter? ( mail-filter/libmilter:= ) + xml? ( dev-libs/libxml2:= )" + +# We need at least autoconf-2.69-r5 because that's the first (patched) +# version of it in Gentoo that supports ./configure --runstatedir. +BDEPEND=">=dev-build/autoconf-2.69-r5 + virtual/pkgconfig" + +DEPEND="${CDEPEND} + metadata-analysis-api? ( dev-libs/json-c:* ) + test? ( dev-libs/check )" +RDEPEND="${CDEPEND} + selinux? ( sec-policy/selinux-clamav )" + +PATCHES=( + "${FILESDIR}/${PN}-0.102.1-libxml2_pkgconfig.patch" #661328 + "${FILESDIR}/${PN}-0.102.2-fix-curl-detection.patch" #709616 + "${FILESDIR}/${PN}-0.103.0-system-tomsfastmath.patch" # 649394 + "${FILESDIR}/${PN}-0.103.1-upstream-openrc.patch" + "${FILESDIR}/${PN}-0.103.12-missing-const.patch" + "${FILESDIR}/${PN}-0.103.12-fix-lzma-uaf.patch" + "${FILESDIR}/${PN}-0.103.12-cve-2025-20260.patch" +) + +src_prepare() { + default + + # Be extra sure that we're using the system copy of tomsfastmath + einfo "removing bundled copy of dev-libs/tomsfastmath" + rm -r libclamav/tomsfastmath || \ + die "failed to remove bundled tomsfastmath" + + AT_NO_RECURSIVE="yes" eautoreconf +} + +src_configure() { + use elibc_musl && append-ldflags -lfts + use ppc64 && append-flags -mminimal-toc + + # according to configure help it should be + # $(use_enable xml) + # but that does not work + # do not add this, since --disable-xml seems to override + # --without-xml + JSONUSE="--without-libjson" + + if use clamsubmit || use metadata-analysis-api; then + # either of those 2 requires libjson. + # clamsubmit will be built as soon as libjson and curl are found + # but we only install the binary if requested + JSONUSE="--with-libjson=${EPREFIX}/usr" + fi + + local myeconfargs=( + $(use_enable bzip2) + $(use_enable clamonacc) + $(use_enable clamdtop) + $(use_enable milter) + $(use_enable test check) + $(use_with xml) + $(use_with iconv) + ${JSONUSE} + $(use_enable libclamav-only) + $(use_with !libclamav-only libcurl) + --enable-ipv6 + --with-system-libmspack + --cache-file="${S}"/config.cache + --disable-experimental + --disable-static + --disable-zlib-vcheck + --enable-id-check + --with-dbdir="${EPREFIX}"/var/lib/clamav + # Don't call --with-zlib=/usr (see bug #699296) + --with-zlib + --disable-llvm + --enable-openrc + --runstatedir=/run + ) + econf "${myeconfargs[@]}" +} + +src_install() { + default + + rm -rf "${ED}"/var/lib/clamav || die + + if ! use libclamav-only ; then + if use systemd; then + # The tmpfiles entry is behind USE=systemd because the + # OpenRC service scripts should (and do) ensure that the + # directories they need exist and have the correct + # permissions without the help of tmpfiles. + newtmpfiles "${FILESDIR}/tmpfiles.d/clamav-r1.conf" clamav.conf + systemd_newunit "${FILESDIR}/clamd_at.service" "clamd@.service" + systemd_dounit "${FILESDIR}/clamd.service" + systemd_newunit "${FILESDIR}/freshclamd.service-r1" \ + "freshclamd.service" + fi + + insinto /etc/logrotate.d + newins "${FILESDIR}/clamd.logrotate" clamd + newins "${FILESDIR}/freshclam.logrotate" freshclam + use milter && \ + newins "${FILESDIR}/clamav-milter.logrotate-r1" clamav-milter + + # Modify /etc/{clamd,freshclam}.conf to be usable out of the box + sed -i -e "s:^\(Example\):\# \1:" \ + -e "s/^#\(PidFile .*\)/\1/" \ + -e "s/^#\(LocalSocket .*\)/\1/" \ + -e "s/^#\(User .*\)/\1/" \ + -e "s:^\#\(LogFile\) .*:\1 ${EPREFIX}/var/log/clamav/clamd.log:" \ + -e "s:^\#\(LogTime\).*:\1 yes:" \ + -e "s/^#\(DatabaseDirectory .*\)/\1/" \ + "${ED}"/etc/clamd.conf.sample || die + + sed -i -e "s:^\(Example\):\# \1:" \ + -e "s/^#\(PidFile .*\)/\1/" \ + -e "s/^#\(DatabaseOwner .*\)/\1/" \ + -e "s:^\#\(UpdateLogFile\) .*:\1 ${EPREFIX}/var/log/clamav/freshclam.log:" \ + -e "s:^\#\(NotifyClamd\).*:\1 ${EPREFIX}/etc/clamd.conf:" \ + -e "s:^\#\(ScriptedUpdates\).*:\1 yes:" \ + -e "s/^#\(DatabaseDirectory .*\)/\1/" \ + "${ED}"/etc/freshclam.conf.sample || die + + if use milter ; then + # Note: only keep the "unix" ClamdSocket and MilterSocket! + sed -i -e "s:^\(Example\):\# \1:" \ + -e "s/^#\(PidFile .*\)/\1/" \ + -e "s/^#\(ClamdSocket unix:.*\)/\1/" \ + -e "s/^#\(User .*\)/\1/" \ + -e "s/^#\(MilterSocket unix:.*\)/\1/" \ + -e "s:^\#\(LogFile\) .*:\1 ${EPREFIX}/var/log/clamav/clamav-milter.log:" \ + "${ED}"/etc/clamav-milter.conf.sample || die + + cat >> "${ED}"/etc/conf.d/clamd <<-EOF + MILTER_NICELEVEL=19 + START_MILTER=no + EOF + + systemd_newunit "${FILESDIR}/clamav-milter.service-r1" clamav-milter.service + fi + + local i + for i in clamd freshclam clamav-milter + do + if [[ -f "${ED}"/etc/"${i}".conf.sample ]]; then + mv "${ED}"/etc/"${i}".conf{.sample,} || die + fi + done + + # These both need to be writable by the clamav user. + # TODO: use syslog by default; that's what it's for. + diropts -o clamav -g clamav + keepdir /var/lib/clamav + keepdir /var/log/clamav + fi + + if use doc ; then + local HTML_DOCS=( docs/html/. ) + einstalldocs + + if ! use libclamav-only ; then + doman docs/man/*.[1-8] + fi + fi + + find "${ED}" -name '*.la' -delete || die +} + +src_test() { + if use libclamav-only ; then + ewarn "Test target not available when USE=libclamav-only is set, skipping tests ..." + return 0 + fi + + emake quick-check +} + +pkg_postinst() { + if ! use libclamav-only ; then + if use systemd ; then + tmpfiles_process clamav.conf + fi + fi + + if use milter ; then + elog "For simple instructions how to setup the clamav-milter read the" + elog "clamav-milter.README.gentoo in /usr/share/doc/${PF}" + fi + + local databases=( "${EROOT}"/var/lib/clamav/main.c[lv]d ) + if [[ ! -f "${databases}" ]] ; then + ewarn "You must run freshclam manually to populate the virus database" + ewarn "before starting clamav for the first time." + fi + + ewarn "This version of ClamAV provides separate OpenRC services" + ewarn "for clamd, freshclam, clamav-milter, and clamonacc. The" + ewarn "clamd service now starts only the clamd daemon itself. You" + ewarn "should add freshclam (and perhaps clamav-milter) to any" + ewarn "runlevels that previously contained clamd." +} diff --git a/app-antivirus/clamav/files/clamav-0.103.12-cve-2025-20260.patch b/app-antivirus/clamav/files/clamav-0.103.12-cve-2025-20260.patch new file mode 100644 index 000000000000..600b23cae179 --- /dev/null +++ b/app-antivirus/clamav/files/clamav-0.103.12-cve-2025-20260.patch @@ -0,0 +1,341 @@ +Taken from + + https://github.com/Cisco-Talos/clamav/commit/fb541aed643 + +and applied with a little fuzz to the 0.103.x branch. The bug is not +actually exploitable on 0.103.x (according to the release notes), but +we might as well apply the readily-available fix. + +diff --git a/libclamav/pdf.c b/libclamav/pdf.c +index b8135f6..fe50146 100644 +--- a/libclamav/pdf.c ++++ b/libclamav/pdf.c +@@ -441,7 +441,7 @@ int pdf_findobj_in_objstm(struct pdf_struct *pdf, struct objstm_struct *objstm, + + if (CL_SUCCESS != cli_strntol_wrap(index, bytes_remaining, 0, 10, &temp_long)) { + /* Failed to find obj offset for next obj */ +- cli_dbgmsg("pdf_findobj_in_objstm: Failed to find next obj offset for obj in object stream though there should be {%u} more.\n", objstm->n - objstm->nobjs_found); ++ cli_dbgmsg("pdf_findobj_in_objstm: Failed to find next obj offset for obj in object stream though there should be {%zu} more.\n", objstm->n - objstm->nobjs_found); + status = CL_EPARSE; + goto done; + } else if (temp_long < 0) { +@@ -1551,18 +1551,18 @@ cl_error_t pdf_extract_obj(struct pdf_struct *pdf, struct pdf_obj *obj, uint32_t + } + } + +- cli_dbgmsg("pdf_extract_obj: calculated length %lld\n", (long long)length); ++ cli_dbgmsg("pdf_extract_obj: calculated length %zu\n", length); + } else { + if (obj->stream_size > (size_t)length + 2) { + cli_dbgmsg("cli_pdf: calculated length %zu < %zu\n", +- (size_t)length, obj->stream_size); ++ length, obj->stream_size); + length = obj->stream_size; + } + } + +- if ((0 != orig_length) && (obj->stream_size > (size_t)orig_length + 20)) { +- cli_dbgmsg("pdf_extract_obj: orig length: %lld, length: %lld, size: %zu\n", +- (long long)orig_length, (long long)length, obj->stream_size); ++ if ((0 != orig_length) && (obj->stream_size > orig_length + 20)) { ++ cli_dbgmsg("pdf_extract_obj: orig length: %zu, length: %zu, size: %zu\n", ++ orig_length, length, obj->stream_size); + pdfobj_flag(pdf, obj, BAD_STREAMLEN); + } + +@@ -1613,18 +1613,18 @@ cl_error_t pdf_extract_obj(struct pdf_struct *pdf, struct pdf_obj *obj, uint32_t + */ + dict_len = obj->stream - start; + if (NULL != (pstr = pdf_getdict(start, &dict_len, "/Type/ObjStm"))) { +- int32_t objstm_first = -1; +- int32_t objstm_length = -1; +- int32_t objstm_n = -1; ++ int objstm_first = -1; ++ int objstm_length = -1; ++ int objstm_n = -1; + + cli_dbgmsg("pdf_extract_obj: Found /Type/ObjStm\n"); + + dict_len = obj->stream - start; +- if ((-1 == (objstm_first = pdf_readint(start, dict_len, "/First")))) { ++ if (-1 == (objstm_first = pdf_readint(start, dict_len, "/First"))) { + cli_warnmsg("pdf_extract_obj: Failed to find offset of first object in object stream\n"); +- } else if ((-1 == (objstm_length = pdf_readint(start, dict_len, "/Length")))) { ++ } else if (-1 == (objstm_length = pdf_readint(start, dict_len, "/Length"))) { + cli_warnmsg("pdf_extract_obj: Failed to find length of object stream\n"); +- } else if ((-1 == (objstm_n = pdf_readint(start, dict_len, "/N")))) { ++ } else if (-1 == (objstm_n = pdf_readint(start, dict_len, "/N"))) { + cli_warnmsg("pdf_extract_obj: Failed to find num objects in object stream\n"); + } else { + /* Add objstm to pdf struct, so it can be freed eventually */ +@@ -1646,19 +1646,19 @@ cl_error_t pdf_extract_obj(struct pdf_struct *pdf, struct pdf_obj *obj, uint32_t + + memset(objstm, 0, sizeof(*objstm)); + +- objstm->first = (uint32_t)objstm_first; +- objstm->current = (uint32_t)objstm_first; ++ objstm->first = (size_t)objstm_first; ++ objstm->current = (size_t)objstm_first; + objstm->current_pair = 0; +- objstm->length = (uint32_t)objstm_length; +- objstm->n = (uint32_t)objstm_n; ++ objstm->length = (size_t)objstm_length; ++ objstm->n = (size_t)objstm_n; + +- cli_dbgmsg("pdf_extract_obj: ObjStm first obj at offset %d\n", objstm->first); +- cli_dbgmsg("pdf_extract_obj: ObjStm length is %d bytes\n", objstm->length); +- cli_dbgmsg("pdf_extract_obj: ObjStm should contain %d objects\n", objstm->n); ++ cli_dbgmsg("pdf_extract_obj: ObjStm first obj at offset %zu\n", objstm->first); ++ cli_dbgmsg("pdf_extract_obj: ObjStm length is %zu bytes\n", objstm->length); ++ cli_dbgmsg("pdf_extract_obj: ObjStm should contain %zu objects\n", objstm->n); + } + } + +- sum = pdf_decodestream(pdf, obj, dparams, obj->stream, (uint32_t)length, xref, fout, &rc, objstm); ++ sum = pdf_decodestream(pdf, obj, dparams, obj->stream, length, xref, fout, &rc, objstm); + if ((CL_SUCCESS != rc) && (CL_VIRUS != rc)) { + cli_dbgmsg("Error decoding stream! Error code: %d\n", rc); + +@@ -3341,7 +3341,7 @@ cl_error_t pdf_find_and_parse_objs_in_objstm(struct pdf_struct *pdf, struct objs + retval = pdf_findobj_in_objstm(pdf, objstm, &obj); + if (retval != CL_SUCCESS) { + if (retval != CL_BREAK) { +- cli_dbgmsg("pdf_find_and_parse_objs_in_objstm: Fewer objects in stream than expected: %u found, %u expected.\n", ++ cli_dbgmsg("pdf_find_and_parse_objs_in_objstm: Fewer objects in stream than expected: %zu found, %zu expected.\n", + objstm->nobjs_found, objstm->n); + badobjects++; + pdf->stats.ninvalidobjs++; +diff --git a/libclamav/pdf.h b/libclamav/pdf.h +index 3a03f19..b5b69ce 100644 +--- a/libclamav/pdf.h ++++ b/libclamav/pdf.h +@@ -25,14 +25,14 @@ + #define PDF_FILTERLIST_MAX 64 + + struct objstm_struct { +- uint32_t first; // offset of first obj +- uint32_t current; // offset of current obj +- uint32_t current_pair; // offset of current pair describing id, location of object +- uint32_t length; // total length of all objects (starting at first) +- uint32_t n; // number of objects that should be found in the object stream +- uint32_t nobjs_found; // number of objects actually found in the object stream +- char *streambuf; // address of stream buffer, beginning with first obj pair +- size_t streambuf_len; // length of stream buffer, includes pairs followed by actual objects ++ size_t first; // offset of first obj ++ size_t current; // offset of current obj ++ size_t current_pair; // offset of current pair describing id, location of object ++ size_t length; // total length of all objects (starting at first) ++ size_t n; // number of objects that should be found in the object stream ++ size_t nobjs_found; // number of objects actually found in the object stream ++ char *streambuf; // address of stream buffer, beginning with first obj pair ++ size_t streambuf_len; // length of stream buffer, includes pairs followed by actual objects + }; + + struct pdf_obj { +diff --git a/libclamav/pdfdecode.c b/libclamav/pdfdecode.c +index 473cfcd..92ba52d 100644 +--- a/libclamav/pdfdecode.c ++++ b/libclamav/pdfdecode.c +@@ -73,7 +73,7 @@ + struct pdf_token { + uint32_t flags; /* tracking flags */ + uint32_t success; /* successfully decoded filters */ +- uint32_t length; /* length of current content; TODO: transition to size_t */ ++ size_t length; /* length of current content; TODO: transition to size_t */ + uint8_t *content; /* content stream */ + }; + +@@ -461,10 +461,16 @@ static cl_error_t filter_ascii85decode(struct pdf_struct *pdf, struct pdf_obj *o + uint32_t declen = 0; + + const uint8_t *ptr = (uint8_t *)token->content; +- uint32_t remaining = token->length; ++ size_t remaining = token->length; + int quintet = 0, rc = CL_SUCCESS; + uint64_t sum = 0; + ++ /* Check for overflow */ ++ if (remaining > (SIZE_MAX / 4)) { ++ cli_dbgmsg("cli_pdf: ascii85decode: overflow detected\n"); ++ return CL_EFORMAT; ++ } ++ + /* 5:4 decoding ratio, with 1:4 expansion sequences => (4*length)+1 */ + if (!(dptr = decoded = (uint8_t *)cli_malloc((4 * remaining) + 1))) { + cli_errmsg("cli_pdf: cannot allocate memory for decoded output\n"); +@@ -851,8 +857,8 @@ static cl_error_t filter_asciihexdecode(struct pdf_struct *pdf, struct pdf_obj * + uint8_t *decoded; + + const uint8_t *content = (uint8_t *)token->content; +- uint32_t length = token->length; +- uint32_t i, j; ++ size_t length = token->length; ++ size_t i, j; + cl_error_t rc = CL_SUCCESS; + + if (!(decoded = (uint8_t *)cli_calloc(length / 2 + 1, sizeof(uint8_t)))) { +@@ -882,8 +888,8 @@ static cl_error_t filter_asciihexdecode(struct pdf_struct *pdf, struct pdf_obj * + if (rc == CL_SUCCESS) { + free(token->content); + +- cli_dbgmsg("cli_pdf: deflated %lu bytes from %lu total bytes\n", +- (unsigned long)j, (unsigned long)(token->length)); ++ cli_dbgmsg("cli_pdf: deflated %zu bytes from %zu total bytes\n", ++ j, token->length); + + token->content = decoded; + token->length = j; +@@ -891,8 +897,8 @@ static cl_error_t filter_asciihexdecode(struct pdf_struct *pdf, struct pdf_obj * + if (!(obj->flags & ((1 << OBJ_IMAGE) | (1 << OBJ_TRUNCATED)))) + pdfobj_flag(pdf, obj, BAD_ASCIIDECODE); + +- cli_dbgmsg("cli_pdf: error occurred parsing byte %lu of %lu\n", +- (unsigned long)i, (unsigned long)(token->length)); ++ cli_dbgmsg("cli_pdf: error occurred parsing byte %zu of %zu\n", ++ i, token->length); + free(decoded); + } + return rc; +@@ -933,27 +939,29 @@ static cl_error_t filter_decrypt(struct pdf_struct *pdf, struct pdf_obj *obj, st + return CL_EPARSE; /* TODO: what should this value be? CL_SUCCESS would mirror previous behavior */ + } + +- cli_dbgmsg("cli_pdf: decrypted %zu bytes from %u total bytes\n", ++ cli_dbgmsg("cli_pdf: decrypted %zu bytes from %zu total bytes\n", + length, token->length); + + free(token->content); + token->content = (uint8_t *)decrypted; +- token->length = (uint32_t)length; /* this may truncate unfortunately, TODO: use 64-bit values internally? */ ++ token->length = length; + return CL_SUCCESS; + } + + static cl_error_t filter_lzwdecode(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdf_dict *params, struct pdf_token *token) + { + uint8_t *decoded, *temp; +- uint32_t declen = 0, capacity = 0; ++ size_t declen = 0, capacity = 0; + + uint8_t *content = (uint8_t *)token->content; + uint32_t length = token->length; + lzw_stream stream; + int echg = 1, lzwstat, rc = CL_SUCCESS; + +- if (pdf->ctx && !(pdf->ctx->dconf->other & OTHER_CONF_LZW)) +- return CL_BREAK; ++ if (pdf->ctx && !(pdf->ctx->dconf->other & OTHER_CONF_LZW)) { ++ rc = CL_BREAK; ++ goto done; ++ } + + if (params) { + struct pdf_dict_node *node = params->nodes; +@@ -984,15 +992,18 @@ static cl_error_t filter_lzwdecode(struct pdf_struct *pdf, struct pdf_obj *obj, + * Sample 0015315109, it has \r followed by zlib header. + * Flag pdf as suspicious, and attempt to extract by skipping the \r. + */ +- if (!length) +- return CL_SUCCESS; ++ if (!length) { ++ rc = CL_SUCCESS; ++ goto done; ++ } + } + + capacity = INFLATE_CHUNK_SIZE; + + if (!(decoded = (uint8_t *)cli_malloc(capacity))) { + cli_errmsg("cli_pdf: cannot allocate memory for decoded output\n"); +- return CL_EMEM; ++ rc = CL_EMEM; ++ goto done; + } + + memset(&stream, 0, sizeof(stream)); +@@ -1007,7 +1018,8 @@ static cl_error_t filter_lzwdecode(struct pdf_struct *pdf, struct pdf_obj *obj, + if (lzwstat != Z_OK) { + cli_warnmsg("cli_pdf: lzwInit failed\n"); + free(decoded); +- return CL_EMEM; ++ rc = CL_EMEM; ++ goto done; + } + + /* initial inflate */ +@@ -1022,16 +1034,23 @@ static cl_error_t filter_lzwdecode(struct pdf_struct *pdf, struct pdf_obj *obj, + length -= q - content; + content = q; + +- stream.next_in = (Bytef *)content; +- stream.avail_in = length; +- stream.next_out = (Bytef *)decoded; ++ stream.next_in = (Bytef *)content; ++ stream.avail_in = length; ++ stream.next_out = (Bytef *)decoded; ++ /* Make sure we don't overflow during type conversion */ ++ if (capacity > UINT_MAX) { ++ cli_dbgmsg("cli_pdf: lzwdecode: overflow detected\n"); ++ rc = CL_EFORMAT; ++ goto done; ++ } + stream.avail_out = capacity; + + lzwstat = lzwInit(&stream); + if (lzwstat != Z_OK) { + cli_warnmsg("cli_pdf: lzwInit failed\n"); + free(decoded); +- return CL_EMEM; ++ rc = CL_EMEM; ++ goto done; + } + + pdfobj_flag(pdf, obj, BAD_FLATESTART); +@@ -1044,7 +1063,7 @@ static cl_error_t filter_lzwdecode(struct pdf_struct *pdf, struct pdf_obj *obj, + /* extend output capacity if needed,*/ + if (stream.avail_out == 0) { + if ((rc = cli_checklimits("pdf", pdf->ctx, capacity + INFLATE_CHUNK_SIZE, 0, 0)) != CL_SUCCESS) { +- cli_dbgmsg("cli_pdf: required buffer size to inflate compressed filter exceeds maximum: %u\n", capacity + INFLATE_CHUNK_SIZE); ++ cli_dbgmsg("cli_pdf: required buffer size to inflate compressed filter exceeds maximum: %zu\n", capacity + INFLATE_CHUNK_SIZE); + break; + } + +@@ -1056,7 +1075,17 @@ static cl_error_t filter_lzwdecode(struct pdf_struct *pdf, struct pdf_obj *obj, + decoded = temp; + stream.next_out = decoded + capacity; + stream.avail_out = INFLATE_CHUNK_SIZE; ++ if (declen > (SIZE_MAX - INFLATE_CHUNK_SIZE)) { ++ cli_dbgmsg("cli_pdf: lzwdecode: overflow detected\n"); ++ rc = CL_EFORMAT; ++ goto done; ++ } + declen += INFLATE_CHUNK_SIZE; ++ if (capacity > (SIZE_MAX - INFLATE_CHUNK_SIZE)) { ++ cli_dbgmsg("cli_pdf: lzwdecode: overflow detected\n"); ++ rc = CL_EFORMAT; ++ goto done; ++ } + capacity += INFLATE_CHUNK_SIZE; + } + +@@ -1064,6 +1093,12 @@ static cl_error_t filter_lzwdecode(struct pdf_struct *pdf, struct pdf_obj *obj, + lzwstat = lzwInflate(&stream); + } + ++ if (declen > (UINT32_MAX - (INFLATE_CHUNK_SIZE - stream.avail_out))) { ++ cli_dbgmsg("cli_pdf: lzwdecode: overflow detected\n"); ++ rc = CL_EFORMAT; ++ goto done; ++ } ++ + /* add stream end fragment to decoded length */ + declen += (INFLATE_CHUNK_SIZE - stream.avail_out); + +@@ -1104,6 +1139,7 @@ static cl_error_t filter_lzwdecode(struct pdf_struct *pdf, struct pdf_obj *obj, + + (void)lzwInflateEnd(&stream); + ++done: + if (rc == CL_SUCCESS) { + if (declen == 0) { + cli_dbgmsg("cli_pdf: empty stream after inflation completed.\n"); diff --git a/app-antivirus/clamav/files/clamav-0.103.12-fix-lzma-uaf.patch b/app-antivirus/clamav/files/clamav-0.103.12-fix-lzma-uaf.patch new file mode 100644 index 000000000000..4d05e5ad16c2 --- /dev/null +++ b/app-antivirus/clamav/files/clamav-0.103.12-fix-lzma-uaf.patch @@ -0,0 +1,34 @@ +From fd9cf81463725023d25838a08c8de459f619a58c Mon Sep 17 00:00:00 2001 +From: Val Snyder +Date: Wed, 12 Mar 2025 16:08:25 -0400 +Subject: [PATCH] Fix lzma-sdk xz bug + +A use-after-free read is possible in the Xz decoder cleanup. + +The fix is to set a pointer to NULL so it doesn't try to +dereference it and free a second time. + +Fixes https://issues.oss-fuzz.com/issues/384549094 + +This fix is also present in lzma-sdk version 18.01. +Ref: https://github.com/welovegit/LZMA-SDK/blame/main/C/XzDec.c#L508 +--- + libclamav/7z/XzDec.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/libclamav/7z/XzDec.c b/libclamav/7z/XzDec.c +index 00a28702f5..7e40d202fb 100644 +--- a/libclamav/7z/XzDec.c ++++ b/libclamav/7z/XzDec.c +@@ -343,8 +343,10 @@ void MixCoder_Free(CMixCoder *p) + for (i = 0; i < p->numCoders; i++) + { + IStateCoder *sc = &p->coders[i]; +- if (p->alloc && sc->p) ++ if (p->alloc && sc->p) { + sc->Free(sc->p, p->alloc); ++ sc->p = NULL; ++ } + } + p->numCoders = 0; + if (p->buf)