[Pkg-clamav-devel] Bug#965257: buster-pu: package clamav/0.102.4+dfsg-0+deb10u1

Sebastian Andrzej Siewior sebastian at breakpoint.cc
Sat Jul 18 11:12:48 BST 2020


Package: release.debian.org
User: release.debian.org at packages.debian.org
Usertags: pu
Tags: buster
Severity: normal

ClamAV upstream released 0.102.4 fixing three CVEs. From their news:

- [CVE-2020-3350]
  Fix a vulnerability wherein a malicious user could replace a scan target's
  directory with a symlink to another path to trick clamscan, clamdscan, or
  clamonacc into removing or moving a different file (eg. a critical system
  file). The issue would affect users that use the --move or --remove options
  for clamscan, clamdscan, and clamonacc.

  For more information about AV quarantine attacks using links, see the
  [RACK911 Lab's report](https://www.rack911labs.com/research/exploiting-almost-every-antivirus-software).

- [CVE-2020-3327]
  Fix a vulnerability in the ARJ archive parsing module in ClamAV 0.102.3 that
  could cause a Denial-of-Service (DoS) condition. Improper bounds checking
  results in an out-of-bounds read which could cause a crash.
  The previous fix for this CVE in 0.102.3 was incomplete. This fix correctly
  resolves the issue.

- [CVE-2020-3481]
  Fix a vulnerability in the EGG archive module in ClamAV 0.102.0 - 0.102.3
  could cause a Denial-of-Service (DoS) condition. Improper error handling
  may result in a crash due to a NULL pointer dereference.
  This vulnerability is mitigated for those using the official ClamAV
  signature databases because the file type signatures in daily.cvd
  will not enable the EGG archive parser in versions affected by the
  vulnerability.

I prepared the packages and gave it a brief test overnight.

Sebastian
-------------- next part --------------
diff -Nru clamav-0.102.3+dfsg/clamdscan/proto.c clamav-0.102.4+dfsg/clamdscan/proto.c
--- clamav-0.102.3+dfsg/clamdscan/proto.c	2020-05-12 03:54:49.000000000 +0200
+++ clamav-0.102.4+dfsg/clamdscan/proto.c	2020-07-15 23:54:36.000000000 +0200
@@ -262,9 +262,23 @@
     char *bol, *eol;
     struct RCVLN rcv;
     STATBUF sb;
+    cl_error_t ret;
+    char *real_filename = NULL;
+
+    if (filename) {
+        ret = cli_realpath((const char *) filename, &real_filename);
+        if (CL_SUCCESS != ret) {
+            logg("Failed to determine real filename of %s.\n", filename);
+            infected = -1;
+            goto done;
+        }
+        filename = real_filename;
+
+        if (1 == chkpath(filename)) {
+            goto done;
+        }
+    }
 
-    if (filename && chkpath(filename))
-        return 0;
     recvlninit(&rcv, sockd);
 
     switch (scantype) {
@@ -273,17 +287,20 @@
         case ALLMATCH:
             if (!filename) {
                 logg("Filename cannot be NULL for MULTISCAN or CONTSCAN.\n");
-                return -1;
+                infected = -1;
+                goto done;
             }
             len = strlen(filename) + strlen(scancmd[scantype]) + 3;
             if (!(bol = malloc(len))) {
                 logg("!Cannot allocate a command buffer: %s\n", strerror(errno));
-                return -1;
+                infected = -1;
+                goto done;
             }
             sprintf(bol, "z%s %s", scancmd[scantype], filename);
             if (sendln(sockd, bol, len)) {
                 free(bol);
-                return -1;
+                infected = -1;
+                goto done;
             }
             free(bol);
             break;
@@ -304,11 +321,15 @@
         *printok = 0;
         if (errors)
             (*errors)++;
-        return len;
+        infected = len;
+        goto done;
     }
 
     while ((len = recvln(&rcv, &bol, &eol))) {
-        if (len == -1) return -1;
+        if (len == -1) {
+            infected = -1;
+            goto done;
+        }
         beenthere = 1;
         if (!filename) logg("~%s\n", bol);
         if (len > 7) {
@@ -328,7 +349,8 @@
                          (scantype < 0 || scantype > MAX_SCANTYPE) ? "unidentified" : scancmd[scantype]);
                 else
                     logg("Failed to parse reply: \"%s\"\n", bol);
-                return -1;
+                infected = -1;
+                goto done;
             } else if (!memcmp(eol - 7, " FOUND", 6)) {
                 static char last_filename[PATH_MAX + 1] = {'\0'};
                 *(eol - 7)                              = 0;
@@ -369,18 +391,26 @@
     if (!beenthere) {
         if (!filename) {
             logg("STDIN: noreply from clamd\n.");
-            return -1;
+            infected = -1;
+            goto done;
         }
         if (CLAMSTAT(filename, &sb) == -1) {
             logg("~%s: stat() failed with %s, clamd may not be responding\n",
                  filename, strerror(errno));
-            return -1;
+            infected = -1;
+            goto done;
         }
         if (!S_ISDIR(sb.st_mode)) {
             logg("~%s: no reply from clamd\n", filename);
-            return -1;
+            infected = -1;
+            goto done;
         }
     }
+
+done:
+    if (NULL != real_filename) {
+        free(real_filename);
+    }
     return infected;
 }
 
diff -Nru clamav-0.102.3+dfsg/clamonacc/client/protocol.c clamav-0.102.4+dfsg/clamonacc/client/protocol.c
--- clamav-0.102.3+dfsg/clamonacc/client/protocol.c	2020-05-12 03:54:49.000000000 +0200
+++ clamav-0.102.4+dfsg/clamonacc/client/protocol.c	2020-07-15 23:54:36.000000000 +0200
@@ -54,6 +54,7 @@
 #endif
 
 #include "libclamav/clamav.h"
+#include "libclamav/others.h"
 #include "shared/actions.h"
 #include "shared/output.h"
 #include "shared/misc.h"
@@ -191,6 +192,22 @@
 
     onas_recvlninit(&rcv, curl);
 
+    cl_error_t ret;
+    char *real_filename = NULL;
+
+    if (filename) {
+        ret = cli_realpath((const char *) filename, &real_filename);
+        if (CL_SUCCESS != ret) {
+            logg("Failed to determine real filename of %s.\n", filename);
+            if (ret_code) {
+                *ret_code = CL_EACCES;
+            }
+            infected = -1;
+            goto done;
+        }
+        filename = real_filename;
+    }
+
     if (ret_code) {
         *ret_code = CL_SUCCESS;
     }
@@ -204,7 +221,8 @@
                 if (ret_code) {
                     *ret_code = CL_ENULLARG;
                 }
-                return -1;
+                infected = -1;
+                goto done;
             }
             len = strlen(filename) + strlen(scancmd[scantype]) + 3;
             if (!(bol = malloc(len))) {
@@ -212,7 +230,8 @@
                 if (ret_code) {
                     *ret_code = CL_EMEM;
                 }
-                return -1;
+                infected = -1;
+                goto done;
             }
             sprintf(bol, "z%s %s", scancmd[scantype], filename);
             if (onas_sendln(curl, bol, len, timeout)) {
@@ -220,7 +239,8 @@
                     *ret_code = CL_EWRITE;
                 }
                 free(bol);
-                return -1;
+                infected = -1;
+                goto done;
             }
             free(bol);
             break;
@@ -241,7 +261,8 @@
         *printok = 0;
         if (errors)
             (*errors)++;
-        return len;
+        infected = len;
+        goto done;
     }
 
     while ((len = onas_recvln(&rcv, &bol, &eol, timeout))) {
@@ -249,7 +270,8 @@
             if (ret_code) {
                 *ret_code = CL_EREAD;
             }
-            return -1;
+            infected = -1;
+            goto done;
         }
         beenthere = 1;
         if (!filename) {
@@ -281,7 +303,8 @@
                 if (ret_code) {
                     *ret_code = CL_EPARSE;
                 }
-                return -1;
+                infected = -1;
+                goto done;
 
             } else if (!memcmp(eol - 7, " FOUND", 6)) {
                 static char last_filename[PATH_MAX + 1] = {'\0'};
@@ -378,7 +401,8 @@
             if (ret_code) {
                 *ret_code = CL_EACCES;
             }
-            return -1;
+            infected = -1;
+            goto done;
         }
         if (CLAMSTAT(filename, &sb) == -1) {
             logg("~%s: stat() failed with %s, clamd may not be responding\n",
@@ -386,15 +410,22 @@
             if (ret_code) {
                 *ret_code = CL_EACCES;
             }
-            return -1;
+            infected = -1;
+            goto done;
         }
         if (!S_ISDIR(sb.st_mode)) {
             logg("~%s: no reply from clamd\n", filename);
             if (ret_code) {
                 *ret_code = CL_EACCES;
             }
-            return -1;
+            infected = -1;
+            goto done;
         }
     }
+
+done:
+    if (NULL != real_filename) {
+        free(real_filename);
+    }
     return infected;
 }
diff -Nru clamav-0.102.3+dfsg/clamscan/manager.c clamav-0.102.4+dfsg/clamscan/manager.c
--- clamav-0.102.3+dfsg/clamscan/manager.c	2020-05-12 03:54:49.000000000 +0200
+++ clamav-0.102.4+dfsg/clamscan/manager.c	2020-07-15 23:54:36.000000000 +0200
@@ -287,7 +287,8 @@
 
 static void scanfile(const char *filename, struct cl_engine *engine, const struct optstruct *opts, struct cl_scan_options *options)
 {
-    int ret = 0, fd, included;
+    cl_error_t ret = CL_SUCCESS;
+    int fd, included;
     unsigned i;
     const struct optstruct *opt;
     const char *virname = NULL;
@@ -295,13 +296,28 @@
     struct metachain chain;
     struct clamscan_cb_data data;
 
+    char *real_filename = NULL;
+
+    if (NULL == filename || NULL == engine || NULL == opts || NULL == options) {
+        logg("scanfile: Invalid args.\n");
+        ret = CL_EARG;
+        goto done;
+    }
+
+    ret = cli_realpath((const char *) filename, &real_filename);
+    if (CL_SUCCESS != ret) {
+        logg("Failed to determine real filename of %s.\n", filename);
+        goto done;
+    }
+    filename = real_filename;
+
     if ((opt = optget(opts, "exclude"))->enabled) {
         while (opt) {
             if (match_regex(filename, opt->strarg) == 1) {
                 if (!printinfected)
                     logg("~%s: Excluded\n", filename);
 
-                return;
+                goto done;
             }
 
             opt = opt->nextarg;
@@ -324,7 +340,7 @@
             if (!printinfected)
                 logg("~%s: Excluded\n", filename);
 
-            return;
+            goto done;
         }
     }
 
@@ -335,14 +351,14 @@
             if (!printinfected)
                 logg("~%s: Excluded (/proc)\n", filename);
 
-            return;
+            goto done;
         }
 #endif
         if (!sb.st_size) {
             if (!printinfected)
                 logg("~%s: Empty file\n", filename);
 
-            return;
+            goto done;
         }
 
         info.rblocks += sb.st_size / CL_COUNT_PRECISION;
@@ -355,7 +371,7 @@
                 logg("~%s: Access denied\n", filename);
 
             info.errors++;
-            return;
+            goto done;
         }
     }
 #endif
@@ -369,7 +385,7 @@
                 free(chain.chains);
                 logg("Unable to allocate memory in scanfile()\n");
                 info.errors++;
-                return;
+                goto done;
             }
             chain.nchains = 1;
         }
@@ -380,7 +396,7 @@
     if ((fd = safe_open(filename, O_RDONLY | O_BINARY)) == -1) {
         logg("^Can't open file %s: %s\n", filename, strerror(errno));
         info.errors++;
-        return;
+        goto done;
     }
 
     data.chain    = &chain;
@@ -421,6 +437,12 @@
 
     if (ret == CL_VIRUS && action)
         action(filename);
+
+done:
+    if (NULL != real_filename) {
+        free(real_filename);
+    }
+    return;
 }
 
 static void scandirs(const char *dirname, struct cl_engine *engine, const struct optstruct *opts, struct cl_scan_options *options, unsigned int depth, dev_t dev)
diff -Nru clamav-0.102.3+dfsg/configure clamav-0.102.4+dfsg/configure
--- clamav-0.102.3+dfsg/configure	2020-05-12 03:54:49.000000000 +0200
+++ clamav-0.102.4+dfsg/configure	2020-07-15 23:54:36.000000000 +0200
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for ClamAV 0.102.3.
+# Generated by GNU Autoconf 2.69 for ClamAV 0.102.4.
 #
 # Report bugs to <https://bugzilla.clamav.net/>.
 #
@@ -592,8 +592,8 @@
 # Identity of this package.
 PACKAGE_NAME='ClamAV'
 PACKAGE_TARNAME='clamav'
-PACKAGE_VERSION='0.102.3'
-PACKAGE_STRING='ClamAV 0.102.3'
+PACKAGE_VERSION='0.102.4'
+PACKAGE_STRING='ClamAV 0.102.4'
 PACKAGE_BUGREPORT='https://bugzilla.clamav.net/'
 PACKAGE_URL='https://www.clamav.net/'
 
@@ -1601,7 +1601,7 @@
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures ClamAV 0.102.3 to adapt to many kinds of systems.
+\`configure' configures ClamAV 0.102.4 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1682,7 +1682,7 @@
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of ClamAV 0.102.3:";;
+     short | recursive ) echo "Configuration of ClamAV 0.102.4:";;
    esac
   cat <<\_ACEOF
   --enable-dependency-tracking
@@ -1911,7 +1911,7 @@
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-ClamAV configure 0.102.3
+ClamAV configure 0.102.4
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -2539,7 +2539,7 @@
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by ClamAV $as_me 0.102.3, which was
+It was created by ClamAV $as_me 0.102.4, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -4297,7 +4297,7 @@
 
 # Define the identity of the package.
  PACKAGE='clamav'
- VERSION='0.102.3'
+ VERSION='0.102.4'
 
 
 # Some tools Automake needs.
@@ -6025,7 +6025,7 @@
 $as_echo "#define PACKAGE PACKAGE_NAME" >>confdefs.h
 
 
-VERSION="0.102.3"
+VERSION="0.102.4"
 
 major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/^0-9//g"`
 minor=`echo $PACKAGE_VERSION |cut -d. -f2 | sed -e "s/^0-9//g"`
@@ -31630,7 +31630,7 @@
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by ClamAV $as_me 0.102.3, which was
+This file was extended by ClamAV $as_me 0.102.4, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -31697,7 +31697,7 @@
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-ClamAV config.status 0.102.3
+ClamAV config.status 0.102.4
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
@@ -34548,7 +34548,7 @@
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by ClamAV $as_me 0.102.3, which was
+This file was extended by ClamAV $as_me 0.102.4, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -34615,7 +34615,7 @@
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-ClamAV config.status 0.102.3
+ClamAV config.status 0.102.4
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
diff -Nru clamav-0.102.3+dfsg/configure.ac clamav-0.102.4+dfsg/configure.ac
--- clamav-0.102.3+dfsg/configure.ac	2020-05-16 11:23:53.000000000 +0200
+++ clamav-0.102.4+dfsg/configure.ac	2020-07-17 20:19:54.000000000 +0200
@@ -22,7 +22,7 @@
 
 dnl For a release change [devel] to the real version [0.xy]
 dnl also change VERSION below
-AC_INIT([ClamAV], [0.102.3], [https://bugzilla.clamav.net/], [clamav], [https://www.clamav.net/])
+AC_INIT([ClamAV], [0.102.4], [https://bugzilla.clamav.net/], [clamav], [https://www.clamav.net/])
 
 dnl put configure auxiliary into config
 AC_CONFIG_AUX_DIR([config])
diff -Nru clamav-0.102.3+dfsg/debian/changelog clamav-0.102.4+dfsg/debian/changelog
--- clamav-0.102.3+dfsg/debian/changelog	2020-05-30 00:07:05.000000000 +0200
+++ clamav-0.102.4+dfsg/debian/changelog	2020-07-18 00:22:32.000000000 +0200
@@ -1,3 +1,13 @@
+clamav (0.102.4+dfsg-0+deb10u1) buster; urgency=medium
+
+  * Import 0.102.4
+    - CVE-2020-3350 (A malicious user trick clamav into moving a different file).
+    - CVE-2020-3327 (A vulnerability in the ARJ archive parsing module).
+    - CVE-2020-3481 (A vulnerability in the EGG archive module).
+  * Update symbol file.
+
+ -- Sebastian Andrzej Siewior <sebastian at breakpoint.cc>  Sat, 18 Jul 2020 00:22:32 +0200
+
 clamav (0.102.3+dfsg-0+deb10u1) buster; urgency=medium
 
   [ Sebastian Andrzej Siewior ]
diff -Nru clamav-0.102.3+dfsg/debian/.git-dpm clamav-0.102.4+dfsg/debian/.git-dpm
--- clamav-0.102.3+dfsg/debian/.git-dpm	2020-05-30 00:03:59.000000000 +0200
+++ clamav-0.102.4+dfsg/debian/.git-dpm	2020-07-18 00:19:32.000000000 +0200
@@ -1,8 +1,8 @@
 # see git-dpm(1) from git-dpm package
-04fd79ea5eace5273a13bd66b095e2fef0ea3bff
-04fd79ea5eace5273a13bd66b095e2fef0ea3bff
-07c9b9ef63bc584a39143a6cd002d199d1d46397
-07c9b9ef63bc584a39143a6cd002d199d1d46397
-clamav_0.102.3+dfsg.orig.tar.xz
-694c77d0aed527d3d135a3ccd7e30729fff55404
-5018320
+c07899f43b92f63e9ad0ccefa5379ca649603d4a
+c07899f43b92f63e9ad0ccefa5379ca649603d4a
+2e5f12d74d7065a47a1cf072e703445b81878e07
+2e5f12d74d7065a47a1cf072e703445b81878e07
+clamav_0.102.4+dfsg.orig.tar.xz
+a139e4b00726fbd97ad88c7b65e88000ebee38ab
+5023528
diff -Nru clamav-0.102.3+dfsg/debian/libclamav9.symbols clamav-0.102.4+dfsg/debian/libclamav9.symbols
--- clamav-0.102.3+dfsg/debian/libclamav9.symbols	2020-05-24 13:13:40.000000000 +0200
+++ clamav-0.102.4+dfsg/debian/libclamav9.symbols	2020-07-18 00:22:08.000000000 +0200
@@ -1,20 +1,20 @@
 libclamav.so.9 libclamav9 #MINVER#
 * Build-Depends-Package: libclamav-dev
- CLAMAV_PRIVATE at CLAMAV_PRIVATE 0.102.3
+ CLAMAV_PRIVATE at CLAMAV_PRIVATE 0.102.4
  CLAMAV_PUBLIC at CLAMAV_PUBLIC 0.101.0
- __cli_strcasestr at CLAMAV_PRIVATE 0.102.3
- __cli_strndup at CLAMAV_PRIVATE 0.102.3
- __cli_strnlen at CLAMAV_PRIVATE 0.102.3
- __cli_strnstr at CLAMAV_PRIVATE 0.102.3
- base64Flush at CLAMAV_PRIVATE 0.102.3
- blobAddData at CLAMAV_PRIVATE 0.102.3
- blobCreate at CLAMAV_PRIVATE 0.102.3
- blobDestroy at CLAMAV_PRIVATE 0.102.3
- cl_ASN1_GetTimeT at CLAMAV_PRIVATE 0.102.3
+ __cli_strcasestr at CLAMAV_PRIVATE 0.102.4
+ __cli_strndup at CLAMAV_PRIVATE 0.102.4
+ __cli_strnlen at CLAMAV_PRIVATE 0.102.4
+ __cli_strnstr at CLAMAV_PRIVATE 0.102.4
+ base64Flush at CLAMAV_PRIVATE 0.102.4
+ blobAddData at CLAMAV_PRIVATE 0.102.4
+ blobCreate at CLAMAV_PRIVATE 0.102.4
+ blobDestroy at CLAMAV_PRIVATE 0.102.4
+ cl_ASN1_GetTimeT at CLAMAV_PRIVATE 0.102.4
  cl_always_gen_section_hash at CLAMAV_PUBLIC 0.101.0
- cl_base64_decode at CLAMAV_PRIVATE 0.102.3
- cl_base64_encode at CLAMAV_PRIVATE 0.102.3
- cl_cleanup_crypto at CLAMAV_PRIVATE 0.102.3
+ cl_base64_decode at CLAMAV_PRIVATE 0.102.4
+ cl_base64_encode at CLAMAV_PRIVATE 0.102.4
+ cl_cleanup_crypto at CLAMAV_PRIVATE 0.102.4
  cl_countsigs at CLAMAV_PUBLIC 0.101.0
  cl_cvdfree at CLAMAV_PUBLIC 0.101.0
  cl_cvdhead at CLAMAV_PUBLIC 0.101.0
@@ -54,19 +54,19 @@
  cl_fmap_close at CLAMAV_PUBLIC 0.101.0
  cl_fmap_open_handle at CLAMAV_PUBLIC 0.101.0
  cl_fmap_open_memory at CLAMAV_PUBLIC 0.101.0
- cl_get_pkey_file at CLAMAV_PRIVATE 0.102.3
- cl_get_x509_from_mem at CLAMAV_PRIVATE 0.102.3
- cl_hash_data at CLAMAV_PRIVATE 0.102.3
+ cl_get_pkey_file at CLAMAV_PRIVATE 0.102.4
+ cl_get_x509_from_mem at CLAMAV_PRIVATE 0.102.4
+ cl_hash_data at CLAMAV_PRIVATE 0.102.4
  cl_hash_destroy at CLAMAV_PUBLIC 0.101.0
- cl_hash_file_fd at CLAMAV_PRIVATE 0.102.3
- cl_hash_file_fd_ctx at CLAMAV_PRIVATE 0.102.3
- cl_hash_file_fp at CLAMAV_PRIVATE 0.102.3
+ cl_hash_file_fd at CLAMAV_PRIVATE 0.102.4
+ cl_hash_file_fd_ctx at CLAMAV_PRIVATE 0.102.4
+ cl_hash_file_fp at CLAMAV_PRIVATE 0.102.4
  cl_hash_init at CLAMAV_PUBLIC 0.101.0
  cl_init at CLAMAV_PUBLIC 0.101.0
- cl_initialize_crypto at CLAMAV_PRIVATE 0.102.3
+ cl_initialize_crypto at CLAMAV_PRIVATE 0.102.4
  cl_load at CLAMAV_PUBLIC 0.101.0
- cl_load_cert at CLAMAV_PRIVATE 0.102.3
- cl_load_crl at CLAMAV_PRIVATE 0.102.3
+ cl_load_cert at CLAMAV_PRIVATE 0.102.4
+ cl_load_crl at CLAMAV_PRIVATE 0.102.4
  cl_retdbdir at CLAMAV_PUBLIC 0.101.0
  cl_retflevel at CLAMAV_PUBLIC 0.102.3
  cl_retver at CLAMAV_PUBLIC 0.101.0
@@ -76,188 +76,191 @@
  cl_scanfile_callback at CLAMAV_PUBLIC 0.101.0
  cl_scanmap_callback at CLAMAV_PUBLIC 0.101.0
  cl_set_clcb_msg at CLAMAV_PUBLIC 0.101.0
- cl_sha1 at CLAMAV_PRIVATE 0.102.3
- cl_sha256 at CLAMAV_PRIVATE 0.102.3
- cl_sign_data at CLAMAV_PRIVATE 0.102.3
- cl_sign_data_keyfile at CLAMAV_PRIVATE 0.102.3
- cl_sign_file_fd at CLAMAV_PRIVATE 0.102.3
- cl_sign_file_fp at CLAMAV_PRIVATE 0.102.3
+ cl_sha1 at CLAMAV_PRIVATE 0.102.4
+ cl_sha256 at CLAMAV_PRIVATE 0.102.4
+ cl_sign_data at CLAMAV_PRIVATE 0.102.4
+ cl_sign_data_keyfile at CLAMAV_PRIVATE 0.102.4
+ cl_sign_file_fd at CLAMAV_PRIVATE 0.102.4
+ cl_sign_file_fp at CLAMAV_PRIVATE 0.102.4
  cl_statchkdir at CLAMAV_PUBLIC 0.101.0
  cl_statfree at CLAMAV_PUBLIC 0.101.0
  cl_statinidir at CLAMAV_PUBLIC 0.101.0
  cl_strerror at CLAMAV_PUBLIC 0.101.0
  cl_update_hash at CLAMAV_PUBLIC 0.101.0
- cl_validate_certificate_chain at CLAMAV_PRIVATE 0.102.3
- cl_validate_certificate_chain_ts_dir at CLAMAV_PRIVATE 0.102.3
- cl_verify_signature at CLAMAV_PRIVATE 0.102.3
- cl_verify_signature_fd at CLAMAV_PRIVATE 0.102.3
- cl_verify_signature_fd_x509 at CLAMAV_PRIVATE 0.102.3
- cl_verify_signature_fd_x509_keyfile at CLAMAV_PRIVATE 0.102.3
- cl_verify_signature_hash at CLAMAV_PRIVATE 0.102.3
- cl_verify_signature_hash_x509 at CLAMAV_PRIVATE 0.102.3
- cl_verify_signature_hash_x509_keyfile at CLAMAV_PRIVATE 0.102.3
- cl_verify_signature_x509 at CLAMAV_PRIVATE 0.102.3
- cl_verify_signature_x509_keyfile at CLAMAV_PRIVATE 0.102.3
- cli_ac_buildtrie at CLAMAV_PRIVATE 0.102.3
- cli_ac_chklsig at CLAMAV_PRIVATE 0.102.3
- cli_ac_free at CLAMAV_PRIVATE 0.102.3
- cli_ac_freedata at CLAMAV_PRIVATE 0.102.3
- cli_ac_init at CLAMAV_PRIVATE 0.102.3
- cli_ac_initdata at CLAMAV_PRIVATE 0.102.3
- cli_ac_scanbuff at CLAMAV_PRIVATE 0.102.3
- cli_bm_free at CLAMAV_PRIVATE 0.102.3
- cli_bm_init at CLAMAV_PRIVATE 0.102.3
- cli_bm_scanbuff at CLAMAV_PRIVATE 0.102.3
- cli_build_regex_list at CLAMAV_PRIVATE 0.102.3
- cli_bytecode_context_alloc at CLAMAV_PRIVATE 0.102.3
- cli_bytecode_context_clear at CLAMAV_PRIVATE 0.102.3
- cli_bytecode_context_destroy at CLAMAV_PRIVATE 0.102.3
- cli_bytecode_context_getresult_int at CLAMAV_PRIVATE 0.102.3
- cli_bytecode_context_set_trace at CLAMAV_PRIVATE 0.102.3
- cli_bytecode_context_setfile at CLAMAV_PRIVATE 0.102.3
- cli_bytecode_context_setfuncid at CLAMAV_PRIVATE 0.102.3
- cli_bytecode_context_setparam_int at CLAMAV_PRIVATE 0.102.3
- cli_bytecode_context_setparam_ptr at CLAMAV_PRIVATE 0.102.3
- cli_bytecode_debug at CLAMAV_PRIVATE 0.102.3
- cli_bytecode_debug_printsrc at CLAMAV_PRIVATE 0.102.3
- cli_bytecode_describe at CLAMAV_PRIVATE 0.102.3
- cli_bytecode_destroy at CLAMAV_PRIVATE 0.102.3
- cli_bytecode_done at CLAMAV_PRIVATE 0.102.3
- cli_bytecode_init at CLAMAV_PRIVATE 0.102.3
- cli_bytecode_load at CLAMAV_PRIVATE 0.102.3
- cli_bytecode_prepare2 at CLAMAV_PRIVATE 0.102.3
- cli_bytecode_printversion at CLAMAV_PRIVATE 0.102.3
- cli_bytecode_run at CLAMAV_PRIVATE 0.102.3
- cli_bytefunc_describe at CLAMAV_PRIVATE 0.102.3
- cli_byteinst_describe at CLAMAV_PRIVATE 0.102.3
- cli_bytetype_describe at CLAMAV_PRIVATE 0.102.3
- cli_bytevalue_describe at CLAMAV_PRIVATE 0.102.3
- cli_calloc at CLAMAV_PRIVATE 0.102.3
- cli_check_auth_header at CLAMAV_PRIVATE 0.102.3
- cli_chomp at CLAMAV_PRIVATE 0.102.3
- cli_ctime at CLAMAV_PRIVATE 0.102.3
- cli_cvdunpack at CLAMAV_PRIVATE 0.102.3
- cli_dbgmsg_internal at CLAMAV_PRIVATE 0.102.3
- cli_dconf_init at CLAMAV_PRIVATE 0.102.3
- cli_debug_flag at CLAMAV_PRIVATE 0.102.3
- cli_detect_environment at CLAMAV_PRIVATE 0.102.3
- cli_disasm_one at CLAMAV_PRIVATE 0.102.3
- cli_errmsg at CLAMAV_PRIVATE 0.102.3
- cli_filecopy at CLAMAV_PRIVATE 0.102.3
- cli_fmap_scandesc at CLAMAV_PRIVATE 0.102.3
- cli_free_vba_project at CLAMAV_PRIVATE 0.102.3
- cli_ftw at CLAMAV_PRIVATE 0.102.3
- cli_genhash_pe at CLAMAV_PRIVATE 0.102.3
- cli_gentemp at CLAMAV_PRIVATE 0.102.3
- cli_gentemp_with_prefix at CLAMAV_PRIVATE 0.102.3
- cli_gentempfd at CLAMAV_PRIVATE 0.102.3
- cli_gettmpdir at CLAMAV_PRIVATE 0.102.3
- cli_hashfile at CLAMAV_PRIVATE 0.102.3
- cli_hashset_destroy at CLAMAV_PRIVATE 0.102.3
- cli_hashstream at CLAMAV_PRIVATE 0.102.3
- cli_hex2str at CLAMAV_PRIVATE 0.102.3
- cli_hex2ui at CLAMAV_PRIVATE 0.102.3
- cli_initroots at CLAMAV_PRIVATE 0.102.3
- cli_isnumber at CLAMAV_PRIVATE 0.102.3
- cli_js_destroy at CLAMAV_PRIVATE 0.102.3
- cli_js_init at CLAMAV_PRIVATE 0.102.3
- cli_js_output at CLAMAV_PRIVATE 0.102.3
- cli_js_parse_done at CLAMAV_PRIVATE 0.102.3
- cli_js_process_buffer at CLAMAV_PRIVATE 0.102.3
- cli_ldbtokenize at CLAMAV_PRIVATE 0.102.3
- cli_malloc at CLAMAV_PRIVATE 0.102.3
- cli_memstr at CLAMAV_PRIVATE 0.102.3
- cli_ole2_extract at CLAMAV_PRIVATE 0.102.3
- cli_parse_add at CLAMAV_PRIVATE 0.102.3
- cli_pcre_build at CLAMAV_PRIVATE 0.102.3
- cli_pcre_freeoff at CLAMAV_PRIVATE 0.102.3
- cli_pcre_init at CLAMAV_PRIVATE 0.102.3
- cli_pcre_perf_events_destroy at CLAMAV_PRIVATE 0.102.3
- cli_pcre_perf_print at CLAMAV_PRIVATE 0.102.3
- cli_pcre_recaloff at CLAMAV_PRIVATE 0.102.3
- cli_pcre_scanbuf at CLAMAV_PRIVATE 0.102.3
- cli_ppt_vba_read at CLAMAV_PRIVATE 0.102.3
- cli_printcxxver at CLAMAV_PRIVATE 0.102.3
- cli_readn at CLAMAV_PRIVATE 0.102.3
- cli_realloc at CLAMAV_PRIVATE 0.102.3
- cli_regcomp at CLAMAV_PRIVATE 0.102.3
- cli_regex2suffix at CLAMAV_PRIVATE 0.102.3
- cli_regexec at CLAMAV_PRIVATE 0.102.3
- cli_regfree at CLAMAV_PRIVATE 0.102.3
- cli_rmdirs at CLAMAV_PRIVATE 0.102.3
- cli_rndnum at CLAMAV_PRIVATE 0.102.3
- cli_sanitize_filepath at CLAMAV_PRIVATE 0.102.3
- cli_scanbuff at CLAMAV_PRIVATE 0.102.3
- cli_sigopts_handler at CLAMAV_PRIVATE 0.102.3
- cli_sigperf_events_destroy at CLAMAV_PRIVATE 0.102.3
- cli_sigperf_print at CLAMAV_PRIVATE 0.102.3
- cli_str2hex at CLAMAV_PRIVATE 0.102.3
- cli_strbcasestr at CLAMAV_PRIVATE 0.102.3
- cli_strdup at CLAMAV_PRIVATE 0.102.3
- cli_strerror at CLAMAV_PRIVATE 0.102.3
- cli_strlcat at CLAMAV_PRIVATE 0.102.3
- cli_strlcpy at CLAMAV_PRIVATE 0.102.3
- cli_strrcpy at CLAMAV_PRIVATE 0.102.3
- cli_strtok at CLAMAV_PRIVATE 0.102.3
- cli_strtokbuf at CLAMAV_PRIVATE 0.102.3
- cli_strtokenize at CLAMAV_PRIVATE 0.102.3
- cli_textbuffer_append_normalize at CLAMAV_PRIVATE 0.102.3
- cli_unescape at CLAMAV_PRIVATE 0.102.3
- cli_unlink at CLAMAV_PRIVATE 0.102.3
- cli_url_canon at CLAMAV_PRIVATE 0.102.3
- cli_utf16_to_utf8 at CLAMAV_PRIVATE 0.102.3
- cli_utf16toascii at CLAMAV_PRIVATE 0.102.3
- cli_vba_inflate at CLAMAV_PRIVATE 0.102.3
- cli_vba_readdir at CLAMAV_PRIVATE 0.102.3
- cli_versig2 at CLAMAV_PRIVATE 0.102.3
- cli_versig at CLAMAV_PRIVATE 0.102.3
- cli_warnmsg at CLAMAV_PRIVATE 0.102.3
- cli_wm_decrypt_macro at CLAMAV_PRIVATE 0.102.3
- cli_wm_readdir at CLAMAV_PRIVATE 0.102.3
- cli_writen at CLAMAV_PRIVATE 0.102.3
- decodeLine at CLAMAV_PRIVATE 0.102.3
- disasmbuf at CLAMAV_PRIVATE 0.102.3
- fmap at CLAMAV_PRIVATE 0.102.3
- get_fpu_endian at CLAMAV_PRIVATE 0.102.3
- have_clamjit at CLAMAV_PRIVATE 0.102.3
- have_rar at CLAMAV_PRIVATE 0.102.3
- html_normalise_map at CLAMAV_PRIVATE 0.102.3
- html_normalise_mem at CLAMAV_PRIVATE 0.102.3
- html_screnc_decode at CLAMAV_PRIVATE 0.102.3
- html_tag_arg_free at CLAMAV_PRIVATE 0.102.3
- init_domainlist at CLAMAV_PRIVATE 0.102.3
- init_regex_list at CLAMAV_PRIVATE 0.102.3
- init_whitelist at CLAMAV_PRIVATE 0.102.3
- is_regex_ok at CLAMAV_PRIVATE 0.102.3
- load_regex_matcher at CLAMAV_PRIVATE 0.102.3
+ cl_validate_certificate_chain at CLAMAV_PRIVATE 0.102.4
+ cl_validate_certificate_chain_ts_dir at CLAMAV_PRIVATE 0.102.4
+ cl_verify_signature at CLAMAV_PRIVATE 0.102.4
+ cl_verify_signature_fd at CLAMAV_PRIVATE 0.102.4
+ cl_verify_signature_fd_x509 at CLAMAV_PRIVATE 0.102.4
+ cl_verify_signature_fd_x509_keyfile at CLAMAV_PRIVATE 0.102.4
+ cl_verify_signature_hash at CLAMAV_PRIVATE 0.102.4
+ cl_verify_signature_hash_x509 at CLAMAV_PRIVATE 0.102.4
+ cl_verify_signature_hash_x509_keyfile at CLAMAV_PRIVATE 0.102.4
+ cl_verify_signature_x509 at CLAMAV_PRIVATE 0.102.4
+ cl_verify_signature_x509_keyfile at CLAMAV_PRIVATE 0.102.4
+ cli_ac_buildtrie at CLAMAV_PRIVATE 0.102.4
+ cli_ac_chklsig at CLAMAV_PRIVATE 0.102.4
+ cli_ac_free at CLAMAV_PRIVATE 0.102.4
+ cli_ac_freedata at CLAMAV_PRIVATE 0.102.4
+ cli_ac_init at CLAMAV_PRIVATE 0.102.4
+ cli_ac_initdata at CLAMAV_PRIVATE 0.102.4
+ cli_ac_scanbuff at CLAMAV_PRIVATE 0.102.4
+ cli_basename at CLAMAV_PRIVATE 0.102.4
+ cli_bm_free at CLAMAV_PRIVATE 0.102.4
+ cli_bm_init at CLAMAV_PRIVATE 0.102.4
+ cli_bm_scanbuff at CLAMAV_PRIVATE 0.102.4
+ cli_build_regex_list at CLAMAV_PRIVATE 0.102.4
+ cli_bytecode_context_alloc at CLAMAV_PRIVATE 0.102.4
+ cli_bytecode_context_clear at CLAMAV_PRIVATE 0.102.4
+ cli_bytecode_context_destroy at CLAMAV_PRIVATE 0.102.4
+ cli_bytecode_context_getresult_int at CLAMAV_PRIVATE 0.102.4
+ cli_bytecode_context_set_trace at CLAMAV_PRIVATE 0.102.4
+ cli_bytecode_context_setfile at CLAMAV_PRIVATE 0.102.4
+ cli_bytecode_context_setfuncid at CLAMAV_PRIVATE 0.102.4
+ cli_bytecode_context_setparam_int at CLAMAV_PRIVATE 0.102.4
+ cli_bytecode_context_setparam_ptr at CLAMAV_PRIVATE 0.102.4
+ cli_bytecode_debug at CLAMAV_PRIVATE 0.102.4
+ cli_bytecode_debug_printsrc at CLAMAV_PRIVATE 0.102.4
+ cli_bytecode_describe at CLAMAV_PRIVATE 0.102.4
+ cli_bytecode_destroy at CLAMAV_PRIVATE 0.102.4
+ cli_bytecode_done at CLAMAV_PRIVATE 0.102.4
+ cli_bytecode_init at CLAMAV_PRIVATE 0.102.4
+ cli_bytecode_load at CLAMAV_PRIVATE 0.102.4
+ cli_bytecode_prepare2 at CLAMAV_PRIVATE 0.102.4
+ cli_bytecode_printversion at CLAMAV_PRIVATE 0.102.4
+ cli_bytecode_run at CLAMAV_PRIVATE 0.102.4
+ cli_bytefunc_describe at CLAMAV_PRIVATE 0.102.4
+ cli_byteinst_describe at CLAMAV_PRIVATE 0.102.4
+ cli_bytetype_describe at CLAMAV_PRIVATE 0.102.4
+ cli_bytevalue_describe at CLAMAV_PRIVATE 0.102.4
+ cli_calloc at CLAMAV_PRIVATE 0.102.4
+ cli_check_auth_header at CLAMAV_PRIVATE 0.102.4
+ cli_chomp at CLAMAV_PRIVATE 0.102.4
+ cli_codepage_to_utf8 at CLAMAV_PRIVATE 0.102.4
+ cli_ctime at CLAMAV_PRIVATE 0.102.4
+ cli_cvdunpack at CLAMAV_PRIVATE 0.102.4
+ cli_dbgmsg_internal at CLAMAV_PRIVATE 0.102.4
+ cli_dconf_init at CLAMAV_PRIVATE 0.102.4
+ cli_debug_flag at CLAMAV_PRIVATE 0.102.4
+ cli_detect_environment at CLAMAV_PRIVATE 0.102.4
+ cli_disasm_one at CLAMAV_PRIVATE 0.102.4
+ cli_errmsg at CLAMAV_PRIVATE 0.102.4
+ cli_filecopy at CLAMAV_PRIVATE 0.102.4
+ cli_fmap_scandesc at CLAMAV_PRIVATE 0.102.4
+ cli_free_vba_project at CLAMAV_PRIVATE 0.102.4
+ cli_ftw at CLAMAV_PRIVATE 0.102.4
+ cli_genhash_pe at CLAMAV_PRIVATE 0.102.4
+ cli_gentemp at CLAMAV_PRIVATE 0.102.4
+ cli_gentemp_with_prefix at CLAMAV_PRIVATE 0.102.4
+ cli_gentempfd at CLAMAV_PRIVATE 0.102.4
+ cli_gettmpdir at CLAMAV_PRIVATE 0.102.4
+ cli_hashfile at CLAMAV_PRIVATE 0.102.4
+ cli_hashset_destroy at CLAMAV_PRIVATE 0.102.4
+ cli_hashstream at CLAMAV_PRIVATE 0.102.4
+ cli_hex2str at CLAMAV_PRIVATE 0.102.4
+ cli_hex2ui at CLAMAV_PRIVATE 0.102.4
+ cli_initroots at CLAMAV_PRIVATE 0.102.4
+ cli_isnumber at CLAMAV_PRIVATE 0.102.4
+ cli_js_destroy at CLAMAV_PRIVATE 0.102.4
+ cli_js_init at CLAMAV_PRIVATE 0.102.4
+ cli_js_output at CLAMAV_PRIVATE 0.102.4
+ cli_js_parse_done at CLAMAV_PRIVATE 0.102.4
+ cli_js_process_buffer at CLAMAV_PRIVATE 0.102.4
+ cli_ldbtokenize at CLAMAV_PRIVATE 0.102.4
+ cli_malloc at CLAMAV_PRIVATE 0.102.4
+ cli_memstr at CLAMAV_PRIVATE 0.102.4
+ cli_ole2_extract at CLAMAV_PRIVATE 0.102.4
+ cli_parse_add at CLAMAV_PRIVATE 0.102.4
+ cli_pcre_build at CLAMAV_PRIVATE 0.102.4
+ cli_pcre_freeoff at CLAMAV_PRIVATE 0.102.4
+ cli_pcre_init at CLAMAV_PRIVATE 0.102.4
+ cli_pcre_perf_events_destroy at CLAMAV_PRIVATE 0.102.4
+ cli_pcre_perf_print at CLAMAV_PRIVATE 0.102.4
+ cli_pcre_recaloff at CLAMAV_PRIVATE 0.102.4
+ cli_pcre_scanbuf at CLAMAV_PRIVATE 0.102.4
+ cli_ppt_vba_read at CLAMAV_PRIVATE 0.102.4
+ cli_printcxxver at CLAMAV_PRIVATE 0.102.4
+ cli_readn at CLAMAV_PRIVATE 0.102.4
+ cli_realloc at CLAMAV_PRIVATE 0.102.4
+ cli_realpath at CLAMAV_PRIVATE 0.102.4
+ cli_regcomp at CLAMAV_PRIVATE 0.102.4
+ cli_regex2suffix at CLAMAV_PRIVATE 0.102.4
+ cli_regexec at CLAMAV_PRIVATE 0.102.4
+ cli_regfree at CLAMAV_PRIVATE 0.102.4
+ cli_rmdirs at CLAMAV_PRIVATE 0.102.4
+ cli_rndnum at CLAMAV_PRIVATE 0.102.4
+ cli_sanitize_filepath at CLAMAV_PRIVATE 0.102.4
+ cli_scanbuff at CLAMAV_PRIVATE 0.102.4
+ cli_sigopts_handler at CLAMAV_PRIVATE 0.102.4
+ cli_sigperf_events_destroy at CLAMAV_PRIVATE 0.102.4
+ cli_sigperf_print at CLAMAV_PRIVATE 0.102.4
+ cli_str2hex at CLAMAV_PRIVATE 0.102.4
+ cli_strbcasestr at CLAMAV_PRIVATE 0.102.4
+ cli_strdup at CLAMAV_PRIVATE 0.102.4
+ cli_strerror at CLAMAV_PRIVATE 0.102.4
+ cli_strlcat at CLAMAV_PRIVATE 0.102.4
+ cli_strlcpy at CLAMAV_PRIVATE 0.102.4
+ cli_strrcpy at CLAMAV_PRIVATE 0.102.4
+ cli_strtok at CLAMAV_PRIVATE 0.102.4
+ cli_strtokbuf at CLAMAV_PRIVATE 0.102.4
+ cli_strtokenize at CLAMAV_PRIVATE 0.102.4
+ cli_textbuffer_append_normalize at CLAMAV_PRIVATE 0.102.4
+ cli_unescape at CLAMAV_PRIVATE 0.102.4
+ cli_unlink at CLAMAV_PRIVATE 0.102.4
+ cli_url_canon at CLAMAV_PRIVATE 0.102.4
+ cli_utf16_to_utf8 at CLAMAV_PRIVATE 0.102.4
+ cli_utf16toascii at CLAMAV_PRIVATE 0.102.4
+ cli_vba_inflate at CLAMAV_PRIVATE 0.102.4
+ cli_vba_readdir at CLAMAV_PRIVATE 0.102.4
+ cli_versig2 at CLAMAV_PRIVATE 0.102.4
+ cli_versig at CLAMAV_PRIVATE 0.102.4
+ cli_warnmsg at CLAMAV_PRIVATE 0.102.4
+ cli_wm_decrypt_macro at CLAMAV_PRIVATE 0.102.4
+ cli_wm_readdir at CLAMAV_PRIVATE 0.102.4
+ cli_writen at CLAMAV_PRIVATE 0.102.4
+ decodeLine at CLAMAV_PRIVATE 0.102.4
+ disasmbuf at CLAMAV_PRIVATE 0.102.4
+ fmap at CLAMAV_PRIVATE 0.102.4
+ get_fpu_endian at CLAMAV_PRIVATE 0.102.4
+ have_clamjit at CLAMAV_PRIVATE 0.102.4
+ have_rar at CLAMAV_PRIVATE 0.102.4
+ html_normalise_map at CLAMAV_PRIVATE 0.102.4
+ html_normalise_mem at CLAMAV_PRIVATE 0.102.4
+ html_screnc_decode at CLAMAV_PRIVATE 0.102.4
+ html_tag_arg_free at CLAMAV_PRIVATE 0.102.4
+ init_domainlist at CLAMAV_PRIVATE 0.102.4
+ init_regex_list at CLAMAV_PRIVATE 0.102.4
+ init_whitelist at CLAMAV_PRIVATE 0.102.4
+ is_regex_ok at CLAMAV_PRIVATE 0.102.4
+ load_regex_matcher at CLAMAV_PRIVATE 0.102.4
  lsig_sub_matched at CLAMAV_PUBLIC 0.101.0
- messageCreate at CLAMAV_PRIVATE 0.102.3
- messageDestroy at CLAMAV_PRIVATE 0.102.3
- mpool_calloc at CLAMAV_PRIVATE 0.102.3
- mpool_create at CLAMAV_PRIVATE 0.102.3
- mpool_destroy at CLAMAV_PRIVATE 0.102.3
- mpool_free at CLAMAV_PRIVATE 0.102.3
- mpool_getstats at CLAMAV_PRIVATE 0.102.3
- phishingScan at CLAMAV_PRIVATE 0.102.3
- phishing_done at CLAMAV_PRIVATE 0.102.3
- phishing_init at CLAMAV_PRIVATE 0.102.3
- regex_list_add_pattern at CLAMAV_PRIVATE 0.102.3
- regex_list_done at CLAMAV_PRIVATE 0.102.3
- regex_list_match at CLAMAV_PRIVATE 0.102.3
- tableCreate at CLAMAV_PRIVATE 0.102.3
- tableDestroy at CLAMAV_PRIVATE 0.102.3
- tableFind at CLAMAV_PRIVATE 0.102.3
- tableInsert at CLAMAV_PRIVATE 0.102.3
- tableIterate at CLAMAV_PRIVATE 0.102.3
- tableRemove at CLAMAV_PRIVATE 0.102.3
- tableUpdate at CLAMAV_PRIVATE 0.102.3
- text_normalize_init at CLAMAV_PRIVATE 0.102.3
- text_normalize_map at CLAMAV_PRIVATE 0.102.3
- text_normalize_reset at CLAMAV_PRIVATE 0.102.3
- uniq_add at CLAMAV_PRIVATE 0.102.3
- uniq_free at CLAMAV_PRIVATE 0.102.3
- uniq_get at CLAMAV_PRIVATE 0.102.3
- uniq_init at CLAMAV_PRIVATE 0.102.3
+ messageCreate at CLAMAV_PRIVATE 0.102.4
+ messageDestroy at CLAMAV_PRIVATE 0.102.4
+ mpool_calloc at CLAMAV_PRIVATE 0.102.4
+ mpool_create at CLAMAV_PRIVATE 0.102.4
+ mpool_destroy at CLAMAV_PRIVATE 0.102.4
+ mpool_free at CLAMAV_PRIVATE 0.102.4
+ mpool_getstats at CLAMAV_PRIVATE 0.102.4
+ phishingScan at CLAMAV_PRIVATE 0.102.4
+ phishing_done at CLAMAV_PRIVATE 0.102.4
+ phishing_init at CLAMAV_PRIVATE 0.102.4
+ regex_list_add_pattern at CLAMAV_PRIVATE 0.102.4
+ regex_list_done at CLAMAV_PRIVATE 0.102.4
+ regex_list_match at CLAMAV_PRIVATE 0.102.4
+ tableCreate at CLAMAV_PRIVATE 0.102.4
+ tableDestroy at CLAMAV_PRIVATE 0.102.4
+ tableFind at CLAMAV_PRIVATE 0.102.4
+ tableInsert at CLAMAV_PRIVATE 0.102.4
+ tableIterate at CLAMAV_PRIVATE 0.102.4
+ tableRemove at CLAMAV_PRIVATE 0.102.4
+ tableUpdate at CLAMAV_PRIVATE 0.102.4
+ text_normalize_init at CLAMAV_PRIVATE 0.102.4
+ text_normalize_map at CLAMAV_PRIVATE 0.102.4
+ text_normalize_reset at CLAMAV_PRIVATE 0.102.4
+ uniq_add at CLAMAV_PRIVATE 0.102.4
+ uniq_free at CLAMAV_PRIVATE 0.102.4
+ uniq_get at CLAMAV_PRIVATE 0.102.4
+ uniq_init at CLAMAV_PRIVATE 0.102.4
 libfreshclam.so.2 libclamav9 #MINVER#
  FRESHCLAM_PRIVATE at FRESHCLAM_PRIVATE 0.102.1
  FRESHCLAM_PUBLIC at FRESHCLAM_PUBLIC 0.102.1
diff -Nru clamav-0.102.3+dfsg/debian/patches/Add-support-for-LLVM-3.7.patch clamav-0.102.4+dfsg/debian/patches/Add-support-for-LLVM-3.7.patch
--- clamav-0.102.3+dfsg/debian/patches/Add-support-for-LLVM-3.7.patch	2020-05-30 00:03:59.000000000 +0200
+++ clamav-0.102.4+dfsg/debian/patches/Add-support-for-LLVM-3.7.patch	2020-07-18 00:19:32.000000000 +0200
@@ -1,4 +1,4 @@
-From 13e4f6183203d196555ebdadfcabcf19429c8f4a Mon Sep 17 00:00:00 2001
+From 036ff01c13e7d7473e3d4d645d831a15f3791fe2 Mon Sep 17 00:00:00 2001
 From: Andreas Cadhalpun <Andreas.Cadhalpun at googlemail.com>
 Date: Fri, 14 Oct 2016 20:24:39 +0200
 Subject: Add support for LLVM 3.7
diff -Nru clamav-0.102.3+dfsg/debian/patches/Add-support-for-LLVM-3.8.patch clamav-0.102.4+dfsg/debian/patches/Add-support-for-LLVM-3.8.patch
--- clamav-0.102.3+dfsg/debian/patches/Add-support-for-LLVM-3.8.patch	2020-05-30 00:03:59.000000000 +0200
+++ clamav-0.102.4+dfsg/debian/patches/Add-support-for-LLVM-3.8.patch	2020-07-18 00:19:32.000000000 +0200
@@ -1,4 +1,4 @@
-From cad467b71ed31a10793bdac88e4c0bb0caa54991 Mon Sep 17 00:00:00 2001
+From 24dd86fa2b6a3bf629bad49897a0bc409f658ed7 Mon Sep 17 00:00:00 2001
 From: Andreas Cadhalpun <Andreas.Cadhalpun at googlemail.com>
 Date: Fri, 14 Oct 2016 20:24:48 +0200
 Subject: Add support for LLVM 3.8
diff -Nru clamav-0.102.3+dfsg/debian/patches/Add-support-for-LLVM-3.9.patch clamav-0.102.4+dfsg/debian/patches/Add-support-for-LLVM-3.9.patch
--- clamav-0.102.3+dfsg/debian/patches/Add-support-for-LLVM-3.9.patch	2020-05-30 00:03:59.000000000 +0200
+++ clamav-0.102.4+dfsg/debian/patches/Add-support-for-LLVM-3.9.patch	2020-07-18 00:19:32.000000000 +0200
@@ -1,4 +1,4 @@
-From f921741f4223abac2066e067495d16311bb8a655 Mon Sep 17 00:00:00 2001
+From 6260c20ec271d77e84b7a32f03a7ebc02bdc43ca Mon Sep 17 00:00:00 2001
 From: Andreas Cadhalpun <Andreas.Cadhalpun at googlemail.com>
 Date: Fri, 14 Oct 2016 20:24:56 +0200
 Subject: Add support for LLVM 3.9
diff -Nru clamav-0.102.3+dfsg/debian/patches/add-support-for-system-tomsfastmath.patch clamav-0.102.4+dfsg/debian/patches/add-support-for-system-tomsfastmath.patch
--- clamav-0.102.3+dfsg/debian/patches/add-support-for-system-tomsfastmath.patch	2020-05-30 00:03:59.000000000 +0200
+++ clamav-0.102.4+dfsg/debian/patches/add-support-for-system-tomsfastmath.patch	2020-07-18 00:19:32.000000000 +0200
@@ -1,4 +1,4 @@
-From 11ee2f472776b01b8ae4de78a1e1e52b1814079c Mon Sep 17 00:00:00 2001
+From 26c7b4bab716aec23e97e34b3cac919d91853439 Mon Sep 17 00:00:00 2001
 From: Andreas Cadhalpun <Andreas.Cadhalpun at googlemail.com>
 Date: Wed, 11 Mar 2015 20:03:15 +0100
 Subject: add support for system tomsfastmath
@@ -14,7 +14,7 @@
  create mode 100644 m4/reorganization/libs/tomsfastmath.m4
 
 diff --git a/configure.ac b/configure.ac
-index 8375971..3cacfb8 100644
+index c3bd7fc..513398d 100644
 --- a/configure.ac
 +++ b/configure.ac
 @@ -98,6 +98,7 @@ m4_include([m4/reorganization/libs/libmspack.m4])
diff -Nru clamav-0.102.3+dfsg/debian/patches/Change-paths-in-sample-conf-file-to-match-Debian.patch clamav-0.102.4+dfsg/debian/patches/Change-paths-in-sample-conf-file-to-match-Debian.patch
--- clamav-0.102.3+dfsg/debian/patches/Change-paths-in-sample-conf-file-to-match-Debian.patch	2020-05-30 00:03:59.000000000 +0200
+++ clamav-0.102.4+dfsg/debian/patches/Change-paths-in-sample-conf-file-to-match-Debian.patch	2020-07-18 00:19:32.000000000 +0200
@@ -1,4 +1,4 @@
-From d6669ed24e9e80dc91b787ab9734bf563f1ef628 Mon Sep 17 00:00:00 2001
+From c21a1e0040b47aff611b92416f9f39a6bb3ed139 Mon Sep 17 00:00:00 2001
 From: Scott Kitterman <scott at kitterman.com>
 Date: Mon, 10 Mar 2014 19:20:18 -0400
 Subject: Change paths in sample conf file to match Debian
diff -Nru clamav-0.102.3+dfsg/debian/patches/clamd_dont_depend_on_clamav_demon_socket.patch clamav-0.102.4+dfsg/debian/patches/clamd_dont_depend_on_clamav_demon_socket.patch
--- clamav-0.102.3+dfsg/debian/patches/clamd_dont_depend_on_clamav_demon_socket.patch	2020-05-30 00:03:59.000000000 +0200
+++ clamav-0.102.4+dfsg/debian/patches/clamd_dont_depend_on_clamav_demon_socket.patch	2020-07-18 00:19:32.000000000 +0200
@@ -1,4 +1,4 @@
-From 23dcb9a9f268a46c7df2d27c79c5adf341671ec4 Mon Sep 17 00:00:00 2001
+From eaa7e8852612270e87c7419601fca621fbd8b444 Mon Sep 17 00:00:00 2001
 From: Sebastian Andrzej Siewior <sebastian at breakpoint.cc>
 Date: Thu, 11 Aug 2016 21:54:10 +0200
 Subject: clamd: don't depend on clamav-demon.socket
diff -Nru clamav-0.102.3+dfsg/debian/patches/clamsubmit-libfreshclam-Use-CURL_CA_BUNDLE.patch clamav-0.102.4+dfsg/debian/patches/clamsubmit-libfreshclam-Use-CURL_CA_BUNDLE.patch
--- clamav-0.102.3+dfsg/debian/patches/clamsubmit-libfreshclam-Use-CURL_CA_BUNDLE.patch	2020-05-30 00:03:59.000000000 +0200
+++ clamav-0.102.4+dfsg/debian/patches/clamsubmit-libfreshclam-Use-CURL_CA_BUNDLE.patch	2020-07-18 00:19:32.000000000 +0200
@@ -1,4 +1,4 @@
-From 04fd79ea5eace5273a13bd66b095e2fef0ea3bff Mon Sep 17 00:00:00 2001
+From c07899f43b92f63e9ad0ccefa5379ca649603d4a Mon Sep 17 00:00:00 2001
 From: Sebastian Andrzej Siewior <sebastian at breakpoint.cc>
 Date: Sun, 16 Feb 2020 17:09:37 +0100
 Subject: clamsubmit / libfreshclam: Use CURL_CA_BUNDLE
diff -Nru clamav-0.102.3+dfsg/debian/rules clamav-0.102.4+dfsg/debian/rules
--- clamav-0.102.3+dfsg/debian/rules	2020-05-30 00:03:59.000000000 +0200
+++ clamav-0.102.4+dfsg/debian/rules	2020-07-18 00:22:08.000000000 +0200
@@ -88,7 +88,7 @@
 	  fi;\
 	done; \
 	# Check for library features which may have been upgraded.
-	if ! grep -q "CL_FLEVEL 114" libclamav/others.h ; then \
+	if ! grep -q "CL_FLEVEL 115" libclamav/others.h ; then \
 		echo "cl_retflevel needs boosting in symbol file"; \
 		touch debian/exit; \
 	fi;
diff -Nru clamav-0.102.3+dfsg/docs/html/UserManual/Installation-Windows.html clamav-0.102.4+dfsg/docs/html/UserManual/Installation-Windows.html
--- clamav-0.102.3+dfsg/docs/html/UserManual/Installation-Windows.html	2020-05-12 03:54:58.000000000 +0200
+++ clamav-0.102.4+dfsg/docs/html/UserManual/Installation-Windows.html	2020-07-15 23:54:53.000000000 +0200
@@ -15,9 +15,9 @@
 <h2 id="install-using-the-clamav-windows-installer">Install using the ClamAV Windows Installer</h2>
 <p>Important: Installing ClamAV using the Installer will require Administrator privileges.</p>
 <ol>
-<li>Download: <a href="http://www.clamav.net/downloads/production/ClamAV-0.102.2.exe" class="uri">http://www.clamav.net/downloads/production/ClamAV-0.102.2.exe</a></li>
+<li>Download: <a href="http://www.clamav.net/downloads/production/ClamAV-0.102.3.exe" class="uri">http://www.clamav.net/downloads/production/ClamAV-0.102.3.exe</a></li>
 <li>Locate the file in your Downloads directory.</li>
-<li>Right-click on <code>ClamAV-0.102.2.exe</code> and select <code>Run as administrator</code>. You may receive a warning message along the lines of "Windows protected your PC". Select <code>More info</code> and then select <code>Run anyway</code>.</li>
+<li>Right-click on <code>ClamAV-0.102.3.exe</code> and select <code>Run as administrator</code>. You may receive a warning message along the lines of "Windows protected your PC". Select <code>More info</code> and then select <code>Run anyway</code>.</li>
 <li>Select <code>I accept the agreement</code> and click <code>Next</code>.</li>
 <li>Click <code>Next</code> again. If you've removed a previous installation of ClamAV, you may receive the prompt "The folder ... already exists...". If you do, select <code>Yes</code>.</li>
 <li>Click <code>Install</code>.</li>
@@ -36,9 +36,9 @@
 <hr />
 <h2 id="install-using-the-clamav-portable-install-package">Install using the ClamAV Portable Install Package</h2>
 <ol>
-<li>Download: <a href="https://www.clamav.net/downloads/production/clamav-0.102.2-win-x64-portable.zip" class="uri">https://www.clamav.net/downloads/production/clamav-0.102.2-win-x64-portable.zip</a></li>
+<li>Download: <a href="https://www.clamav.net/downloads/production/clamav-0.102.3-win-x64-portable.zip" class="uri">https://www.clamav.net/downloads/production/clamav-0.102.3-win-x64-portable.zip</a></li>
 <li>Unzip it.</li>
-<li>Open the <code>clamav-0.102.2-win-x64-portable</code> directory.</li>
+<li>Open the <code>clamav-0.102.3-win-x64-portable</code> directory.</li>
 <li>Hold down Shift and then right-click on the background in the current directory (but not on one of the files). Select <code>"Open PowerShell window here"</code>. If that option doesn't appear, try again.</li>
 </ol>
 <p>Continue on to "First Time Set-Up"...</p>
diff -Nru clamav-0.102.3+dfsg/libclamav/blob.c clamav-0.102.4+dfsg/libclamav/blob.c
--- clamav-0.102.3+dfsg/libclamav/blob.c	2020-05-12 03:54:49.000000000 +0200
+++ clamav-0.102.4+dfsg/libclamav/blob.c	2020-07-15 23:54:36.000000000 +0200
@@ -176,7 +176,7 @@
 int blobAddData(blob *b, const unsigned char *data, size_t len)
 {
 #if HAVE_CLI_GETPAGESIZE
-    static int pagesize;
+    static int pagesize = 0;
     int growth;
 #endif
 
@@ -225,6 +225,10 @@
 
         b->size = growth;
         b->data = cli_malloc(growth);
+        if (NULL == b->data){
+            b->size = 0;
+            return -1;
+        }
     } else if (b->size < b->len + (off_t)len) {
         unsigned char *p = cli_realloc(b->data, b->size + growth);
 
@@ -241,6 +245,10 @@
 
         b->size = (off_t)len * 4;
         b->data = cli_malloc(b->size);
+        if (NULL == b->data){
+            b->size = 0;
+            return -1;
+        }
     } else if (b->size < b->len + (off_t)len) {
         unsigned char *p = cli_realloc(b->data, b->size + (len * 4));
 
@@ -255,6 +263,9 @@
     if (b->data) {
         memcpy(&b->data[b->len], data, len);
         b->len += (off_t)len;
+    } else {
+        b->size = 0;
+        return -1;
     }
     return 0;
 }
diff -Nru clamav-0.102.3+dfsg/libclamav/bytecode_api.h clamav-0.102.4+dfsg/libclamav/bytecode_api.h
--- clamav-0.102.3+dfsg/libclamav/bytecode_api.h	2020-05-12 03:54:49.000000000 +0200
+++ clamav-0.102.4+dfsg/libclamav/bytecode_api.h	2020-07-15 23:54:36.000000000 +0200
@@ -145,6 +145,7 @@
     FUNC_LEVEL_0102_1    = 112, /**< LibClamAV release 0.102.1 */
     FUNC_LEVEL_0102_2    = 113, /**< LibClamAV release 0.102.2 */
     FUNC_LEVEL_0102_3    = 114, /**< LibClamAV release 0.102.3 */
+    FUNC_LEVEL_0102_4    = 115, /**< LibClamAV release 0.102.4 */
 };
 
 /**
diff -Nru clamav-0.102.3+dfsg/libclamav/egg.c clamav-0.102.4+dfsg/libclamav/egg.c
--- clamav-0.102.3+dfsg/libclamav/egg.c	2020-05-12 03:54:49.000000000 +0200
+++ clamav-0.102.4+dfsg/libclamav/egg.c	2020-07-15 23:54:36.000000000 +0200
@@ -547,7 +547,7 @@
                     goto done;
                 }
 
-                lpWideCharStr = malloc((cchWideChar + 1) * sizeof(WCHAR));
+                lpWideCharStr = cli_malloc((cchWideChar + 1) * sizeof(WCHAR));
                 if (NULL == lpWideCharStr) {
                     cli_dbgmsg("egg_filename_to_utf8: failed to allocate memory for wide char string.\n");
                     status = CL_EMEM;
@@ -589,7 +589,7 @@
                 goto done;
             }
 
-            out_utf8 = malloc(out_utf8_size + 1);
+            out_utf8 = cli_malloc(out_utf8_size + 1);
             if (NULL == lpWideCharStr) {
                 cli_dbgmsg("egg_filename_to_utf8: failed to allocate memory for wide char to utf-8 string.\n");
                 status = CL_EMEM;
@@ -625,8 +625,16 @@
                 }
             }
 
+            if (NULL == encoding) {
+                cli_dbgmsg("egg_filename_to_utf8: Invalid codepage parameter passed in.\n");
+                goto done;
+            }
+
             for (attempt = 1; attempt <= 3; attempt++) {
-                char* out_utf8_tmp;
+                char* out_utf8_index = NULL;
+                char* out_utf8_tmp   = NULL;
+                char* inbuf          = in;
+                size_t iconvRet      = -1;
 
                 /* Charset to UTF-8 should never exceed in_size * 6;
                  * We can shrink final buffer after the conversion, if needed. */
@@ -635,7 +643,7 @@
                 inbytesleft  = in_size;
                 outbytesleft = out_utf8_size;
 
-                out_utf8 = cli_calloc(1, out_utf8_size + 1);
+                out_utf8 = out_utf8_index = cli_calloc(1, out_utf8_size + 1);
                 if (NULL == out_utf8) {
                     cli_errmsg("egg_filename_to_utf8: Failure allocating buffer for utf8 data.\n");
                     status = CL_EMEM;
@@ -647,7 +655,10 @@
                     goto done;
                 }
 
-                if ((size_t)-1 == iconv(conv, &in, &inbytesleft, &out_utf8, &outbytesleft)) {
+                iconvRet = iconv(conv, &inbuf, &inbytesleft, &out_utf8_index, &outbytesleft);
+                iconv_close(conv);
+                conv = (iconv_t)-1;
+                if ((size_t)-1 == iconvRet) {
                     switch (errno) {
                         case E2BIG:
                             cli_warnmsg("egg_filename_to_utf8: iconv error: There is not sufficient room at *outbuf.\n");
@@ -676,6 +687,7 @@
                 }
                 out_utf8      = out_utf8_tmp;
                 out_utf8_size = out_utf8_size - outbytesleft;
+                break;
             }
 
 #else
diff -Nru clamav-0.102.3+dfsg/libclamav/hwp.c clamav-0.102.4+dfsg/libclamav/hwp.c
--- clamav-0.102.3+dfsg/libclamav/hwp.c	2020-05-12 03:54:49.000000000 +0200
+++ clamav-0.102.4+dfsg/libclamav/hwp.c	2020-07-15 23:54:36.000000000 +0200
@@ -1810,6 +1810,8 @@
         new_offset = offset + (2 + nfonts * 40);
         if ((new_offset <= offset) || (new_offset >= map->len)) {
             cli_errmsg("HWP3.x: Font Entry: number of fonts is too high, invalid. %u\n", nfonts);
+            if (dmap)
+                funmap(dmap);
             return CL_EPARSE;
         }
         offset = new_offset;
@@ -1831,6 +1833,8 @@
     new_offset = offset + (2 + nstyles * 238);
     if ((new_offset <= offset) || (new_offset >= map->len)) {
         cli_errmsg("HWP3.x: Font Entry: number of font styles is too high, invalid. %u\n", nstyles);
+        if (dmap)
+            funmap(dmap);
         return CL_EPARSE;
     }
     offset += (2 + nstyles * 238);
diff -Nru clamav-0.102.3+dfsg/libclamav/libclamav.map clamav-0.102.4+dfsg/libclamav/libclamav.map
--- clamav-0.102.3+dfsg/libclamav/libclamav.map	2020-05-12 03:54:49.000000000 +0200
+++ clamav-0.102.4+dfsg/libclamav/libclamav.map	2020-07-15 23:54:36.000000000 +0200
@@ -258,6 +258,9 @@
     cl_base64_encode;
     cli_sanitize_filepath;
     cli_gentemp_with_prefix;
+    cli_basename;
+    cli_realpath;
+    cli_codepage_to_utf8;
 
     __cli_strcasestr;
     __cli_strndup;
diff -Nru clamav-0.102.3+dfsg/libclamav/others_common.c clamav-0.102.4+dfsg/libclamav/others_common.c
--- clamav-0.102.3+dfsg/libclamav/others_common.c	2020-05-12 03:54:49.000000000 +0200
+++ clamav-0.102.4+dfsg/libclamav/others_common.c	2020-07-15 23:54:36.000000000 +0200
@@ -1104,12 +1104,8 @@
 
 cl_error_t cli_get_filepath_from_filedesc(int desc, char **filepath)
 {
-    cl_error_t status = CL_EARG;
-
-    if (NULL == filepath) {
-        cli_errmsg("cli_get_filepath_from_filedesc: Invalid args.\n");
-        goto done;
-    }
+    cl_error_t status        = CL_EARG;
+    char *evaluated_filepath = NULL;
 
 #ifdef __linux__
     char fname[PATH_MAX];
@@ -1119,6 +1115,11 @@
 
     memset(&fname, 0, PATH_MAX);
 
+    if (NULL == filepath) {
+        cli_errmsg("cli_get_filepath_from_filedesc: Invalid args.\n");
+        goto done;
+    }
+
     snprintf(link, sizeof(link), "/proc/self/fd/%u", desc);
     link[sizeof(link) - 1] = '\0';
 
@@ -1131,8 +1132,8 @@
     /* Success. Add null terminator */
     fname[linksz] = '\0';
 
-    *filepath = CLI_STRNDUP(fname, CLI_STRNLEN(fname, PATH_MAX));
-    if (NULL == *filepath) {
+    evaluated_filepath = CLI_STRNDUP(fname, CLI_STRNLEN(fname, PATH_MAX));
+    if (NULL == evaluated_filepath) {
         cli_errmsg("cli_get_filepath_from_filedesc: Failed to allocate memory to store filename\n");
         status = CL_EMEM;
         goto done;
@@ -1142,59 +1143,135 @@
     char fname[PATH_MAX];
     memset(&fname, 0, PATH_MAX);
 
+    if (NULL == filepath) {
+        cli_errmsg("cli_get_filepath_from_filedesc: Invalid args.\n");
+        goto done;
+    }
+
     if (fcntl(desc, F_GETPATH, &fname) < 0) {
         printf("cli_get_filepath_from_filedesc: Failed to resolve filename for descriptor %d\n", desc);
         status = CL_EOPEN;
         goto done;
     }
 
-    *filepath = CLI_STRNDUP(fname, CLI_STRNLEN(fname, PATH_MAX));
-    if (NULL == *filepath) {
+    evaluated_filepath = CLI_STRNDUP(fname, CLI_STRNLEN(fname, PATH_MAX));
+    if (NULL == evaluated_filepath) {
         cli_errmsg("cli_get_filepath_from_filedesc: Failed to allocate memory to store filename\n");
         status = CL_EMEM;
         goto done;
     }
 
 #elif _WIN32
-    DWORD dwRet    = 0;
-    intptr_t hFile = _get_osfhandle(desc);
+    DWORD dwRet                   = 0;
+    intptr_t hFile                = _get_osfhandle(desc);
+    char *long_evaluated_filepath = NULL;
 
-    dwRet = GetFinalPathNameByHandleA((HANDLE)hFile, NULL, 0, VOLUME_NAME_NT);
+    if (NULL == filepath) {
+        cli_errmsg("cli_get_filepath_from_filedesc: Invalid args.\n");
+        goto done;
+    }
+
+    dwRet = GetFinalPathNameByHandleA((HANDLE)hFile, NULL, 0, VOLUME_NAME_DOS);
     if (dwRet == 0) {
         cli_errmsg("cli_get_filepath_from_filedesc: Failed to resolve filename for descriptor %d\n", desc);
         status = CL_EOPEN;
         goto done;
     }
 
-    *filepath = calloc(dwRet + 1, 1);
-    if (NULL == *filepath) {
+    long_evaluated_filepath = calloc(dwRet + 1, 1);
+    if (NULL == long_evaluated_filepath) {
         cli_errmsg("cli_get_filepath_from_filedesc: Failed to allocate %u bytes to store filename\n", dwRet + 1);
         status = CL_EMEM;
         goto done;
     }
 
-    dwRet = GetFinalPathNameByHandleA((HANDLE)hFile, *filepath, dwRet + 1, VOLUME_NAME_NT);
+    dwRet = GetFinalPathNameByHandleA((HANDLE)hFile, long_evaluated_filepath, dwRet + 1, VOLUME_NAME_DOS);
     if (dwRet == 0) {
         cli_errmsg("cli_get_filepath_from_filedesc: Failed to resolve filename for descriptor %d\n", desc);
-        free(*filepath);
-        *filepath = NULL;
-        status    = CL_EOPEN;
+        free(long_evaluated_filepath);
+        long_evaluated_filepath = NULL;
+        status                  = CL_EOPEN;
+        goto done;
+    }
+
+    evaluated_filepath = calloc(strlen(long_evaluated_filepath) - strlen("\\\\?\\") + 1, 1);
+    if (NULL == evaluated_filepath) {
+        cli_errmsg("cli_get_filepath_from_filedesc: Failed to allocate %u bytes to store filename\n", dwRet + 1);
+        status = CL_EMEM;
         goto done;
     }
+    memcpy(evaluated_filepath,
+           long_evaluated_filepath + strlen("\\\\?\\"),
+           strlen(long_evaluated_filepath) - strlen("\\\\?\\"));
 
 #else
 
     cli_dbgmsg("cli_get_filepath_from_filedesc: No mechanism implemented to determine filename from file descriptor.\n");
-    *filepath = NULL;
-    status    = CL_BREAK;
+    status = CL_BREAK;
     goto done;
 
 #endif
 
     cli_dbgmsg("cli_get_filepath_from_filedesc: File path for fd [%d] is: %s\n", desc, *filepath);
+    status    = CL_SUCCESS;
+    *filepath = evaluated_filepath;
+
+done:
+
+#ifdef _WIN32
+    if (NULL != long_evaluated_filepath) {
+        free(long_evaluated_filepath);
+    }
+#endif
+    return status;
+}
+
+cl_error_t cli_realpath(const char *file_name, char **real_filename)
+{
+    char *real_file_path = NULL;
+    cl_error_t status    = CL_EARG;
+#ifdef _WIN32
+    int desc = -1;
+#endif
+
+    cli_dbgmsg("Checking realpath of %s\n", file_name);
+
+    if (NULL == file_name || NULL == real_filename) {
+        cli_warnmsg("cli_realpath: Invalid arguments.\n");
+        goto done;
+    }
+
+#ifndef _WIN32
+
+    real_file_path = realpath(file_name, NULL);
+    if (NULL == real_file_path) {
+        status = CL_EMEM;
+        goto done;
+    }
+
     status = CL_SUCCESS;
 
+#else
+
+    if ((desc = safe_open(file_name, O_RDONLY | O_BINARY)) == -1) {
+        cli_warnmsg("Can't open file %s: %s\n", file_name, strerror(errno));
+        status = CL_EOPEN;
+        goto done;
+    }
+
+    status = cli_get_filepath_from_filedesc(desc, &real_file_path);
+
+#endif
+
+    *real_filename = real_file_path;
+
 done:
 
+#ifdef _WIN32
+    if (-1 != desc) {
+        close(desc);
+    }
+#endif
+
     return status;
 }
diff -Nru clamav-0.102.3+dfsg/libclamav/others.h clamav-0.102.4+dfsg/libclamav/others.h
--- clamav-0.102.3+dfsg/libclamav/others.h	2020-05-12 03:54:49.000000000 +0200
+++ clamav-0.102.4+dfsg/libclamav/others.h	2020-07-15 23:54:36.000000000 +0200
@@ -71,7 +71,7 @@
  * in re-enabling affected modules.
  */
 
-#define CL_FLEVEL 114
+#define CL_FLEVEL 115
 #define CL_FLEVEL_DCONF CL_FLEVEL
 #define CL_FLEVEL_SIGTOOL CL_FLEVEL
 
@@ -903,4 +903,18 @@
  */
 cl_error_t cli_get_filepath_from_filedesc(int desc, char **filepath);
 
+/**
+ * @brief   Attempt to get the real path of a provided path (evaluating symlinks).
+ *
+ * Caller is responsible for free'ing the file path.
+ * On posix systems this just calls realpath() under the hood.
+ * On Win32, it opens a handle and uses cli_get_filepath_from_filedesc()
+ * to get the real path.
+ *
+ * @param desc          A file path to evaluate.
+ * @param char*         [out] A malloced string containing the real path.
+ * @return cl_error_t   CL_SUCCESS if found, else an error code.
+ */
+cl_error_t cli_realpath(const char *file_name, char **real_filename);
+
 #endif
diff -Nru clamav-0.102.3+dfsg/libclamav/unarj.c clamav-0.102.4+dfsg/libclamav/unarj.c
--- clamav-0.102.3+dfsg/libclamav/unarj.c	2020-05-12 03:54:49.000000000 +0200
+++ clamav-0.102.4+dfsg/libclamav/unarj.c	2020-07-15 23:54:36.000000000 +0200
@@ -840,11 +840,17 @@
     unsigned char *comnorm = NULL;
     uint32_t ret           = TRUE;
 
+    size_t filename_max_len = 0;
+    size_t filename_len     = 0;
+    size_t comment_max_len  = 0;
+    size_t comment_len      = 0;
+    size_t orig_offset      = metadata->offset;
+
     if (fmap_readn(metadata->map, &header_size, metadata->offset, 2) != 2)
         return FALSE;
 
     metadata->offset += 2;
-    header_size   = le16_to_host(header_size);
+    header_size = le16_to_host(header_size);
     cli_dbgmsg("Header Size: %d\n", header_size);
     if (header_size == 0) {
         /* End of archive */
@@ -856,6 +862,11 @@
         ret = FALSE;
         goto done;
     }
+    if ((header_size + sizeof(header_size)) > (metadata->map->real_len - metadata->offset)) {
+        cli_dbgmsg("arj_read_header: invalid header_size: %u, exceeds length of file.\n", header_size);
+        ret = FALSE;
+        goto done;
+    }
     if (fmap_readn(metadata->map, &main_hdr, metadata->offset, 30) != 30) {
         ret = FALSE;
         goto done;
@@ -880,29 +891,47 @@
         metadata->offset += main_hdr.first_hdr_size - 30;
     }
 
-    fnnorm   = cli_calloc(sizeof(unsigned char), header_size + 1);
-    filename = fmap_need_offstr(metadata->map, metadata->offset, header_size + 1);
-    if (!filename) {
-        cli_dbgmsg("UNARJ: Unable to allocate memory for filename\n");
+    filename_max_len = (header_size + sizeof(header_size)) - (metadata->offset - orig_offset);
+    if (filename_max_len > header_size) {
+        cli_dbgmsg("UNARJ: Format error. First Header Size invalid");
         ret = FALSE;
         goto done;
     }
-    metadata->offset += CLI_STRNLEN(filename, header_size) + 1;
+    if (filename_max_len > 0) {
+        fnnorm   = cli_calloc(sizeof(unsigned char), filename_max_len + 1);
+        filename = fmap_need_offstr(metadata->map, metadata->offset, filename_max_len + 1);
+        if (!filename || !fnnorm) {
+            cli_dbgmsg("UNARJ: Unable to allocate memory for filename\n");
+            ret = FALSE;
+            goto done;
+        }
+        filename_len = CLI_STRNLEN(filename, filename_max_len);
+    }
+    metadata->offset += filename_len + 1;
 
-    comnorm = cli_calloc(sizeof(unsigned char), header_size + 1);
-    comment = fmap_need_offstr(metadata->map, metadata->offset, header_size + 1);
-    if (!comment || !comnorm) {
-        cli_dbgmsg("UNARJ: Unable to allocate memory for comment\n");
+    comment_max_len = (header_size + sizeof(header_size)) - (metadata->offset - orig_offset);
+    if (comment_max_len > header_size) {
+        cli_dbgmsg("UNARJ: Format error. First Header Size invalid");
         ret = FALSE;
         goto done;
     }
-    metadata->offset += CLI_STRNLEN(comment, header_size) + 1;
+    if (comment_max_len > 0) {
+        comnorm = cli_calloc(sizeof(unsigned char), comment_max_len + 1);
+        comment = fmap_need_offstr(metadata->map, metadata->offset, comment_max_len + 1);
+        if (!comment || !comnorm) {
+            cli_dbgmsg("UNARJ: Unable to allocate memory for comment\n");
+            ret = FALSE;
+            goto done;
+        }
+        comment_len = CLI_STRNLEN(comment, comment_max_len);
+    }
+    metadata->offset += comment_len + 1;
 
-    text_normalize_init(&fnstate, fnnorm, header_size);
-    text_normalize_init(&comstate, comnorm, header_size);
+    text_normalize_init(&fnstate, fnnorm, filename_max_len);
+    text_normalize_init(&comstate, comnorm, comment_max_len);
 
-    text_normalize_buffer(&fnstate, (const unsigned char *)filename, header_size);
-    text_normalize_buffer(&comstate, (const unsigned char *)comment, header_size);
+    text_normalize_buffer(&fnstate, (const unsigned char *)filename, filename_len);
+    text_normalize_buffer(&comstate, (const unsigned char *)comment, comment_len);
 
     cli_dbgmsg("Filename: %s\n", fnnorm);
     cli_dbgmsg("Comment: %s\n", comnorm);
@@ -949,6 +978,12 @@
     unsigned char *comnorm = NULL;
     uint32_t ret           = CL_SUCCESS;
 
+    size_t filename_max_len = 0;
+    size_t filename_len     = 0;
+    size_t comment_max_len  = 0;
+    size_t comment_len      = 0;
+    size_t orig_offset      = metadata->offset;
+
     if (fmap_readn(metadata->map, &header_size, metadata->offset, 2) != 2)
         return CL_EFORMAT;
     header_size = le16_to_host(header_size);
@@ -965,7 +1000,11 @@
         ret = CL_EFORMAT;
         goto done;
     }
-
+    if ((header_size + sizeof(header_size)) > (metadata->map->real_len - metadata->offset)) {
+        cli_dbgmsg("arj_read_file_header: invalid header_size: %u, exceeds length of file.\n", header_size);
+        ret = FALSE;
+        goto done;
+    }
     if (fmap_readn(metadata->map, &file_hdr, metadata->offset, 30) != 30) {
         ret = CL_EFORMAT;
         goto done;
@@ -997,33 +1036,51 @@
         metadata->offset += file_hdr.first_hdr_size - 30;
     }
 
-    fnnorm   = cli_calloc(sizeof(unsigned char), header_size + 1);
-    filename = fmap_need_offstr(metadata->map, metadata->offset, header_size + 1);
-    if (!filename) {
-        cli_dbgmsg("UNARJ: Unable to allocate memory for filename\n");
+    filename_max_len = (header_size + sizeof(header_size)) - (metadata->offset - orig_offset);
+    if (filename_max_len > header_size) {
+        cli_dbgmsg("UNARJ: Format error. First Header Size invalid");
         ret = FALSE;
         goto done;
     }
-    metadata->offset += CLI_STRNLEN(filename, header_size) + 1;
+    if (filename_max_len > 0) {
+        fnnorm   = cli_calloc(sizeof(unsigned char), filename_max_len + 1);
+        filename = fmap_need_offstr(metadata->map, metadata->offset, filename_max_len + 1);
+        if (!filename || !fnnorm) {
+            cli_dbgmsg("UNARJ: Unable to allocate memory for filename\n");
+            ret = FALSE;
+            goto done;
+        }
+        filename_len = CLI_STRNLEN(filename, filename_max_len);
+    }
+    metadata->offset += filename_len + 1;
 
-    comnorm = cli_calloc(sizeof(unsigned char), header_size + 1);
-    comment = fmap_need_offstr(metadata->map, metadata->offset, header_size + 1);
-    if (!comment) {
-        cli_dbgmsg("UNARJ: Unable to allocate memory for comment\n");
+    comment_max_len = (header_size + sizeof(header_size)) - (metadata->offset - orig_offset);
+    if (comment_max_len > header_size) {
+        cli_dbgmsg("UNARJ: Format error. First Header Size invalid");
         ret = FALSE;
         goto done;
     }
-    metadata->offset += CLI_STRNLEN(comment, header_size) + 1;
+    if (comment_max_len > 0) {
+        comnorm = cli_calloc(sizeof(unsigned char), comment_max_len + 1);
+        comment = fmap_need_offstr(metadata->map, metadata->offset, comment_max_len + 1);
+        if (!comment || !comnorm) {
+            cli_dbgmsg("UNARJ: Unable to allocate memory for comment\n");
+            ret = FALSE;
+            goto done;
+        }
+        comment_len += CLI_STRNLEN(comment, comment_max_len);
+    }
+    metadata->offset += comment_len + 1;
 
-    text_normalize_init(&fnstate, fnnorm, header_size);
-    text_normalize_init(&comstate, comnorm, header_size);
+    text_normalize_init(&fnstate, fnnorm, filename_max_len);
+    text_normalize_init(&comstate, comnorm, comment_max_len);
 
-    text_normalize_buffer(&fnstate, (const unsigned char *)filename, header_size);
-    text_normalize_buffer(&comstate, (const unsigned char *)comment, header_size);
+    text_normalize_buffer(&fnstate, (const unsigned char *)filename, filename_len);
+    text_normalize_buffer(&comstate, (const unsigned char *)comment, comment_len);
 
     cli_dbgmsg("Filename: %s\n", fnnorm);
     cli_dbgmsg("Comment: %s\n", comnorm);
-    metadata->filename = CLI_STRNDUP(filename, header_size);
+    metadata->filename = CLI_STRNDUP(filename, filename_len);
 
     /* Skip CRC */
     metadata->offset += 4;
diff -Nru clamav-0.102.3+dfsg/m4/reorganization/version.m4 clamav-0.102.4+dfsg/m4/reorganization/version.m4
--- clamav-0.102.3+dfsg/m4/reorganization/version.m4	2020-05-12 03:54:49.000000000 +0200
+++ clamav-0.102.4+dfsg/m4/reorganization/version.m4	2020-07-15 23:54:36.000000000 +0200
@@ -3,7 +3,7 @@
 dnl For beta,                  set: VERSION="<version>-beta"
 dnl For release candidate,     set: VERSION="<version>-rc"
 dnl For release,               set: VERSION="<version>"
-VERSION="0.102.3"
+VERSION="0.102.4"
 
 major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/[^0-9]//g"`
 minor=`echo $PACKAGE_VERSION |cut -d. -f2 | sed -e "s/[^0-9]//g"`
diff -Nru clamav-0.102.3+dfsg/NEWS.md clamav-0.102.4+dfsg/NEWS.md
--- clamav-0.102.3+dfsg/NEWS.md	2020-05-12 03:54:49.000000000 +0200
+++ clamav-0.102.4+dfsg/NEWS.md	2020-07-15 23:54:36.000000000 +0200
@@ -3,6 +3,36 @@
 Note: This file refers to the source tarball. Things described here may differ
  slightly from the binary packages.
 
+## 0.102.4
+
+ClamAV 0.102.4 is a bug patch release to address the following issues.
+
+- [CVE-2020-3350](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-3350):
+  Fix a vulnerability wherein a malicious user could replace a scan target's
+  directory with a symlink to another path to trick clamscan, clamdscan, or
+  clamonacc into removing or moving a different file (eg. a critical system
+  file). The issue would affect users that use the --move or --remove options
+  for clamscan, clamdscan, and clamonacc.
+
+  For more information about AV quarantine attacks using links, see the
+  [RACK911 Lab's report](https://www.rack911labs.com/research/exploiting-almost-every-antivirus-software).
+
+- [CVE-2020-3327](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-3327):
+  Fix a vulnerability in the ARJ archive parsing module in ClamAV 0.102.3 that
+  could cause a Denial-of-Service (DoS) condition. Improper bounds checking
+  results in an out-of-bounds read which could cause a crash.
+  The previous fix for this CVE in 0.102.3 was incomplete. This fix correctly
+  resolves the issue.
+
+- [CVE-2020-3481](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-3481):
+  Fix a vulnerability in the EGG archive module in ClamAV 0.102.0 - 0.102.3
+  could cause a Denial-of-Service (DoS) condition. Improper error handling
+  may result in a crash due to a NULL pointer dereference.
+  This vulnerability is mitigated for those using the official ClamAV
+  signature databases because the file type signatures in daily.cvd
+  will not enable the EGG archive parser in versions affected by the
+  vulnerability.
+
 ## 0.102.3
 
 ClamAV 0.102.3 is a bug patch release to address the following issues.
diff -Nru clamav-0.102.3+dfsg/shared/actions.c clamav-0.102.4+dfsg/shared/actions.c
--- clamav-0.102.3+dfsg/shared/actions.c	2020-05-12 03:54:49.000000000 +0200
+++ clamav-0.102.4+dfsg/shared/actions.c	2020-07-15 23:54:36.000000000 +0200
@@ -2,7 +2,7 @@
  *  Copyright (C) 2013-2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
  *  Copyright (C) 2009-2013 Sourcefire, Inc.
  *
- *  Author: aCaB
+ *  Author: aCaB, Micah Snyder
  *
  *  These functions are actions that may be taken when a sample alerts.
  *  The user may wish to:
@@ -25,6 +25,11 @@
  *  MA 02110-1301, USA.
  */
 
+#ifdef _WIN32
+#include <windows.h>
+#include <winternl.h>
+#endif
+
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
@@ -36,11 +41,14 @@
 #if HAVE_UNISTD_H
 #include <unistd.h>
 #endif
+#include <stdbool.h>
 #include <fcntl.h>
 #include <errno.h>
 #include <libgen.h>
 
 #include "libclamav/clamav.h"
+#include "libclamav/str.h"
+#include "libclamav/others.h"
 #include "shared/optparser.h"
 #include "shared/output.h"
 #include "shared/misc.h"
@@ -84,24 +92,554 @@
     return -1;
 }
 
+#ifdef _WIN32
+
+typedef LONG (*PNTCF)(
+    PHANDLE FileHandle, // OUT
+    ACCESS_MASK DesiredAccess,
+    POBJECT_ATTRIBUTES ObjectAttributes,
+    PIO_STATUS_BLOCK IoStatusBlock, // OUT
+    PLARGE_INTEGER AllocationSize,
+    ULONG FileAttributes,
+    ULONG ShareAccess,
+    ULONG CreateDisposition,
+    ULONG CreateOptions,
+    PVOID EaBuffer,
+    ULONG EaLength);
+
+typedef void (*PRIUS)(
+    PUNICODE_STRING DestinationString,
+    PCWSTR SourceString);
+
+/**
+ * @brief A openat equivalent for Win32 with a check to NOFOLLOW soft-links.
+ *
+ * The caller is resposible for closing the HANDLE.
+ *
+ * For the desiredAccess, fileAttributes, createOptions, and shareAccess parameters
+ * see https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntcreatefile
+ *
+ * @param current_handle        The current handle. If set to NULL, then filename should be a drive letter.
+ * @param filename              The directory to open. If current_handle is valid, should be a directory found in the current directory.
+ * @param pNtCreateFile         A function pointer to the NtCreateFile Win32 Native API.
+ * @param pRtlInitUnicodeString A function pointer to the RtlInitUnicodeString Win32 Native API.
+ * @param desiredAccess         The DesiredAccess option for NtCreateFile
+ * @param fileAttributes        The FileAttributes option for NtCreateFile
+ * @param createOptions         The CreateOptions option for NtCreateFile
+ * @param shareAccess           The ShareAccess option for NtCreateFile
+ * @return HANDLE               A handle on success, NULL on failure.
+ */
+static HANDLE win32_openat(
+    HANDLE current_handle,
+    const char *filename,
+    PNTCF pNtCreateFile,
+    PRIUS pRtlInitUnicodeString,
+    ACCESS_MASK desiredAccess,
+    ULONG fileAttributes,
+    ULONG createOptions,
+    ULONG shareAccess)
+{
+    HANDLE next_handle = NULL;
+
+    LONG ntStatus;
+    WCHAR *filenameW = NULL;
+    UNICODE_STRING filenameU;
+    int cchNextDirectoryName        = 0;
+    IO_STATUS_BLOCK ioStatusBlock   = {0};
+    OBJECT_ATTRIBUTES objAttributes = {0};
+    FILE_ATTRIBUTE_TAG_INFO tagInfo = {0};
+
+    /* Convert filename to a UNICODE_STRING, required by the native API NtCreateFile() */
+    cchNextDirectoryName = MultiByteToWideChar(CP_UTF8, 0, filename, -1, NULL, 0);
+    filenameW            = malloc(cchNextDirectoryName * sizeof(WCHAR));
+    if (NULL == filenameW) {
+        logg("win32_openat: failed to allocate memory for next directory name UTF16LE string\n");
+        goto done;
+    }
+    if (0 == MultiByteToWideChar(CP_UTF8, 0, filename, -1, filenameW, cchNextDirectoryName)) {
+        logg("win32_openat: failed to allocate buffer for unicode version of intermediate directory name.\n");
+        goto done;
+    }
+    pRtlInitUnicodeString(&filenameU, filenameW);
+
+    InitializeObjectAttributes(
+        &objAttributes,       // ObjectAttributes
+        &filenameU,           // ObjectName
+        OBJ_CASE_INSENSITIVE, // Attributes
+        current_handle,       // Root directory
+        NULL);                // SecurityDescriptor
+
+    ntStatus = pNtCreateFile(
+        &next_handle,   // FileHandle
+        desiredAccess,  // DesiredAccess
+        &objAttributes, // ObjectAttributes
+        &ioStatusBlock, // [out] status
+        0,              // AllocationSize
+        fileAttributes, // FileAttributes
+        shareAccess,    // ShareAccess
+        FILE_OPEN,      // CreateDisposition
+        createOptions,  // CreateOptions
+        NULL,           // EaBuffer
+        0);             // EaLength
+    if (!NT_SUCCESS(ntStatus) || (NULL == next_handle)) {
+        logg("win32_openat: Failed to open file '%s'. \nError: 0x%x \nioStatusBlock: 0x%x\n", filename, ntStatus, ioStatusBlock.Information);
+        goto done;
+    }
+    logg("*win32_openat: Opened file \"%s\"\n", filename);
+
+    if (0 == GetFileInformationByHandleEx(
+                 next_handle,                        // hFile,
+                 FileAttributeTagInfo,               // FileInformationClass
+                 &tagInfo,                           // lpFileInformation
+                 sizeof(FILE_ATTRIBUTE_TAG_INFO))) { // dwBufferSize
+        logg("win32_openat: Failed to get file information by handle '%s'.  Error: %d.\n", filename, GetLastError());
+
+        CloseHandle(next_handle);
+        next_handle = NULL;
+        goto done;
+    }
+    logg("*win32_openat: tagInfo.FileAttributes: 0x%0x\n", tagInfo.FileAttributes);
+    logg("*win32_openat: tagInfo.ReparseTag:     0x%0x\n", tagInfo.ReparseTag);
+    if (0 != (tagInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
+        logg("win32_openat: File is a soft link: '%s' Aborting path traversal.\n\n", filename);
+
+        CloseHandle(next_handle);
+        next_handle = NULL;
+        goto done;
+    }
+    logg("*win32_openat: File or directory is not a soft link.\n\n");
+
+done:
+    if (NULL != filenameW) {
+        free(filenameW);
+    }
+
+    return next_handle;
+}
+#endif
+
+/**
+ * @brief Traverse from root to the specified directory without following symlinks.
+ *
+ * The intention is so you can use `unlinkat` or `rename_at` to safely move or
+ * delete the target directory.
+ *
+ * The caller is responsible for closing the output file descriptor if the
+ * traversal succeeded.
+ *
+ * @param directory             The directory to traverse to (must be NULL terminated).
+ * @param want_directory_handle Set to true to get the directory handle containing the file, false to get the file handle.
+ * @param[out] out_handle       An open file descriptor or HANDLE (win32) for the directory.
+ * @return 0                    Traverse succeeded.
+ * @return -1                   Traverse failed.
+ */
+#ifndef _WIN32
+static int traverse_to(const char *directory, bool want_directory_handle, int *out_handle)
+#else
+static int traverse_to(const char *directory, bool want_directory_handle, HANDLE *out_handle)
+#endif
+{
+    int status = -1;
+    size_t tokens_count;
+    const char *tokens[PATH_MAX / 2];
+    size_t i;
+    char *tokenized_directory = NULL;
+#ifndef _WIN32
+    int current_handle = -1;
+    int next_handle    = -1;
+#else
+    bool bNeedDeleteFileAccess = false;
+
+    HMODULE ntdll               = NULL;
+    PNTCF pNtCreateFile         = NULL;
+    PRIUS pRtlInitUnicodeString = NULL;
+
+    PHANDLE current_handle = NULL;
+    PHANDLE next_handle    = NULL;
+
+    ACCESS_MASK desiredAccess = STANDARD_RIGHTS_READ | STANDARD_RIGHTS_WRITE | SYNCHRONIZE | FILE_READ_ATTRIBUTES | FILE_READ_EA;
+    ULONG fileAttributes      = FILE_ATTRIBUTE_DIRECTORY;
+    ULONG createOptions       = FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT;
+    ULONG shareAccess         = FILE_SHARE_READ;
+#endif
+
+    if (NULL == directory || NULL == out_handle) {
+        logg("traverse_to: Invalid arguments!\n");
+        goto done;
+    }
+
+#ifdef _WIN32
+    ntdll = LoadLibraryA("ntdll.dll");
+    if (NULL == ntdll) {
+        logg("traverse_to: failed to load ntdll!\n");
+        goto done;
+    }
+    pNtCreateFile = (PNTCF)GetProcAddress(ntdll, "NtCreateFile");
+    if (NULL == pNtCreateFile) {
+        logg("traverse_to: failed to get NtCreateFile proc address!\n");
+        goto done;
+    }
+    pRtlInitUnicodeString = (PRIUS)GetProcAddress(ntdll, "RtlInitUnicodeString");
+    if (NULL == pRtlInitUnicodeString) {
+        logg("traverse_to: failed to get pRtlInitUnicodeString proc address!\n");
+        goto done;
+    }
+#endif
+
+    tokenized_directory = strdup(directory);
+    if (NULL == tokenized_directory) {
+        logg("traverse_to: Failed to get copy of directory path to be tokenized!\n");
+        goto done;
+    }
+
+    tokens_count = cli_strtokenize(tokenized_directory, *PATHSEP, PATH_MAX / 2, tokens);
+    if (0 == tokens_count) {
+        logg("traverse_to: tokenize of target directory returned 0 tokens!\n");
+        goto done;
+    }
+
+#ifndef _WIN32
+    /*
+     * Open the root(/) directory, because it won't be the first token like a
+     * drive letter (i.e. "C:") would be on Windows.
+     */
+    current_handle = open("/", O_RDONLY | O_NOFOLLOW);
+    if (-1 == current_handle) {
+        logg("traverse_to: Failed to open file descriptor for '/' directory.\n");
+        goto done;
+    }
+#endif
+
+    if (true == want_directory_handle) {
+        tokens_count -= 1;
+    }
+
+    if (0 == tokens_count) {
+        logg("traverse_to: Failed to get copy of directory path to be tokenized!\n");
+        goto done;
+    }
+
+    for (i = 0; i < tokens_count; i++) {
+        if (0 == strlen(tokens[i])) {
+            /* Empty token, likely first / or double // */
+            continue;
+        }
+#ifndef _WIN32
+        next_handle = openat(current_handle, tokens[i], O_RDONLY | O_NOFOLLOW);
+        if (-1 == next_handle) {
+            logg("traverse_to: Failed open %s\n", tokens[i]);
+            goto done;
+        }
+        close(current_handle);
+        current_handle = next_handle;
+        next_handle    = -1;
+#else
+        if (true != want_directory_handle) {
+            if (i == tokens_count - 1) {
+                /* Change createfile options for our target file instead of an intermediate directory. */
+                desiredAccess  = FILE_ALL_ACCESS | DELETE;
+                fileAttributes = FILE_ATTRIBUTE_NORMAL;
+                createOptions  = FILE_NON_DIRECTORY_FILE;
+                shareAccess    = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
+            }
+        }
+        if (i == 0) {
+            /* NtCreateFile requires the \???\ prefix on drive letters. Eg: \???\C:\ */
+            size_t driveroot_len = strlen("\\??\\\\") + strlen(tokens[0]) + 1;
+            char *driveroot      = malloc(driveroot_len);
+            snprintf(driveroot, driveroot_len + 1, "\\??\\%s\\", tokens[0]);
+            next_handle = win32_openat(current_handle,
+                                       driveroot,
+                                       pNtCreateFile,
+                                       pRtlInitUnicodeString,
+                                       desiredAccess,
+                                       fileAttributes,
+                                       createOptions,
+                                       shareAccess);
+            free(driveroot);
+        } else {
+            next_handle = win32_openat(current_handle,
+                                       tokens[i],
+                                       pNtCreateFile,
+                                       pRtlInitUnicodeString,
+                                       desiredAccess,
+                                       fileAttributes,
+                                       createOptions,
+                                       shareAccess);
+        }
+        if (NULL == next_handle) {
+            logg("traverse_to: Failed open %s\n", tokens[i]);
+            goto done;
+        }
+        CloseHandle(current_handle);
+        current_handle = next_handle;
+        next_handle    = NULL;
+#endif
+        logg("*traverse_to: Handle opened for '%s' directory.\n", tokens[i]);
+    }
+
+    status      = 0;
+    *out_handle = current_handle;
+
+done:
+#ifndef _WIN32
+    if ((-1 == status) && (-1 != current_handle)) {
+        close(current_handle);
+    }
+#else
+    if ((-1 == status) && (NULL != current_handle)) {
+        CloseHandle(current_handle);
+    }
+#endif
+    if (NULL != tokenized_directory) {
+        free(tokenized_directory);
+    }
+
+    return status;
+}
+
+/**
+ * @brief Rename (move) a file from Source to Destination without following symlinks.
+ *
+ * This approach mitigates the possibility that one of the directories
+ * in the path has been replaces with a malicious symlink.
+ *
+ * @param source        Source pathname.
+ * @param destination   Destination pathname (including file name)
+ * @return 0            Rename succeeded.
+ * @return -1           Rename failed.
+ */
+static int traverse_rename(const char *source, const char *destination)
+{
+    int status = -1;
+#ifndef _WIN32
+    cl_error_t ret;
+    int source_directory_fd = -1;
+    char *source_basename   = NULL;
+#else
+    FILE_RENAME_INFO *fileInfo    = NULL;
+    HANDLE source_file_handle     = NULL;
+    HANDLE destination_dir_handle = NULL;
+    WCHAR *destFilepathW          = NULL;
+    int cchDestFilepath           = 0;
+#endif
+
+    if (NULL == source || NULL == destination) {
+        logg("traverse_rename: Invalid arguments!\n");
+        goto done;
+    }
+
+#ifndef _WIN32
+    if (0 != traverse_to(source, true, &source_directory_fd)) {
+        logg("traverse_rename: Failed to open file descriptor for source directory!\n");
+        goto done;
+    }
+#else
+    if (0 != traverse_to(source, false, &source_file_handle)) {
+        logg("traverse_rename: Failed to open file descriptor for source file!\n");
+        goto done;
+    }
+    if (0 != traverse_to(destination, true, &destination_dir_handle)) {
+        logg("traverse_rename: Failed to open file descriptor for destination directory!\n");
+        goto done;
+    }
+#endif
+
+#ifndef _WIN32
+    ret = cli_basename(source, strlen(source), &source_basename);
+    if (CL_SUCCESS != ret) {
+        logg("traverse_rename: Failed to get basename of source path:%s\n\tError: %d\n", source, (int)ret);
+        goto done;
+    }
+
+    if (0 != renameat(source_directory_fd, source_basename, -1, destination)) {
+        logg("traverse_rename: Failed to rename: %s\n\tto: %s\nError:%s\n", source, destination, strerror(errno));
+        goto done;
+    }
+#else
+    /* Convert destination filepath to a PWCHAR */
+    cchDestFilepath = MultiByteToWideChar(CP_UTF8, 0, destination, strlen(destination), NULL, 0);
+    destFilepathW   = calloc(cchDestFilepath * sizeof(WCHAR), 1);
+    if (NULL == destFilepathW) {
+        logg("traverse_rename: failed to allocate memory for destination basename UTF16LE string\n");
+        goto done;
+    }
+    if (0 == MultiByteToWideChar(CP_UTF8, 0, destination, strlen(destination), destFilepathW, cchDestFilepath)) {
+        logg("traverse_rename: failed to allocate buffer for UTF16LE version of destination file basename.\n");
+        goto done;
+    }
+
+    fileInfo = calloc(1, sizeof(FILE_RENAME_INFO) + cchDestFilepath * sizeof(WCHAR));
+    if (NULL == fileInfo) {
+        logg("traverse_rename: failed to allocate memory for fileInfo struct\n");
+        goto done;
+    }
+
+    fileInfo->ReplaceIfExists = TRUE;
+    fileInfo->RootDirectory   = NULL;
+    memcpy(fileInfo->FileName, destFilepathW, cchDestFilepath * sizeof(WCHAR));
+    fileInfo->FileNameLength = cchDestFilepath;
+    if (FALSE == SetFileInformationByHandle(
+                     source_file_handle,                                            // FileHandle
+                     FileRenameInfo,                                                // FileInformationClass
+                     fileInfo,                                                      // FileInformation
+                     sizeof(FILE_RENAME_INFO) + cchDestFilepath * sizeof(WCHAR))) { // Length
+
+        logg("traverse_rename: Failed to set file rename info for '%s' to '%s'.\nError: %d\n", source, destination, GetLastError());
+        goto done;
+    }
+#endif
+
+    status = 0;
+
+done:
+
+#ifndef _WIN32
+    if (NULL != source_basename) {
+        free(source_basename);
+    }
+
+    if (-1 != source_directory_fd) {
+        close(source_directory_fd);
+    }
+#else
+    if (NULL != fileInfo) {
+        free(fileInfo);
+    }
+    if (NULL != destFilepathW) {
+        free(destFilepathW);
+    }
+    if (NULL != source_file_handle) {
+        CloseHandle(source_file_handle);
+    }
+    if (NULL != destination_dir_handle) {
+        CloseHandle(destination_dir_handle);
+    }
+#endif
+
+    return status;
+}
+
+/**
+ * @brief Unlink (delete) a target file without following symlinks.
+ *
+ * This approach mitigates the possibility that one of the directories
+ * in the path has been replaces with a malicious symlink.
+ *
+ * @param target    A file to be deleted.
+ * @return 0        Unlink succeeded.
+ * @return -1       Unlink failed.
+ */
+static int traverse_unlink(const char *target)
+{
+    int status = -1;
+    cl_error_t ret;
+#ifndef _WIN32
+    int target_directory_fd = -1;
+#else
+    FILE_DISPOSITION_INFO fileInfo = {0};
+    HANDLE target_file_handle      = NULL;
+#endif
+    char *target_basename = NULL;
+
+    if (NULL == target) {
+        logg("traverse_unlink: Invalid arguments!\n");
+        goto done;
+    }
+
+#ifndef _WIN32
+    /* On posix, we want a file descriptor for the directory */
+    if (0 != traverse_to(target, true, &target_directory_fd)) {
+#else
+    /* On Windows, we want a handle to the file, not the directory */
+    if (0 != traverse_to(target, false, &target_file_handle)) {
+#endif
+        logg("traverse_unlink: Failed to open file descriptor for target directory!\n");
+        goto done;
+    }
+
+    ret = cli_basename(target, strlen(target), &target_basename);
+    if (CL_SUCCESS != ret) {
+        logg("traverse_unlink: Failed to get basename of target path: %s\n\tError: %d\n", target, (int)ret);
+        goto done;
+    }
+
+#ifndef _WIN32
+    if (0 != unlinkat(target_directory_fd, target_basename, 0)) {
+        logg("traverse_unlink: Failed to unlink: %s\nError:%s\n", target, strerror(errno));
+        goto done;
+    }
+#else
+    fileInfo.DeleteFileA = TRUE;
+    if (FALSE == SetFileInformationByHandle(
+                     target_file_handle,               // FileHandle
+                     FileDispositionInfo,              // FileInformationClass
+                     &fileInfo,                        // FileInformation
+                     sizeof(FILE_DISPOSITION_INFO))) { // Length
+
+        logg("traverse_unlink: Failed to set file disposition to 'DELETE' for '%s'.\n", target);
+        goto done;
+    }
+    if (FALSE == CloseHandle(target_file_handle)) {
+        logg("traverse_unlink: Failed to set close & delete file '%s'.\n", target);
+        goto done;
+    }
+    target_file_handle = NULL;
+#endif
+
+    status = 0;
+
+done:
+
+    if (NULL != target_basename) {
+        free(target_basename);
+    }
+
+#ifndef _WIN32
+    if (-1 != target_directory_fd) {
+        close(target_directory_fd);
+    }
+#else
+    if (NULL != target_file_handle) {
+        CloseHandle(target_file_handle);
+    }
+#endif
+    return status;
+}
+
 static void action_move(const char *filename)
 {
-    char *nuname;
-    int fd = getdest(filename, &nuname), copied = 0;
+    char *nuname        = NULL;
+    char *real_filename = NULL;
+    int fd              = -1;
+    int copied          = 0;
+
+    if (NULL == filename) {
+        goto done;
+    }
+
+    fd = getdest(filename, &nuname);
 
-    if (fd < 0 || (rename(filename, nuname) && ((copied = 1)) && filecopy(filename, nuname))) {
-        logg("!Can't move file %s\n", filename);
+#ifndef _WIN32
+    if (fd < 0 || (0 != traverse_rename(filename, nuname) && ((copied = 1)) && filecopy(filename, nuname))) {
+#else
+    if (fd < 0 || (((copied = 1)) && filecopy(filename, nuname))) {
+#endif
+        logg("!Can't move file %s to %s\n", filename, nuname);
         notmoved++;
-        if (nuname) unlink(nuname);
+        if (nuname) traverse_unlink(nuname);
     } else {
-        if (copied && unlink(filename))
-            logg("!Can't unlink '%s': %s\n", filename, strerror(errno));
+        if (copied && (0 != traverse_unlink(filename)))
+            logg("!Can't unlink '%s' after copy: %s\n", filename, strerror(errno));
         else
             logg("~%s: moved to '%s'\n", filename, nuname);
     }
 
+done:
+    if (NULL != real_filename) free(real_filename);
     if (fd >= 0) close(fd);
-    if (nuname) free(nuname);
+    if (NULL != nuname) free(nuname);
+    return;
 }
 
 static void action_copy(const char *filename)
@@ -112,7 +650,7 @@
     if (fd < 0 || filecopy(filename, nuname)) {
         logg("!Can't copy file '%s'\n", filename);
         notmoved++;
-        if (nuname) unlink(nuname);
+        if (nuname) traverse_unlink(nuname);
     } else
         logg("~%s: copied to '%s'\n", filename, nuname);
 
@@ -122,12 +660,22 @@
 
 static void action_remove(const char *filename)
 {
-    if (unlink(filename)) {
-        logg("!Can't remove file '%s'.\n", filename);
+    char *real_filename = NULL;
+
+    if (NULL == filename) {
+        goto done;
+    }
+
+    if (0 != traverse_unlink(filename)) {
+        logg("!Can't remove file '%s'\n", filename);
         notremoved++;
     } else {
         logg("~%s: Removed.\n", filename);
     }
+
+done:
+    if (NULL != real_filename) free(real_filename);
+    return;
 }
 
 static int isdir(void)
@@ -148,7 +696,15 @@
 {
     int move = optget(opts, "move")->enabled;
     if (move || optget(opts, "copy")->enabled) {
+        cl_error_t ret;
         actarget = optget(opts, move ? "move" : "copy")->strarg;
+#ifndef _WIN32
+        ret = cli_realpath((const char *)actarget, &actarget);
+        if (CL_SUCCESS != ret || NULL == actarget) {
+            logg("action_setup: Failed to get realpath of %s\n", actarget);
+            return 0;
+        }
+#endif
         if (!isdir()) return 1;
         action  = move ? action_move : action_copy;
         targlen = strlen(actarget);
diff -Nru clamav-0.102.3+dfsg/unit_tests/check_clamav.c clamav-0.102.4+dfsg/unit_tests/check_clamav.c
--- clamav-0.102.3+dfsg/unit_tests/check_clamav.c	2020-05-12 03:54:50.000000000 +0200
+++ clamav-0.102.4+dfsg/unit_tests/check_clamav.c	2020-07-15 23:54:36.000000000 +0200
@@ -973,6 +973,26 @@
 }
 END_TEST
 
+cl_error_t cli_codepage_to_utf8(char* in, size_t in_size, uint16_t codepage, char** out, size_t* out_size);
+
+START_TEST(test_cli_codepage_to_utf8)
+{
+    cl_error_t ret;
+    char *utf8       = NULL;
+    size_t utf8_size = 0;
+
+    ret = cli_codepage_to_utf8("\x00\x48\x00\x65\x00\x6c\x00\x6c\x00\x6f\x00\x20\x00\x77\x00\x6f\x00\x72\x00\x6c\x00\x64\x00\x21\x00\x00", 26, 1201, &utf8, &utf8_size);
+    ck_assert_msg(CL_SUCCESS == ret, "test_cli_codepage_to_utf8: Failed to convert CODEPAGE_UTF16_LE to UTF8: ret != SUCCESS!");
+    ck_assert_msg(NULL != utf8, "sanitize_path: Failed to convert CODEPAGE_UTF16_LE to UTF8: utf8 pointer is NULL!");
+    ck_assert_msg(0 == strcmp(utf8, "Hello world!"), "sanitize_path: '%s' doesn't match '%s'", utf8, "Hello world!");
+
+    if (NULL != utf8) {
+        free(utf8);
+        utf8 = NULL;
+    }
+}
+END_TEST
+
 static Suite *test_cli_suite(void)
 {
     Suite *s               = suite_create("cli");
@@ -992,6 +1012,7 @@
 
     suite_add_tcase(s, tc_cli_assorted);
     tcase_add_test(tc_cli_assorted, test_sanitize_path);
+    tcase_add_test(tc_cli_assorted, test_cli_codepage_to_utf8);
 
     return s;
 }
diff -Nru clamav-0.102.3+dfsg/unit_tests/valgrind.supp clamav-0.102.4+dfsg/unit_tests/valgrind.supp
--- clamav-0.102.3+dfsg/unit_tests/valgrind.supp	2020-05-12 03:54:50.000000000 +0200
+++ clamav-0.102.4+dfsg/unit_tests/valgrind.supp	2020-07-15 23:54:36.000000000 +0200
@@ -265,7 +265,6 @@
    fun:internal_ascii_loop
    fun:__gconv_transform_internal_ascii
    fun:wcsrtombs
-   fun:wcsrtombs
    ...
 }
 {
@@ -273,7 +272,6 @@
    Memcheck:Cond
    fun:__wcsnlen_avx2
    fun:wcsrtombs
-   fun:wcsrtombs
    ...
 }
 {
@@ -281,7 +279,6 @@
    Memcheck:Cond
    fun:__wcsnlen_sse4_1
    fun:wcsrtombs
-   fun:wcsrtombs
    ...
 }
 {


More information about the Pkg-clamav-devel mailing list