diff --git a/CMakeLists.txt b/CMakeLists.txt index 94a61f0517..93f87b197e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,7 @@ string(TIMESTAMP TODAY "%Y%m%d") set(VERSION_SUFFIX "") project( ClamAV - VERSION "1.4.0" + VERSION "1.4.1" DESCRIPTION "ClamAV open source email, web, and end-point anti-virus toolkit." ) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) diff --git a/Jenkinsfile b/Jenkinsfile index d54a956b0f..f32e8ba5f1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -10,7 +10,7 @@ properties( parameters( [ string(name: 'VERSION', - defaultValue: '1.4.0', + defaultValue: '1.4.1', description: 'ClamAV version string'), string(name: 'FRAMEWORK_BRANCH', defaultValue: '1.4', diff --git a/NEWS.md b/NEWS.md index c1b7336d53..9092496e79 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,6 +3,38 @@ Note: This file refers to the official packages. Things described here may differ slightly from third-party binary packages. +## 1.4.1 + +ClamAV 1.4.1 is a critical patch release with the following fixes: + +- [CVE-2024-20506](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-20506): + Changed the logging module to disable following symlinks on Linux and Unix + systems so as to prevent an attacker with existing access to the 'clamd' or + 'freshclam' services from using a symlink to corrupt system files. + + This issue affects all currently supported versions. It will be fixed in: + - 1.4.1 + - 1.3.2 + - 1.0.7 + - 0.103.12 + + Thank you to Detlef for identifying this issue. + +- [CVE-2024-20505](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-20505): + Fixed a possible out-of-bounds read bug in the PDF file parser that could + cause a denial-of-service (DoS) condition. + + This issue affects all currently supported versions. It will be fixed in: + - 1.4.1 + - 1.3.2 + - 1.0.7 + - 0.103.12 + + Thank you to OSS-Fuzz for identifying this issue. + +- Removed unused Python modules from freshclam tests including deprecated + 'cgi' module that is expected to cause test failures in Python 3.13. + ## 1.4.0 ClamAV 1.4.0 includes the following improvements and changes: diff --git a/common/output.c b/common/output.c index 72e57ea96e..385dad7b15 100644 --- a/common/output.c +++ b/common/output.c @@ -58,6 +58,12 @@ #include "output.h" +// Define O_NOFOLLOW for systems that don't have it. +// Notably, Windows doesn't have O_NOFOLLOW. +#ifndef O_NOFOLLOW +#define O_NOFOLLOW 0 +#endif + #ifdef CL_THREAD_SAFE #include pthread_mutex_t logg_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -304,7 +310,6 @@ int logg(loglevel_t loglevel, const char *str, ...) char buffer[1025], *abuffer = NULL, *buff; time_t currtime; size_t len; - mode_t old_umask; #ifdef F_WRLCK struct flock fl; #endif @@ -338,18 +343,36 @@ int logg(loglevel_t loglevel, const char *str, ...) logg_open(); if (!logg_fp && logg_file) { - old_umask = umask(0037); - if ((logg_fp = fopen(logg_file, "at")) == NULL) { - umask(old_umask); + int logg_file_fd = -1; + + logg_file_fd = open(logg_file, O_WRONLY | O_CREAT | O_APPEND | O_NOFOLLOW, 0640); + if (-1 == logg_file_fd) { + char errbuf[128]; + cli_strerror(errno, errbuf, sizeof(errbuf)); + printf("ERROR: Failed to open log file %s: %s\n", logg_file, errbuf); + #ifdef CL_THREAD_SAFE pthread_mutex_unlock(&logg_mutex); #endif - printf("ERROR: Can't open %s in append mode (check permissions!).\n", logg_file); - if (len > sizeof(buffer)) + if (abuffer) free(abuffer); return -1; - } else - umask(old_umask); + } + + logg_fp = fdopen(logg_file_fd, "at"); + if (NULL == logg_fp) { + char errbuf[128]; + cli_strerror(errno, errbuf, sizeof(errbuf)); + printf("ERROR: Failed to convert the open log file descriptor for %s to a FILE* handle: %s\n", logg_file, errbuf); + + close(logg_file_fd); +#ifdef CL_THREAD_SAFE + pthread_mutex_unlock(&logg_mutex); +#endif + if (abuffer) + free(abuffer); + return -1; + } #ifdef F_WRLCK if (logg_lock) { @@ -362,11 +385,16 @@ int logg(loglevel_t loglevel, const char *str, ...) else #endif { + char errbuf[128]; + cli_strerror(errno, errbuf, sizeof(errbuf)); + printf("ERROR: Failed to lock the log file %s: %s\n", logg_file, errbuf); + #ifdef CL_THREAD_SAFE pthread_mutex_unlock(&logg_mutex); #endif - printf("ERROR: %s is locked by another process\n", logg_file); - if (len > sizeof(buffer)) + fclose(logg_fp); + logg_fp = NULL; + if (abuffer) free(abuffer); return -1; } @@ -441,8 +469,9 @@ int logg(loglevel_t loglevel, const char *str, ...) pthread_mutex_unlock(&logg_mutex); #endif - if (len > sizeof(buffer)) + if (abuffer) free(abuffer); + return 0; } diff --git a/libclamav/bytecode_api.h b/libclamav/bytecode_api.h index 63b730f493..fe254fa975 100644 --- a/libclamav/bytecode_api.h +++ b/libclamav/bytecode_api.h @@ -157,6 +157,7 @@ enum FunctionalityLevels { FUNC_LEVEL_0103_9 = 130, /**< LibClamAV release 0.103.9 */ FUNC_LEVEL_0103_10 = 131, /**< LibClamAV release 0.103.10 */ FUNC_LEVEL_0103_11 = 132, /**< LibClamAV release 0.103.11 */ + FUNC_LEVEL_0103_12 = 133, /**< LibClamAV release 0.103.12 */ FUNC_LEVEL_0104 = 140, /**< LibClamAV release 0.104.0 */ FUNC_LEVEL_0104_1 = 141, /**< LibClamAV release 0.104.1 */ @@ -175,6 +176,7 @@ enum FunctionalityLevels { FUNC_LEVEL_1_0_4 = 164, /**< LibClamAV release 1.0.4 */ FUNC_LEVEL_1_0_5 = 165, /**< LibClamAV release 1.0.5 */ FUNC_LEVEL_1_0_6 = 166, /**< LibClamAV release 1.0.6 */ + FUNC_LEVEL_1_0_7 = 167, /**< LibClamAV release 1.0.7 */ FUNC_LEVEL_1_1 = 180, /**< LibClamAV release 1.1.0 */ FUNC_LEVEL_1_1_1 = 181, /**< LibClamAV release 1.1.1 */ @@ -188,8 +190,10 @@ enum FunctionalityLevels { FUNC_LEVEL_1_3 = 200, /**< LibClamAV release 1.3.0 */ FUNC_LEVEL_1_3_1 = 201, /**< LibClamAV release 1.3.1 */ + FUNC_LEVEL_1_3_2 = 202, /**< LibClamAV release 1.3.2 */ - FUNC_LEVEL_1_4 = 210, /**< LibClamAV release 1.4.0 */ + FUNC_LEVEL_1_4 = 210, /**< LibClamAV release 1.4.0 */ + FUNC_LEVEL_1_4_1 = 211, /**< LibClamAV release 1.4.1 */ }; /** diff --git a/libclamav/others.h b/libclamav/others.h index 8cebf78d35..e967354b26 100644 --- a/libclamav/others.h +++ b/libclamav/others.h @@ -69,7 +69,7 @@ * in re-enabling affected modules. */ -#define CL_FLEVEL 210 +#define CL_FLEVEL 211 #define CL_FLEVEL_DCONF CL_FLEVEL #define CL_FLEVEL_SIGTOOL CL_FLEVEL diff --git a/libclamav/pdf.c b/libclamav/pdf.c index a5dd159e8a..6b2c7d2ff8 100644 --- a/libclamav/pdf.c +++ b/libclamav/pdf.c @@ -1003,8 +1003,26 @@ static size_t find_length(struct pdf_struct *pdf, struct pdf_obj *obj, const cha return 0; } - indirect_obj_start = pdf->map + obj->start; - bytes_remaining = pdf->size - obj->start; + if (NULL == obj->objstm) { + indirect_obj_start = (const char *)(obj->start + pdf->map); + + if (!CLI_ISCONTAINED(pdf->map, pdf->size, indirect_obj_start, obj->size)) { + cli_dbgmsg("find_length: indirect object found, but not contained in PDF\n"); + return 0; + } + + bytes_remaining = pdf->size - obj->start; + + } else { + indirect_obj_start = (const char *)(obj->start + obj->objstm->streambuf); + + if (!CLI_ISCONTAINED(obj->objstm->streambuf, obj->objstm->streambuf_len, indirect_obj_start, obj->size)) { + cli_dbgmsg("find_length: indirect object found, but not contained in PDF streambuf\n"); + return 0; + } + + bytes_remaining = obj->objstm->streambuf_len - obj->start; + } /* Ok so we found the indirect object, lets read the value. */ index = pdf_nextobject(indirect_obj_start, bytes_remaining); @@ -3260,15 +3278,30 @@ void pdf_handle_enc(struct pdf_struct *pdf) obj = find_obj(pdf, pdf->objs[0], pdf->enc_objid); if (!obj) { - cli_dbgmsg("pdf_handle_enc: can't find encrypted object %d %d\n", pdf->enc_objid >> 8, pdf->enc_objid & 0xff); - noisy_warnmsg("pdf_handle_enc: can't find encrypted object %d %d\n", pdf->enc_objid >> 8, pdf->enc_objid & 0xff); + cli_dbgmsg("pdf_handle_enc: can't find encryption object %d %d\n", pdf->enc_objid >> 8, pdf->enc_objid & 0xff); + noisy_warnmsg("pdf_handle_enc: can't find encryption object %d %d\n", pdf->enc_objid >> 8, pdf->enc_objid & 0xff); return; } len = obj->size; - q = (obj->objstm) ? (const char *)(obj->start + obj->objstm->streambuf) - : (const char *)(obj->start + pdf->map); + if (NULL == obj->objstm) { + q = (const char *)(obj->start + pdf->map); + + if (!CLI_ISCONTAINED(pdf->map, pdf->size, q, len)) { + cli_dbgmsg("pdf_handle_enc: encryption object found, but not contained in PDF\n"); + noisy_warnmsg("pdf_handle_enc: encryption object found, but not contained in PDF\n"); + return; + } + } else { + q = (const char *)(obj->start + obj->objstm->streambuf); + + if (!CLI_ISCONTAINED(obj->objstm->streambuf, obj->objstm->streambuf_len, q, len)) { + cli_dbgmsg("pdf_handle_enc: encryption object found, but not contained in PDF streambuf\n"); + noisy_warnmsg("pdf_handle_enc: encryption object found, but not contained in PDF streambuf\n"); + return; + } + } O = U = UE = StmF = StrF = EFF = NULL; diff --git a/libclamav/pdfng.c b/libclamav/pdfng.c index 575cece831..5c3b9a116f 100644 --- a/libclamav/pdfng.c +++ b/libclamav/pdfng.c @@ -518,6 +518,11 @@ char *pdf_parse_string(struct pdf_struct *pdf, struct pdf_obj *obj, const char * if (!(newobj)) return NULL; + if (!CLI_ISCONTAINED(pdf->map, pdf->size, newobj->start, newobj->size)) { + cli_dbgmsg("pdf_parse_string: object not contained in PDF\n"); + return NULL; + } + if (newobj == obj) return NULL; diff --git a/unit_tests/freshclam_test.py b/unit_tests/freshclam_test.py index bec400f6aa..78de30c028 100644 --- a/unit_tests/freshclam_test.py +++ b/unit_tests/freshclam_test.py @@ -10,14 +10,10 @@ from pathlib import Path import platform import shutil -import subprocess -import sys -import time import unittest from functools import partial from http.server import HTTPServer, BaseHTTPRequestHandler -import cgi import testcase