Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,7 @@ if(WIN32)
set(HAVE_MKSTEMP 1)
set(HAVE_POLL 1)
else()
check_symbol_exists(funlinkat "unistd.h" HAVE_FUNLINKAT)
check_symbol_exists(fseeko "stdio.h" HAVE_FSEEKO)
check_symbol_exists(getaddrinfo "netdb.h" HAVE_GETADDRINFO)
check_symbol_exists(getpagesize "unistd.h" HAVE_GETPAGESIZE)
Expand Down
3 changes: 3 additions & 0 deletions clamav-config.h.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@
/* use "Cache-Control: no-cache" in freshclam */
#cmakedefine FRESHCLAM_NO_CACHE 1

/* Have funlinkat */
#cmakedefine HAVE_FUNLINKAT 1

/* attrib aligned */
#cmakedefine HAVE_ATTRIB_ALIGNED 1

Expand Down
59 changes: 52 additions & 7 deletions common/actions.c
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,19 @@ static int action_unlinkat_nointr(int dirfd, const char *path, int flags)
return rc;
}

#if defined(__FreeBSD__) && defined(HAVE_FUNLINKAT)
static int action_funlinkat_nointr(int dirfd, const char *path, int fd, int flags)
{
int rc;

do {
rc = funlinkat(dirfd, path, fd, flags);
} while ((rc < 0) && (EINTR == errno));

return rc;
}
#endif

#ifndef _WIN32
static int action_fstatat_nointr(int dirfd, const char *path, STATBUF *st, int flags)
{
Expand Down Expand Up @@ -3313,12 +3326,16 @@ static int action_restore_captured_unlink_target(
* This approach mitigates the possibility that one of the directories
* in the path has been replaced with a malicious symlink.
*
* @param target A file to be deleted.
* @return 0 Unlink succeeded.
* @return -1 Unlink failed.
* @param target A file to be deleted.
* @param source_fd POSIX descriptor for the scanned source.
* @param expected_stat POSIX metadata for the scanned source.
* @param target_file_handle Windows handle for the scanned source.
* @param target_file_handle_can_delete Whether the Windows handle has delete access.
* @return 0 Unlink succeeded.
* @return -1 Unlink failed.
*/
#ifndef _WIN32
static int traverse_unlink(const char *target, const STATBUF *expected_stat)
static int traverse_unlink(const char *target, int source_fd, const STATBUF *expected_stat)
#else
static int traverse_unlink(
const char *target,
Expand Down Expand Up @@ -3347,6 +3364,10 @@ static int traverse_unlink(
goto done;
}

#if !defined(_WIN32) && (!defined(__FreeBSD__) || !defined(HAVE_FUNLINKAT))
UNUSEDPARAM(source_fd);
#endif

#ifndef _WIN32
/* On posix, we want a file descriptor for the directory */
if (0 != traverse_to(target, true, &target_directory_fd)) {
Expand Down Expand Up @@ -3382,6 +3403,30 @@ static int traverse_unlink(
goto done;
}
} else {
#if defined(__FreeBSD__) && defined(HAVE_FUNLINKAT)
if (source_fd >= 0) {
/*
* FreeBSD verifies atomically that target_basename still names
* source_fd before unlinking it. This avoids the private capture
* and no-replace restore required on other POSIX platforms.
*/
if (0 != action_funlinkat_nointr(target_directory_fd, target_basename, source_fd, 0)) {
int unlink_errno = errno;

if (EDEADLK == unlink_errno) {
logg(LOGG_INFO, "traverse_unlink: Refusing to unlink '%s' because the source changed after validation.\n", target);
errno = EAGAIN;
} else {
logg(LOGG_INFO, "traverse_unlink: Failed to unlink '%s' through its opened descriptor: %s\n", target, strerror(unlink_errno));
errno = unlink_errno;
}
goto done;
}

status = 0;
goto done;
}
#endif
if (0 != action_create_private_unlink_dir(
target_directory_fd,
private_directory_name,
Expand Down Expand Up @@ -3541,7 +3586,7 @@ static void action_move(const action_source_t *source)
notmoved++;
goto done;
}
if (0 != traverse_unlink(action_filename, &source_stat)) {
if (0 != traverse_unlink(action_filename, source->scan_fd, &source_stat)) {
int unlink_errno = errno;
if (show_action_path) {
logg(LOGG_ERROR, "Can't unlink '%s' (real path: '%s') after linking into quarantine: %s\n", filename, action_filename, strerror(unlink_errno));
Expand Down Expand Up @@ -3613,7 +3658,7 @@ static void action_move(const action_source_t *source)
goto done;
}
#ifndef _WIN32
if (0 != traverse_unlink(action_filename, &source_stat)) {
if (0 != traverse_unlink(action_filename, source->scan_fd, &source_stat)) {
if (show_action_path) {
logg(LOGG_ERROR, "Can't unlink '%s' (real path: '%s') after copy: %s\n", filename, action_filename, strerror(errno));
} else {
Expand Down Expand Up @@ -3742,7 +3787,7 @@ static void action_remove(const action_source_t *source)
#ifndef _WIN32
if ((false == source->has_stat) ||
!S_ISREG(source->statbuf.st_mode) ||
(0 != traverse_unlink(action_filename, &source->statbuf))) {
(0 != traverse_unlink(action_filename, source->scan_fd, &source->statbuf))) {
#else
if (0 != traverse_unlink(
action_filename,
Expand Down
2 changes: 1 addition & 1 deletion libclamav/others.h
Original file line number Diff line number Diff line change
Expand Up @@ -1231,7 +1231,7 @@ cl_error_t cli_get_filepath_from_handle(HANDLE hFile, char **filepath);
* @brief Attempt to get a filename from an open file descriptor.
*
* Caller is responsible for free'ing the filename.
* Should work on Linux, macOS, Windows.
* Should work on Linux, FreeBSD, macOS, Windows.
*
* @param desc File descriptor
* @param[out] filepath Will be set to file path if found, or NULL.
Expand Down
28 changes: 28 additions & 0 deletions libclamav/others_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
#endif
#include <time.h>
#include <fcntl.h>
#ifdef __FreeBSD__
#include <sys/user.h>
#endif
#ifdef HAVE_PWD_H
#include <pwd.h>
#endif
Expand Down Expand Up @@ -1493,6 +1496,31 @@ cl_error_t cli_get_filepath_from_filedesc(int desc, char **filepath)
goto done;
}

#elif defined(__FreeBSD__)
Comment thread
val-ms marked this conversation as resolved.
Outdated

struct kinfo_file file_info;

if (NULL == filepath) {
cli_errmsg("cli_get_filepath_from_filedesc: Invalid args.\n");
goto done;
}

memset(&file_info, 0, sizeof(file_info));
file_info.kf_structsize = sizeof(file_info);

if ((fcntl(desc, F_KINFO, &file_info) < 0) || ('\0' == file_info.kf_path[0])) {
cli_dbgmsg("cli_get_filepath_from_filedesc: Failed to resolve filename for descriptor %d\n", desc);
status = CL_EOPEN;
goto done;
}

evaluated_filepath = CLI_STRNDUP(file_info.kf_path, CLI_STRNLEN(file_info.kf_path, sizeof(file_info.kf_path)));
Comment thread
val-ms marked this conversation as resolved.
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 C_DARWIN

char fname[PATH_MAX];
Expand Down
Loading