/src/httrack/fuzz/fuzz-arc.c
Line | Count | Source |
1 | | /* ------------------------------------------------------------ */ |
2 | | /* |
3 | | HTTrack Website Copier, Offline Browser for Windows and Unix |
4 | | Copyright (C) 2026 Xavier Roche and other contributors |
5 | | |
6 | | SPDX-License-Identifier: GPL-3.0-or-later |
7 | | |
8 | | This program is free software: you can redistribute it and/or modify |
9 | | it under the terms of the GNU General Public License as published by |
10 | | the Free Software Foundation, either version 3 of the License, or |
11 | | (at your option) any later version. |
12 | | |
13 | | This program is distributed in the hope that it will be useful, |
14 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | | GNU General Public License for more details. |
17 | | |
18 | | You should have received a copy of the GNU General Public License |
19 | | along with this program. If not, see <http://www.gnu.org/licenses/>. |
20 | | |
21 | | Ethical use: we kindly ask that you NOT use this software to harvest email |
22 | | addresses or to collect any other private information about people. Doing so |
23 | | would dishonor our work and waste the many hours we have spent on it. |
24 | | |
25 | | Please visit our Website: http://www.httrack.com |
26 | | */ |
27 | | |
28 | | /* Fuzz proxytrack's .arc reader the way `--convert` drives it: the record loop |
29 | | seeks on lengths read from the file, and every entry reaches a writer. */ |
30 | | #include "fuzz.h" |
31 | | |
32 | | #include <stdarg.h> |
33 | | #include <stdio.h> |
34 | | #include <stdlib.h> |
35 | | #include <unistd.h> |
36 | | |
37 | | #include "coucal.h" |
38 | | #include "proxy/store.h" |
39 | | |
40 | | /* An .arc past this size says nothing a smaller one cannot. */ |
41 | 9.02k | #define FUZZ_ARC_MAXSIZE (1024 * 1024) |
42 | | |
43 | | static char arc_dir[256]; |
44 | | static char arc_in[sizeof(arc_dir) + sizeof("/in.arc")]; |
45 | | static char arc_out[sizeof(arc_dir) + sizeof("/out.arc")]; |
46 | | |
47 | 1 | static void fuzz_arc_cleanup(void) { |
48 | 1 | (void) unlink(arc_in); |
49 | 1 | (void) unlink(arc_out); |
50 | 1 | (void) rmdir(arc_dir); |
51 | 1 | } |
52 | | |
53 | | /* proxytrack's main() installs one; without it coucal logs stats per free */ |
54 | | static void fuzz_arc_coucal_log(coucal_opaque arg, coucal_loglevel level, |
55 | 18.4k | const char *format, va_list args) { |
56 | 18.4k | (void) arg; |
57 | 18.4k | (void) level; |
58 | 18.4k | (void) format; |
59 | 18.4k | (void) args; |
60 | 18.4k | } |
61 | | |
62 | | /* PT_GetType() picks the format from the extension, so the names end in .arc */ |
63 | 4.51k | static int fuzz_arc_setup(void) { |
64 | 4.51k | if (arc_in[0] == '\0') { |
65 | 1 | const char *const tmp = getenv("TMPDIR"); |
66 | | |
67 | 1 | coucal_set_global_assert_handler(fuzz_arc_coucal_log, NULL); |
68 | | |
69 | 1 | snprintf(arc_dir, sizeof(arc_dir), "%s/fuzz-arc-XXXXXX", |
70 | 1 | tmp != NULL && *tmp != '\0' ? tmp : "/tmp"); |
71 | 1 | if (mkdtemp(arc_dir) == NULL) { |
72 | 0 | return -1; |
73 | 0 | } |
74 | 1 | snprintf(arc_out, sizeof(arc_out), "%s/out.arc", arc_dir); |
75 | 1 | snprintf(arc_in, sizeof(arc_in), "%s/in.arc", arc_dir); |
76 | 1 | atexit(fuzz_arc_cleanup); |
77 | 1 | } |
78 | 4.51k | return 0; |
79 | 4.51k | } |
80 | | |
81 | | /* PT_Enumerate() returns a NULL-terminated array of pointers into the same |
82 | | block, so touching each string is what proves the references land in it. */ |
83 | 13.5k | static void fuzz_arc_walk(char **list) { |
84 | 13.5k | static volatile size_t sink; /* keeps the reads from being optimised away */ |
85 | 13.5k | size_t i; |
86 | | |
87 | 13.5k | if (list == NULL) { |
88 | 7.17k | return; |
89 | 7.17k | } |
90 | 81.1k | for (i = 0; list[i] != NULL; i++) { |
91 | 74.7k | sink += strlen(list[i]); |
92 | 74.7k | } |
93 | 6.36k | } |
94 | | |
95 | 4.51k | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { |
96 | 4.51k | PT_Indexes indexes; |
97 | 4.51k | char **flat = NULL, **tree = NULL, **none = NULL; |
98 | 4.51k | FILE *fp; |
99 | 4.51k | hts_boolean written; |
100 | | |
101 | 4.51k | if (size > FUZZ_ARC_MAXSIZE || fuzz_arc_setup() != 0) { |
102 | 0 | return 0; |
103 | 0 | } |
104 | 4.51k | if ((fp = fopen(arc_in, "wb")) == NULL) { |
105 | 0 | return 0; |
106 | 0 | } |
107 | 4.51k | written = fwrite(data, 1, size, fp) == size ? HTS_TRUE : HTS_FALSE; |
108 | 4.51k | if (fclose(fp) != 0 || !written) { |
109 | 0 | return 0; |
110 | 0 | } |
111 | | |
112 | 4.51k | indexes = PT_New(); |
113 | 4.51k | if (indexes != NULL) { |
114 | 4.51k | if (PT_AddIndex(indexes, arc_in) > 0) { |
115 | | /* the writer reads back every entry the loader indexed */ |
116 | 3.14k | (void) PT_SaveCache(indexes, arc_out); |
117 | | /* An empty prefix takes every entry, subtree=1 takes the other shape, |
118 | | and a prefix almost no input indexes takes the no-match return. */ |
119 | 3.14k | flat = PT_Enumerate(indexes, "", 0); |
120 | 3.14k | tree = PT_Enumerate(indexes, "http://", 1); |
121 | 3.14k | none = PT_Enumerate(indexes, "http://255.255.255.255./", 0); |
122 | 3.14k | } |
123 | 4.51k | PT_Delete(indexes); |
124 | 4.51k | } |
125 | 4.51k | fuzz_arc_walk(flat); |
126 | 4.51k | fuzz_arc_walk(tree); |
127 | 4.51k | fuzz_arc_walk(none); |
128 | 4.51k | PT_Enumerate_Delete(&flat); |
129 | 4.51k | PT_Enumerate_Delete(&tree); |
130 | 4.51k | PT_Enumerate_Delete(&none); |
131 | 4.51k | return 0; |
132 | 4.51k | } |