commit:     dab0419a1723cf871b14cd02cbef12c9c05cdbe7
Author:     Michael Orlitzky <mjo <AT> gentoo <DOT> org>
AuthorDate: Thu Jun 19 00:23:12 2025 +0000
Commit:     Michael Orlitzky <mjo <AT> gentoo <DOT> 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 <mjo <AT> 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" 
"[email protected]"
+                       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 <[email protected]>
+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)

Reply via email to