Bläddra i källkod

Merge topic 'fix-archive-path-traversal'

03f19aa4ea cmSystemTools: Fix path traversal vulnerability in archive extraction
be2ac223b0 cmSystemTools: Factor out libarchive extraction flags
9c408b844b Tests/RunCMake/CMP0135: Avoid paths with .. components in test archive
6015ef807d cmake -E tar: Improve error message formatting

Acked-by: Kitware Robot <[email protected]>
Merge-request: !11564
Brad King 3 dagar sedan
förälder
incheckning
f71894a17d
31 ändrade filer med 345 tillägg och 31 borttagningar
  1. 4 0
      Help/command/file.rst
  2. 4 0
      Help/manual/cmake.1.rst
  3. 7 0
      Help/release/dev/archive-path-traversal.rst
  4. 3 2
      Source/cmArchiveWrite.cxx
  5. 1 1
      Source/cmFileCommand.cxx
  6. 15 10
      Source/cmSystemTools.cxx
  7. 3 2
      Source/cmcmd.cxx
  8. 2 1
      Tests/RunCMake/CMP0135/CMP0135-Common.cmake
  9. 2 1
      Tests/RunCMake/CMakeLists.txt
  10. 2 1
      Tests/RunCMake/CommandLineTar/7zip-zstd-unsupported-stderr.txt
  11. 6 0
      Tests/RunCMake/CommandLineTar/RunCMakeTest.cmake
  12. 7 2
      Tests/RunCMake/CommandLineTar/bad-file-stderr.txt
  13. 7 2
      Tests/RunCMake/CommandLineTar/bad-from4-stderr.txt
  14. 7 2
      Tests/RunCMake/CommandLineTar/bad-from5-stderr.txt
  15. 2 1
      Tests/RunCMake/CommandLineTar/bad-mtime1-stderr.txt
  16. 7 2
      Tests/RunCMake/CommandLineTar/end-opt1-stderr.txt
  17. 7 0
      Tests/RunCMake/CommandLineTar/path-absolute-stderr.txt
  18. 57 0
      Tests/RunCMake/CommandLineTar/path-absolute.cmake
  19. 6 0
      Tests/RunCMake/CommandLineTar/path-traversal-stderr.txt
  20. 57 0
      Tests/RunCMake/CommandLineTar/path-traversal.cmake
  21. 2 1
      Tests/RunCMake/CommandLineTar/zip-bz2-unsupported-stderr.txt
  22. 2 1
      Tests/RunCMake/CommandLineTar/zip-lzma-unsupported-stderr.txt
  23. 2 1
      Tests/RunCMake/CommandLineTar/zip-xz-unsupported-stderr.txt
  24. 2 1
      Tests/RunCMake/CommandLineTar/zip-zstd-unsupported-stderr.txt
  25. 6 0
      Tests/RunCMake/File_Archive/RunCMakeTest.cmake
  26. 1 0
      Tests/RunCMake/File_Archive/path-absolute-result.txt
  27. 10 0
      Tests/RunCMake/File_Archive/path-absolute-stderr.txt
  28. 52 0
      Tests/RunCMake/File_Archive/path-absolute.cmake
  29. 1 0
      Tests/RunCMake/File_Archive/path-traversal-result.txt
  30. 9 0
      Tests/RunCMake/File_Archive/path-traversal-stderr.txt
  31. 52 0
      Tests/RunCMake/File_Archive/path-traversal.cmake

+ 4 - 0
Help/command/file.rst

@@ -1065,6 +1065,10 @@ Archiving
   ``VERBOSE``
   ``VERBOSE``
     Enable verbose output from the extraction operation.
     Enable verbose output from the extraction operation.
 
 
+  .. versionchanged:: 4.3
+    Archive entries containing path traversal sequences (``..``), or
+    absolute paths, are rejected for security.
+
   .. note::
   .. note::
     The working directory for this subcommand is the ``DESTINATION`` directory
     The working directory for this subcommand is the ``DESTINATION`` directory
     (provided or computed) except when ``LIST_ONLY`` is specified. Therefore,
     (provided or computed) except when ``LIST_ONLY`` is specified. Therefore,

+ 4 - 0
Help/manual/cmake.1.rst

@@ -1454,6 +1454,10 @@ Available commands are:
       When extracting selected files or directories, you must provide their exact
       When extracting selected files or directories, you must provide their exact
       names including the path, as printed by list (``-t``).
       names including the path, as printed by list (``-t``).
 
 
+    .. versionchanged:: 4.3
+      Archive entries containing path traversal sequences (``..``), or
+      absolute paths, are rejected for security.
+
   .. option:: t
   .. option:: t
 
 
     List archive contents.
     List archive contents.

+ 7 - 0
Help/release/dev/archive-path-traversal.rst

@@ -0,0 +1,7 @@
+archive-path-traversal
+----------------------
+
+* The :manual:`cmake(1)` :option:`-E tar <cmake-E tar>` command-line tool,
+  and the :command:`file(ARCHIVE_EXTRACT)` command, now reject archive
+  entries whose paths are absolute or contain ``..`` path traversal
+  components.

+ 3 - 2
Source/cmArchiveWrite.cxx

@@ -448,8 +448,9 @@ bool cmArchiveWrite::AddFile(char const* file, size_t skip, char const* prefix)
   cm_archive_entry_copy_pathname(e, dest);
   cm_archive_entry_copy_pathname(e, dest);
   if (archive_read_disk_entry_from_file(this->Disk, e, -1, nullptr) !=
   if (archive_read_disk_entry_from_file(this->Disk, e, -1, nullptr) !=
       ARCHIVE_OK) {
       ARCHIVE_OK) {
-    this->Error = cmStrCat("Unable to read from file '", file,
-                           "': ", cm_archive_error_string(this->Disk));
+    this->Error =
+      cmStrCat("Unable to read from file:\n  ", file, "\nbecause:\n  ",
+               cm_archive_error_string(this->Disk));
     return false;
     return false;
   }
   }
   if (!this->MTime.empty()) {
   if (!this->MTime.empty()) {

+ 1 - 1
Source/cmFileCommand.cxx

@@ -3901,7 +3901,7 @@ bool HandleArchiveExtractCommand(std::vector<std::string> const& args,
           parsedArgs.Touch ? cmSystemTools::cmTarExtractTimestamps::No
           parsedArgs.Touch ? cmSystemTools::cmTarExtractTimestamps::No
                            : cmSystemTools::cmTarExtractTimestamps::Yes,
                            : cmSystemTools::cmTarExtractTimestamps::Yes,
           parsedArgs.Verbose)) {
           parsedArgs.Verbose)) {
-      status.SetError(cmStrCat("failed to extract: ", inFile));
+      status.SetError(cmStrCat("failed to extract:\n  ", inFile));
       cmSystemTools::SetFatalErrorOccurred();
       cmSystemTools::SetFatalErrorOccurred();
       return false;
       return false;
     }
     }

+ 15 - 10
Source/cmSystemTools.cxx

@@ -2636,6 +2636,19 @@ bool extract_tar(std::string const& outFileName,
   static_cast<void>(localeRAII);
   static_cast<void>(localeRAII);
   struct archive* a = archive_read_new();
   struct archive* a = archive_read_new();
   struct archive* ext = archive_write_disk_new();
   struct archive* ext = archive_write_disk_new();
+  if (extract) {
+    int flags = ARCHIVE_EXTRACT_SECURE_NODOTDOT |
+      ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS | ARCHIVE_EXTRACT_SECURE_SYMLINKS;
+    if (extractTimestamps == cmSystemTools::cmTarExtractTimestamps::Yes) {
+      flags |= ARCHIVE_EXTRACT_TIME;
+    }
+    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;
+    }
+  }
   archive_read_support_filter_all(a);
   archive_read_support_filter_all(a);
   archive_read_support_format_all(a);
   archive_read_support_format_all(a);
   struct archive_entry* entry;
   struct archive_entry* entry;
@@ -2687,14 +2700,6 @@ bool extract_tar(std::string const& outFileName,
       cmSystemTools::Stdout(cmStrCat(cm_archive_entry_pathname(entry), '\n'));
       cmSystemTools::Stdout(cmStrCat(cm_archive_entry_pathname(entry), '\n'));
     }
     }
     if (extract) {
     if (extract) {
-      if (extractTimestamps == cmSystemTools::cmTarExtractTimestamps::Yes) {
-        r = archive_write_disk_set_options(ext, ARCHIVE_EXTRACT_TIME);
-        if (r != ARCHIVE_OK) {
-          ArchiveError("Problem with archive_write_disk_set_options(): ", ext);
-          break;
-        }
-      }
-
       r = archive_write_header(ext, entry);
       r = archive_write_header(ext, entry);
       if (r == ARCHIVE_OK) {
       if (r == ARCHIVE_OK) {
         if (!copy_data(a, ext)) {
         if (!copy_data(a, ext)) {
@@ -2715,8 +2720,8 @@ bool extract_tar(std::string const& outFileName,
 #  endif
 #  endif
       else {
       else {
         ArchiveError("Problem with archive_write_header(): ", ext);
         ArchiveError("Problem with archive_write_header(): ", ext);
-        cmSystemTools::Error("Current file: " +
-                             cm_archive_entry_pathname(entry));
+        cmSystemTools::Error(
+          cmStrCat("Current file:\n  ", cm_archive_entry_pathname(entry)));
         break;
         break;
       }
       }
     }
     }

+ 3 - 2
Source/cmcmd.cxx

@@ -1790,13 +1790,14 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args,
         if (!cmSystemTools::CreateTar(outFile, files, {}, compress, verbose,
         if (!cmSystemTools::CreateTar(outFile, files, {}, compress, verbose,
                                       mtime, format, compressionLevel,
                                       mtime, format, compressionLevel,
                                       numThreads)) {
                                       numThreads)) {
-          cmSystemTools::Error("Problem creating tar: " + outFile);
+          cmSystemTools::Error(cmStrCat("Problem creating tar:\n  ", outFile));
           return 1;
           return 1;
         }
         }
       } else if (action == cmSystemTools::TarActionExtract) {
       } else if (action == cmSystemTools::TarActionExtract) {
         if (!cmSystemTools::ExtractTar(outFile, files, extractTimestamps,
         if (!cmSystemTools::ExtractTar(outFile, files, extractTimestamps,
                                        verbose)) {
                                        verbose)) {
-          cmSystemTools::Error("Problem extracting tar: " + outFile);
+          cmSystemTools::Error(
+            cmStrCat("Problem extracting tar:\n  ", outFile));
           return 1;
           return 1;
         }
         }
 #ifdef _WIN32
 #ifdef _WIN32

+ 2 - 1
Tests/RunCMake/CMP0135/CMP0135-Common.cmake

@@ -25,7 +25,8 @@ set(stamp_dir "${CMAKE_CURRENT_BINARY_DIR}/stamps-fc")
 set(archive_file ${CMAKE_CURRENT_BINARY_DIR}/test_archive.7z)
 set(archive_file ${CMAKE_CURRENT_BINARY_DIR}/test_archive.7z)
 file(ARCHIVE_CREATE
 file(ARCHIVE_CREATE
   OUTPUT ${archive_file}
   OUTPUT ${archive_file}
-  PATHS ${CMAKE_CURRENT_LIST_DIR}
+  PATHS "${CMAKE_CURRENT_LIST_FILE}"
+  WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}"
   FORMAT 7zip
   FORMAT 7zip
 )
 )
 include(FetchContent)
 include(FetchContent)

+ 2 - 1
Tests/RunCMake/CMakeLists.txt

@@ -979,7 +979,7 @@ endif()
 
 
 add_RunCMake_test(LinkLibrariesProcessing)
 add_RunCMake_test(LinkLibrariesProcessing)
 add_RunCMake_test(LinkLibrariesStrategy)
 add_RunCMake_test(LinkLibrariesStrategy)
-add_RunCMake_test(File_Archive)
+add_RunCMake_test(File_Archive -DPython_EXECUTABLE=${Python_EXECUTABLE})
 add_RunCMake_test(File_Configure)
 add_RunCMake_test(File_Configure)
 add_RunCMake_test(File_Generate)
 add_RunCMake_test(File_Generate)
 add_RunCMake_test(ExportWithoutLanguage)
 add_RunCMake_test(ExportWithoutLanguage)
@@ -1075,6 +1075,7 @@ add_RunCMake_test(CommandLine -DLLVM_RC=$<TARGET_FILE:pseudo_llvm-rc> -DCMAKE_SY
 if(CMake_TEST_LibArchive_VERSION)
 if(CMake_TEST_LibArchive_VERSION)
   list(APPEND CommandLineTar_ARGS -DCMake_TEST_LibArchive_VERSION=${CMake_TEST_LibArchive_VERSION})
   list(APPEND CommandLineTar_ARGS -DCMake_TEST_LibArchive_VERSION=${CMake_TEST_LibArchive_VERSION})
 endif()
 endif()
+list(APPEND CommandLineTar_ARGS -DPython_EXECUTABLE=${Python_EXECUTABLE})
 add_RunCMake_test(CommandLineTar)
 add_RunCMake_test(CommandLineTar)
 
 
 if(CMAKE_PLATFORM_NO_VERSIONED_SONAME OR (NOT CMAKE_SHARED_LIBRARY_SONAME_FLAG AND NOT CMAKE_SHARED_LIBRARY_SONAME_C_FLAG))
 if(CMAKE_PLATFORM_NO_VERSIONED_SONAME OR (NOT CMAKE_SHARED_LIBRARY_SONAME_FLAG AND NOT CMAKE_SHARED_LIBRARY_SONAME_C_FLAG))

+ 2 - 1
Tests/RunCMake/CommandLineTar/7zip-zstd-unsupported-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Zstd is not supported for 7zip. Please, build CMake with libarchive 3.8.0 or newer if you want to use it.
 ^CMake Error: Zstd is not supported for 7zip. Please, build CMake with libarchive 3.8.0 or newer if you want to use it.
-CMake Error: Problem creating tar: bad.7z$
+CMake Error: Problem creating tar:
+  bad\.7z$

+ 6 - 0
Tests/RunCMake/CommandLineTar/RunCMakeTest.cmake

@@ -127,3 +127,9 @@ run_cmake(set-mtime)
 
 
 # Use the --touch option to avoid extracting the mtime
 # Use the --touch option to avoid extracting the mtime
 run_cmake(touch-mtime)
 run_cmake(touch-mtime)
+
+# Security: Test path traversal protection
+if(Python_EXECUTABLE)
+  run_cmake_script(path-absolute -DPython_EXECUTABLE=${Python_EXECUTABLE})
+  run_cmake_script(path-traversal -DPython_EXECUTABLE=${Python_EXECUTABLE})
+endif()

+ 7 - 2
Tests/RunCMake/CommandLineTar/bad-file-stderr.txt

@@ -1,2 +1,7 @@
-^CMake Error: Unable to read from file 'badfile\.txt': .*
-CMake Error: Problem creating tar: bad\.tar$
+^CMake Error: Unable to read from file:
+  badfile\.txt
+because:
+  [^
+]*
+CMake Error: Problem creating tar:
+  bad\.tar$

+ 7 - 2
Tests/RunCMake/CommandLineTar/bad-from4-stderr.txt

@@ -1,2 +1,7 @@
-^CMake Error: Unable to read from file 'does-not-exist':.*
-CMake Error: Problem creating tar: bad\.tar$
+^CMake Error: Unable to read from file:
+  does-not-exist
+because:
+  [^
+]*
+CMake Error: Problem creating tar:
+  bad\.tar$

+ 7 - 2
Tests/RunCMake/CommandLineTar/bad-from5-stderr.txt

@@ -1,2 +1,7 @@
-^CMake Error: Unable to read from file 'does-not-exist':.*
-CMake Error: Problem creating tar: bad\.tar$
+^CMake Error: Unable to read from file:
+  does-not-exist
+because:
+  [^
+]*
+CMake Error: Problem creating tar:
+  bad\.tar$

+ 2 - 1
Tests/RunCMake/CommandLineTar/bad-mtime1-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: unable to parse mtime 'bad'
 ^CMake Error: unable to parse mtime 'bad'
-CMake Error: Problem creating tar: bad\.tar$
+CMake Error: Problem creating tar:
+  bad\.tar$

+ 7 - 2
Tests/RunCMake/CommandLineTar/end-opt1-stderr.txt

@@ -1,2 +1,7 @@
-^CMake Error: Unable to read from file '--bad':.*
-CMake Error: Problem creating tar: bad\.tar$
+^CMake Error: Unable to read from file:
+  --bad
+because:
+  [^
+]*
+CMake Error: Problem creating tar:
+  bad\.tar$

+ 7 - 0
Tests/RunCMake/CommandLineTar/path-absolute-stderr.txt

@@ -0,0 +1,7 @@
+^CMake Error: Problem with archive_write_header\(\): Path is absolute
+CMake Error: Current file:
+  [^
+]*/Tests/RunCMake/CommandLineTar/path-absolute-build/SHOULD_NOT_EXIST_ABS\.txt
+CMake Error: Problem extracting tar:
+  [^
+]*/Tests/RunCMake/CommandLineTar/path-absolute-build/malicious_abs\.tar$

+ 57 - 0
Tests/RunCMake/CommandLineTar/path-absolute.cmake

@@ -0,0 +1,57 @@
+# Test that absolute path attacks are blocked during extraction
+
+set(EXTRACT_DIR "${CMAKE_CURRENT_BINARY_DIR}/extract_dir_abs")
+# Use an absolute path within the build tree (but outside EXTRACT_DIR)
+set(MALICIOUS_FILE "${CMAKE_CURRENT_BINARY_DIR}/SHOULD_NOT_EXIST_ABS.txt")
+
+# Clean up
+file(REMOVE_RECURSE "${EXTRACT_DIR}")
+file(REMOVE "${MALICIOUS_FILE}")
+file(MAKE_DIRECTORY "${EXTRACT_DIR}")
+
+# Create a malicious tar archive using Python
+# The archive contains a file with an absolute path
+set(MALICIOUS_TAR "${CMAKE_CURRENT_BINARY_DIR}/malicious_abs.tar")
+file(REMOVE "${MALICIOUS_TAR}")
+
+execute_process(
+  COMMAND "${Python_EXECUTABLE}" -c [==[
+import sys
+import tarfile
+import io
+
+# Create a tar archive in memory
+tar_data = io.BytesIO()
+with tarfile.open(fileobj=tar_data, mode='w') as tar:
+    # Add a file with absolute path
+    data = b'malicious content'
+    info = tarfile.TarInfo(name=sys.argv[2])
+    info.size = len(data)
+    tar.addfile(info, io.BytesIO(data))
+
+# Write to file
+with open(sys.argv[1], 'wb') as f:
+    f.write(tar_data.getvalue())
+]==] "${MALICIOUS_TAR}" "${MALICIOUS_FILE}"
+  RESULT_VARIABLE result
+)
+
+if(NOT result EQUAL 0)
+  message(FATAL_ERROR "Failed to create malicious tar archive")
+endif()
+
+# Try to extract the malicious archive
+execute_process(
+  COMMAND "${CMAKE_COMMAND}" -E tar xf "${MALICIOUS_TAR}"
+  WORKING_DIRECTORY "${EXTRACT_DIR}"
+  RESULT_VARIABLE extract_result
+)
+
+# The file should not exist at the absolute path
+if(EXISTS "${MALICIOUS_FILE}")
+  message(FATAL_ERROR "PATH TRAVERSAL VULNERABILITY: File was created outside extraction directory!")
+endif()
+
+if(extract_result EQUAL 0)
+  message(FATAL_ERROR "Extraction of malicious path did not fail!")
+endif()

+ 6 - 0
Tests/RunCMake/CommandLineTar/path-traversal-stderr.txt

@@ -0,0 +1,6 @@
+^CMake Error: Problem with archive_write_header\(\): Path contains '\.\.'
+CMake Error: Current file:
+  \.\./SHOULD_NOT_EXIST.txt
+CMake Error: Problem extracting tar:
+  [^
+]*/Tests/RunCMake/CommandLineTar/path-traversal-build/malicious\.tar$

+ 57 - 0
Tests/RunCMake/CommandLineTar/path-traversal.cmake

@@ -0,0 +1,57 @@
+# Test that path traversal attacks are blocked during extraction
+
+set(EXTRACT_DIR "${CMAKE_CURRENT_BINARY_DIR}/extract_dir")
+set(PARENT_DIR "${CMAKE_CURRENT_BINARY_DIR}")
+set(MALICIOUS_FILE "${PARENT_DIR}/SHOULD_NOT_EXIST.txt")
+
+# Clean up
+file(REMOVE_RECURSE "${EXTRACT_DIR}")
+file(REMOVE "${MALICIOUS_FILE}")
+file(MAKE_DIRECTORY "${EXTRACT_DIR}")
+
+# Create a malicious tar archive using Python
+# The archive contains a file with path "../SHOULD_NOT_EXIST.txt"
+set(MALICIOUS_TAR "${CMAKE_CURRENT_BINARY_DIR}/malicious.tar")
+file(REMOVE "${MALICIOUS_TAR}")
+
+execute_process(
+  COMMAND "${Python_EXECUTABLE}" -c [==[
+import sys
+import tarfile
+import io
+
+# Create a tar archive in memory
+tar_data = io.BytesIO()
+with tarfile.open(fileobj=tar_data, mode='w') as tar:
+    # Add a file with path traversal
+    data = b'malicious content'
+    info = tarfile.TarInfo(name='../SHOULD_NOT_EXIST.txt')
+    info.size = len(data)
+    tar.addfile(info, io.BytesIO(data))
+
+# Write to file
+with open(sys.argv[1], 'wb') as f:
+    f.write(tar_data.getvalue())
+]==] "${MALICIOUS_TAR}"
+  RESULT_VARIABLE result
+)
+
+if(NOT result EQUAL 0)
+  message(FATAL_ERROR "Failed to create malicious tar archive")
+endif()
+
+# Try to extract the malicious archive
+execute_process(
+  COMMAND "${CMAKE_COMMAND}" -E tar xf "${MALICIOUS_TAR}"
+  WORKING_DIRECTORY "${EXTRACT_DIR}"
+  RESULT_VARIABLE extract_result
+)
+
+# The extraction should fail or the file should not exist outside extract dir
+if(EXISTS "${MALICIOUS_FILE}")
+  message(FATAL_ERROR "PATH TRAVERSAL VULNERABILITY: File was created outside extraction directory!")
+endif()
+
+if(extract_result EQUAL 0)
+  message(FATAL_ERROR "Extraction of malicious path did not fail!")
+endif()

+ 2 - 1
Tests/RunCMake/CommandLineTar/zip-bz2-unsupported-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: BZip2 is not supported for zip. Please, build CMake with libarchive 3.8.0 or newer if you want to use it.
 ^CMake Error: BZip2 is not supported for zip. Please, build CMake with libarchive 3.8.0 or newer if you want to use it.
-CMake Error: Problem creating tar: bad.zip$
+CMake Error: Problem creating tar:
+  bad\.zip$

+ 2 - 1
Tests/RunCMake/CommandLineTar/zip-lzma-unsupported-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: LZMA is not supported for zip. Please, build CMake with libarchive 3.8.0 or newer if you want to use it.
 ^CMake Error: LZMA is not supported for zip. Please, build CMake with libarchive 3.8.0 or newer if you want to use it.
-CMake Error: Problem creating tar: bad.zip$
+CMake Error: Problem creating tar:
+  bad\.zip$

+ 2 - 1
Tests/RunCMake/CommandLineTar/zip-xz-unsupported-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: LZMA2 \(XZ\) is not supported for zip. Please, build CMake with libarchive 3.8.0 or newer if you want to use it.
 ^CMake Error: LZMA2 \(XZ\) is not supported for zip. Please, build CMake with libarchive 3.8.0 or newer if you want to use it.
-CMake Error: Problem creating tar: bad.zip$
+CMake Error: Problem creating tar:
+  bad\.zip$

+ 2 - 1
Tests/RunCMake/CommandLineTar/zip-zstd-unsupported-stderr.txt

@@ -1,2 +1,3 @@
 ^CMake Error: Zstd is not supported for zip. Please, build CMake with libarchive 3.8.0 or newer if you want to use it.
 ^CMake Error: Zstd is not supported for zip. Please, build CMake with libarchive 3.8.0 or newer if you want to use it.
-CMake Error: Problem creating tar: bad.zip$
+CMake Error: Problem creating tar:
+  bad\.zip$

+ 6 - 0
Tests/RunCMake/File_Archive/RunCMakeTest.cmake

@@ -52,3 +52,9 @@ run_cmake(pax-xz-compression-level)
 run_cmake(pax-zstd-compression-level)
 run_cmake(pax-zstd-compression-level)
 run_cmake(paxr-bz2-compression-level)
 run_cmake(paxr-bz2-compression-level)
 run_cmake(zip-deflate-compression-level)
 run_cmake(zip-deflate-compression-level)
+
+# Security: Test path traversal protection
+if(Python_EXECUTABLE)
+  run_cmake_script(path-absolute -DPython_EXECUTABLE=${Python_EXECUTABLE})
+  run_cmake_script(path-traversal -DPython_EXECUTABLE=${Python_EXECUTABLE})
+endif()

+ 1 - 0
Tests/RunCMake/File_Archive/path-absolute-result.txt

@@ -0,0 +1 @@
+1

+ 10 - 0
Tests/RunCMake/File_Archive/path-absolute-stderr.txt

@@ -0,0 +1,10 @@
+^CMake Error: Problem with archive_write_header\(\): Path is absolute
+CMake Error: Current file:
+  [^
+]*/Tests/RunCMake/File_Archive/path-absolute-build/SHOULD_NOT_EXIST_ABS\.txt
+CMake Error at [^
+]*/Tests/RunCMake/File_Archive/path-absolute\.cmake:[0-9]+ \(file\):
+  file failed to extract:
+
+    [^
+]*/Tests/RunCMake/File_Archive/path-absolute-build/malicious_abs\.tar$

+ 52 - 0
Tests/RunCMake/File_Archive/path-absolute.cmake

@@ -0,0 +1,52 @@
+# Test that path traversal attacks are blocked during file(ARCHIVE_EXTRACT)
+
+set(EXTRACT_DIR "${CMAKE_CURRENT_BINARY_DIR}/extract_dir_abs")
+# Use an absolute path within the build tree (but outside EXTRACT_DIR)
+set(MALICIOUS_FILE "${CMAKE_CURRENT_BINARY_DIR}/SHOULD_NOT_EXIST_ABS.txt")
+
+# Clean up
+file(REMOVE_RECURSE "${EXTRACT_DIR}")
+file(REMOVE "${MALICIOUS_FILE}")
+file(MAKE_DIRECTORY "${EXTRACT_DIR}")
+
+# Create a malicious tar archive using Python
+# The archive contains a file with an absolute path
+set(MALICIOUS_TAR "${CMAKE_CURRENT_BINARY_DIR}/malicious_abs.tar")
+file(REMOVE "${MALICIOUS_TAR}")
+
+execute_process(
+  COMMAND "${Python_EXECUTABLE}" -c [==[
+import sys
+import tarfile
+import io
+
+# Create a tar archive in memory
+tar_data = io.BytesIO()
+with tarfile.open(fileobj=tar_data, mode='w') as tar:
+    # Add a file with absolute path
+    data = b'malicious content'
+    info = tarfile.TarInfo(name=sys.argv[2])
+    info.size = len(data)
+    tar.addfile(info, io.BytesIO(data))
+
+# Write to file
+with open(sys.argv[1], 'wb') as f:
+    f.write(tar_data.getvalue())
+]==] "${MALICIOUS_TAR}" "${MALICIOUS_FILE}"
+  RESULT_VARIABLE result
+)
+
+if(NOT result EQUAL 0)
+  message(FATAL_ERROR "Failed to create malicious tar archive")
+endif()
+
+# Try to extract the malicious archive using file(ARCHIVE_EXTRACT)
+file(ARCHIVE_EXTRACT
+  INPUT "${MALICIOUS_TAR}"
+  DESTINATION "${EXTRACT_DIR}"
+)
+
+# The file should not exist outside the extraction directory
+if(EXISTS "${MALICIOUS_FILE}")
+  message(FATAL_ERROR "PATH TRAVERSAL VULNERABILITY: File was created outside extraction directory!")
+endif()

+ 1 - 0
Tests/RunCMake/File_Archive/path-traversal-result.txt

@@ -0,0 +1 @@
+1

+ 9 - 0
Tests/RunCMake/File_Archive/path-traversal-stderr.txt

@@ -0,0 +1,9 @@
+^CMake Error: Problem with archive_write_header\(\): Path contains '\.\.'
+CMake Error: Current file:
+  \.\./SHOULD_NOT_EXIST\.txt
+CMake Error at [^
+]*/Tests/RunCMake/File_Archive/path-traversal\.cmake:[0-9]+ \(file\):
+  file failed to extract:
+
+    [^
+]*/Tests/RunCMake/File_Archive/path-traversal-build/malicious\.tar$

+ 52 - 0
Tests/RunCMake/File_Archive/path-traversal.cmake

@@ -0,0 +1,52 @@
+# Test that path traversal attacks are blocked during file(ARCHIVE_EXTRACT)
+
+set(EXTRACT_DIR "${CMAKE_CURRENT_BINARY_DIR}/extract_dir")
+set(PARENT_DIR "${CMAKE_CURRENT_BINARY_DIR}")
+set(MALICIOUS_FILE "${PARENT_DIR}/SHOULD_NOT_EXIST.txt")
+
+# Clean up
+file(REMOVE_RECURSE "${EXTRACT_DIR}")
+file(REMOVE "${MALICIOUS_FILE}")
+file(MAKE_DIRECTORY "${EXTRACT_DIR}")
+
+# Create a malicious tar archive using Python
+# The archive contains a file with path "../SHOULD_NOT_EXIST.txt"
+set(MALICIOUS_TAR "${CMAKE_CURRENT_BINARY_DIR}/malicious.tar")
+file(REMOVE "${MALICIOUS_TAR}")
+
+execute_process(
+  COMMAND "${Python_EXECUTABLE}" -c [==[
+import sys
+import tarfile
+import io
+
+# Create a tar archive in memory
+tar_data = io.BytesIO()
+with tarfile.open(fileobj=tar_data, mode='w') as tar:
+    # Add a file with path traversal
+    data = b'malicious content'
+    info = tarfile.TarInfo(name='../SHOULD_NOT_EXIST.txt')
+    info.size = len(data)
+    tar.addfile(info, io.BytesIO(data))
+
+# Write to file
+with open(sys.argv[1], 'wb') as f:
+    f.write(tar_data.getvalue())
+]==] "${MALICIOUS_TAR}"
+  RESULT_VARIABLE result
+)
+
+if(NOT result EQUAL 0)
+  message(FATAL_ERROR "Failed to create malicious tar archive")
+endif()
+
+# Try to extract the malicious archive using file(ARCHIVE_EXTRACT)
+file(ARCHIVE_EXTRACT
+  INPUT "${MALICIOUS_TAR}"
+  DESTINATION "${EXTRACT_DIR}"
+)
+
+# The file should not exist outside the extraction directory
+if(EXISTS "${MALICIOUS_FILE}")
+  message(FATAL_ERROR "PATH TRAVERSAL VULNERABILITY: File was created outside extraction directory!")
+endif()