file(ARCHIVE_EXTRACT): Fix resource leaks on error paths

extract_tar() allocates archive_read, archive_write_disk, and
archive_match handles, but several early-return error paths did not free
them. Most notably, a PATTERNS entry matching nothing leaked all three
handles on every call, which accumulates inside a long-running
configure.

Scope the three handles with std::unique_ptr custom deleters so every
return path releases them automatically.  std::unique_ptr does not
invoke the deleter for a null pointer, and the libarchive *_free
functions are themselves no-ops on null, so no explicit guarding is
required.  archive_read_free()/archive_write_free() implicitly close the
handle if needed, matching the previous behavior.

Fixes: #27872
This commit is contained in:
Daksh Mamodiya
2026-06-13 15:05:36 +02:00
committed by Brad King
parent cdd5dbbba5
commit c31361d089
5 changed files with 53 additions and 13 deletions

View File

@@ -2503,14 +2503,33 @@ bool copy_data(struct archive* ar, struct archive* aw)
# endif
}
struct ArchiveReadDeleter
{
void operator()(struct archive* a) const { archive_read_free(a); }
};
struct ArchiveWriteDeleter
{
void operator()(struct archive* a) const { archive_write_free(a); }
};
struct ArchiveMatchDeleter
{
void operator()(struct archive* a) const { archive_match_free(a); }
};
bool extract_tar(std::string const& arFileName,
std::vector<std::string> const& files,
std::string const& encoding, bool verbose,
cmSystemTools::cmTarExtractTimestamps extractTimestamps,
bool extract)
{
struct archive* a = archive_read_new();
struct archive* ext = archive_write_disk_new();
std::unique_ptr<struct archive, ArchiveReadDeleter> a_owner(
archive_read_new());
std::unique_ptr<struct archive, ArchiveWriteDeleter> ext_owner(
archive_write_disk_new());
struct archive* a = a_owner.get();
struct archive* ext = ext_owner.get();
if (extract) {
int flags =
ARCHIVE_EXTRACT_SECURE_NODOTDOT | ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS;
@@ -2519,8 +2538,6 @@ bool extract_tar(std::string const& arFileName,
}
if (archive_write_disk_set_options(ext, flags) != ARCHIVE_OK) {
ArchiveError("Problem with archive_write_disk_set_options(): ", ext);
archive_write_free(ext);
archive_read_free(a);
return false;
}
}
@@ -2537,7 +2554,9 @@ bool extract_tar(std::string const& arFileName,
}
struct archive_entry* entry;
struct archive* matching = archive_match_new();
std::unique_ptr<struct archive, ArchiveMatchDeleter> matching_owner(
archive_match_new());
struct archive* matching = matching_owner.get();
if (!matching) {
cmSystemTools::Error("Out of memory");
return false;
@@ -2554,10 +2573,6 @@ bool extract_tar(std::string const& arFileName,
int r = cm_archive_read_open_filename(a, arFileName.c_str(), 10240);
if (r) {
ArchiveError("Problem with archive_read_open_filename(): ", a);
archive_write_free(ext);
archive_read_close(a);
archive_read_free(a);
archive_match_free(matching);
return false;
}
for (;;) {
@@ -2633,10 +2648,6 @@ bool extract_tar(std::string const& arFileName,
return false;
}
}
archive_match_free(matching);
archive_write_free(ext);
archive_read_close(a);
archive_read_free(a);
return r == ARCHIVE_EOF || r == ARCHIVE_OK;
}
}

View File

@@ -70,6 +70,7 @@ run_cmake(zip-filtered)
run_cmake(create-missing-args)
run_cmake(extract-missing-args)
run_cmake(extract-missing-pattern)
run_cmake(unsupported-format)
run_cmake(zip-with-bad-compression)

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1 @@
^CMake Error: tar: no_such_entry: Not found in archive

View File

@@ -0,0 +1,26 @@
# A PATTERNS entry that matches no entry in the archive must fail with a clear
# "Not found in archive" diagnostic. This exercises the most user-reachable
# error path in extract_tar() (the one that previously leaked all three
# libarchive handles), so it guards that the error path still behaves correctly
# after the RAII cleanup refactor.
set(archive "${CMAKE_CURRENT_BINARY_DIR}/test.zip")
set(src "${CMAKE_CURRENT_BINARY_DIR}/src")
set(dest "${CMAKE_CURRENT_BINARY_DIR}/dest")
file(REMOVE "${archive}")
file(REMOVE_RECURSE "${src}" "${dest}")
file(MAKE_DIRECTORY "${src}")
file(WRITE "${src}/f1.txt" "content\n")
file(MAKE_DIRECTORY "${dest}")
file(ARCHIVE_CREATE
OUTPUT "${archive}"
FORMAT zip
PATHS "${src}")
# "no_such_entry" matches nothing in the archive, so extraction must fail.
file(ARCHIVE_EXTRACT
INPUT "${archive}"
DESTINATION "${dest}"
PATTERNS no_such_entry)