/src/CMake/Tests/Fuzzing/cmArchiveExtractFuzzer.cxx
Line | Count | Source |
1 | | /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
2 | | file LICENSE.rst or https://cmake.org/licensing for details. */ |
3 | | |
4 | | /* |
5 | | * Fuzzer for CMake's archive extraction (tar/zip) |
6 | | * |
7 | | * CMake extracts archives via cmSystemTools::ExtractTar. This is a critical |
8 | | * attack surface as malicious archives could contain path traversal sequences |
9 | | * (Zip Slip) or other exploits. |
10 | | * |
11 | | * Coverage targets: |
12 | | * - Archive format detection (tar, gzip, bzip2, xz, zip) |
13 | | * - Path handling during extraction |
14 | | * - Symlink handling |
15 | | * - Large file handling |
16 | | * - Malformed archive recovery |
17 | | * |
18 | | * Security focus: |
19 | | * - Path traversal (../) detection |
20 | | * - Absolute path handling |
21 | | * - Symlink escape attempts |
22 | | */ |
23 | | |
24 | | #include <cstddef> |
25 | | #include <cstdint> |
26 | | #include <cstdio> |
27 | | #include <cstdlib> |
28 | | #include <string> |
29 | | #include <vector> |
30 | | |
31 | | #include "cmSystemTools.h" |
32 | | |
33 | | // Archives can be large but limit for fuzzing |
34 | | static constexpr size_t kMinInputSize = 4; // Minimum magic bytes |
35 | | static constexpr size_t kMaxInputSize = 256 * 1024; // 256KB |
36 | | |
37 | | // Sandbox directory for extraction |
38 | | static std::string g_extractDir; |
39 | | |
40 | | extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) |
41 | 6 | { |
42 | 6 | (void)argc; |
43 | 6 | (void)argv; |
44 | | |
45 | | // Create a unique extraction directory in /tmp |
46 | 6 | char tmpl[] = "/tmp/cmake_fuzz_extract_XXXXXX"; |
47 | 6 | char* dir = mkdtemp(tmpl); |
48 | 6 | if (dir) { |
49 | 6 | g_extractDir = dir; |
50 | 6 | } else { |
51 | 0 | g_extractDir = "/tmp/cmake_fuzz_extract"; |
52 | 0 | cmSystemTools::MakeDirectory(g_extractDir); |
53 | 0 | } |
54 | | |
55 | 6 | return 0; |
56 | 6 | } |
57 | | |
58 | | extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) |
59 | 14.5k | { |
60 | 14.5k | if (size < kMinInputSize || size > kMaxInputSize) { |
61 | 2 | return 0; |
62 | 2 | } |
63 | | |
64 | | // Write archive to temp file |
65 | 14.5k | std::string archiveFile = g_extractDir + "/test_archive"; |
66 | 14.5k | { |
67 | 14.5k | FILE* fp = fopen(archiveFile.c_str(), "wb"); |
68 | 14.5k | if (!fp) { |
69 | 0 | return 0; |
70 | 0 | } |
71 | 14.5k | fwrite(data, 1, size, fp); |
72 | 14.5k | fclose(fp); |
73 | 14.5k | } |
74 | | |
75 | | // Create a fresh extraction subdirectory each time |
76 | 0 | std::string extractSubDir = g_extractDir + "/out"; |
77 | 14.5k | cmSystemTools::RemoveADirectory(extractSubDir); |
78 | 14.5k | cmSystemTools::MakeDirectory(extractSubDir); |
79 | | |
80 | | // Save current directory |
81 | 14.5k | std::string cwd = cmSystemTools::GetCurrentWorkingDirectory(); |
82 | | |
83 | | // Change to extraction directory (cmSystemTools extracts to cwd) |
84 | 14.5k | if (cmSystemTools::ChangeDirectory(extractSubDir)) { |
85 | | // Try extraction with different options |
86 | 14.5k | std::vector<std::string> files; |
87 | | |
88 | | // Extract without verbose, with timestamps |
89 | 14.5k | bool result1 = cmSystemTools::ExtractTar( |
90 | 14.5k | archiveFile, files, cmSystemTools::cmTarExtractTimestamps::Yes, false); |
91 | 14.5k | (void)result1; |
92 | | |
93 | | // Restore directory BEFORE removing (can't remove cwd) |
94 | 14.5k | cmSystemTools::ChangeDirectory(cwd); |
95 | | |
96 | | // Clean up extracted files |
97 | 14.5k | cmSystemTools::RemoveADirectory(extractSubDir); |
98 | 14.5k | cmSystemTools::MakeDirectory(extractSubDir); |
99 | | |
100 | | // Change back for second extraction |
101 | 14.5k | if (cmSystemTools::ChangeDirectory(extractSubDir)) { |
102 | | // Extract with verbose, without timestamps |
103 | 14.5k | files.clear(); |
104 | 14.5k | bool result2 = cmSystemTools::ExtractTar( |
105 | 14.5k | archiveFile, files, cmSystemTools::cmTarExtractTimestamps::No, true); |
106 | 14.5k | (void)result2; |
107 | | |
108 | | // Restore directory |
109 | 14.5k | cmSystemTools::ChangeDirectory(cwd); |
110 | 14.5k | } |
111 | 14.5k | } |
112 | | |
113 | | // Note: A more thorough security check would verify nothing escaped the |
114 | | // sandbox, but for fuzzing we rely on sanitizers to catch path traversal |
115 | | |
116 | | // Cleanup |
117 | 14.5k | cmSystemTools::RemoveADirectory(extractSubDir); |
118 | 14.5k | cmSystemTools::RemoveFile(archiveFile); |
119 | | |
120 | 14.5k | return 0; |
121 | 14.5k | } |