1 /*-
2 * Copyright (c) 2025 Tim Kientzle
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25 #include "test.h"
26 #include "test_fuzz_consumer.h"
27
28 #include <stdlib.h>
29
30 /*
31 * Replay a fuzzer binary through the MTREE writer, matching the protocol
32 * in fuzzers/custom/fuzz_writer_mtree.cc.
33 */
DEFINE_TEST(test_write_format_mtree_null_deref)34 DEFINE_TEST(test_write_format_mtree_null_deref)
35 {
36 const char *refname = "test_write_format_mtree_null_deref.bin";
37 FILE *f;
38 uint8_t raw[16384];
39 size_t rawsize;
40 struct fuzz_consumer consumer;
41 uint8_t opts, num_entries;
42 struct archive *a;
43 struct archive_entry *entry;
44 size_t used;
45 void *out_buf;
46 int i;
47
48 extract_reference_file(refname);
49 f = fopen(refname, "rb");
50 if (!assert(f != NULL))
51 return;
52 rawsize = fread(raw, 1, sizeof(raw), f);
53 fclose(f);
54 if (!assert(rawsize >= 4))
55 return;
56
57 fuzz_consumer_init(&consumer, raw, rawsize);
58 opts = fuzz_consume_byte(&consumer);
59 num_entries = (fuzz_consume_byte(&consumer) % 8) + 1;
60
61 a = archive_write_new();
62 if (!assert(a != NULL))
63 return;
64
65 if (opts & 0x01)
66 archive_write_set_format_mtree_classic(a);
67 else
68 archive_write_set_format_mtree(a);
69
70 if (opts & 0x02)
71 archive_write_set_options(a, "mtree:all");
72 if (opts & 0x04)
73 archive_write_set_options(a, "mtree:use-set");
74 if (opts & 0x08)
75 archive_write_set_options(a, "mtree:indent");
76 if (opts & 0x10)
77 archive_write_set_options(a, "mtree:dironly");
78
79 out_buf = malloc(256 * 1024);
80 if (!assert(out_buf != NULL)) {
81 archive_write_free(a);
82 return;
83 }
84 if (archive_write_open_memory(a, out_buf, 256 * 1024, &used)
85 != ARCHIVE_OK) {
86 archive_write_free(a);
87 free(out_buf);
88 return;
89 }
90
91 entry = archive_entry_new();
92 if (!assert(entry != NULL)) {
93 archive_write_free(a);
94 free(out_buf);
95 return;
96 }
97
98 for (i = 0; i < num_entries && fuzz_consumer_remaining(&consumer) > 2;
99 i++) {
100 const char *name;
101 uint8_t ftype;
102 uint32_t file_size = 0;
103
104 archive_entry_clear(entry);
105
106 name = fuzz_consume_string(&consumer, 128);
107 if (name[0] == '\0')
108 name = "file.txt";
109 archive_entry_set_pathname(entry, name);
110
111 ftype = fuzz_consume_byte(&consumer) % 5;
112 switch (ftype) {
113 case 0:
114 archive_entry_set_filetype(entry, AE_IFREG);
115 archive_entry_set_perm(entry, 0644);
116 break;
117 case 1:
118 archive_entry_set_filetype(entry, AE_IFDIR);
119 archive_entry_set_perm(entry, 0755);
120 break;
121 case 2:
122 archive_entry_set_filetype(entry, AE_IFLNK);
123 archive_entry_set_perm(entry, 0777);
124 archive_entry_set_symlink(entry,
125 fuzz_consume_string(&consumer, 64));
126 break;
127 case 3:
128 archive_entry_set_filetype(entry, AE_IFBLK);
129 archive_entry_set_perm(entry, 0600);
130 archive_entry_set_rdev(entry,
131 fuzz_consume_u16(&consumer));
132 break;
133 case 4:
134 archive_entry_set_filetype(entry, AE_IFIFO);
135 archive_entry_set_perm(entry, 0644);
136 break;
137 }
138
139 archive_entry_set_uid(entry, fuzz_consume_byte(&consumer));
140 archive_entry_set_gid(entry, fuzz_consume_byte(&consumer));
141 archive_entry_set_mtime(entry,
142 1700000000 + fuzz_consume_u16(&consumer), 0);
143 archive_entry_set_uname(entry, "user");
144 archive_entry_set_gname(entry, "group");
145
146 if (fuzz_consumer_remaining(&consumer) > 1 &&
147 (fuzz_consume_byte(&consumer) & 0x01))
148 archive_entry_copy_fflags_text(entry, "uappnd,uchg");
149
150 if (ftype == 0) {
151 file_size = fuzz_consume_byte(&consumer) % 128;
152 archive_entry_set_size(entry, file_size);
153 }
154
155 if (archive_write_header(a, entry) != ARCHIVE_OK)
156 continue;
157
158 if (file_size > 0 && fuzz_consumer_remaining(&consumer) > 0) {
159 size_t to_write = file_size;
160 uint8_t data[128];
161 if (to_write > fuzz_consumer_remaining(&consumer))
162 to_write = fuzz_consumer_remaining(&consumer);
163 fuzz_consume_bytes(&consumer, data, to_write);
164 archive_write_data(a, data, to_write);
165 }
166 }
167
168 archive_entry_free(entry);
169 /* Close triggers tree traversal; must not crash. */
170 archive_write_close(a);
171 archive_write_free(a);
172 free(out_buf);
173 }
174
DEFINE_TEST(test_write_format_mtree_no_set_symlink)175 DEFINE_TEST(test_write_format_mtree_no_set_symlink)
176 {
177 struct archive *a;
178 size_t buffsize = 4096;
179 char *buff;
180 size_t used;
181 assert((buff = malloc(buffsize)) != NULL);
182 assert((a = archive_write_new()) != NULL);
183 assertEqualIntA(a, ARCHIVE_OK, archive_write_set_format_mtree(a));
184 assertEqualIntA(a, ARCHIVE_OK,
185 archive_write_open_memory(a, buff, buffsize, &used));
186
187 struct archive_entry *ae;
188 assert((ae = archive_entry_new()) != NULL);
189 archive_entry_set_pathname(ae, "./badlink"),
190 archive_entry_set_filetype(ae, AE_IFLNK);
191 archive_entry_set_perm(ae, 0777);
192 /* archive_entry_set_symlink(ae, "target"); (omitted) */
193 assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
194 archive_entry_free(ae);
195
196 assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a));
197 assertEqualInt(ARCHIVE_OK, archive_write_free(a));
198 free(buff);
199 }
200
DEFINE_TEST(test_write_format_mtree_reg_dot_root)201 DEFINE_TEST(test_write_format_mtree_reg_dot_root)
202 {
203 struct archive *a;
204 size_t buffsize = 4096;
205 char *buff;
206 size_t used;
207 assert((buff = malloc(buffsize)) != NULL);
208 assert((a = archive_write_new()) != NULL);
209 assertEqualIntA(a, ARCHIVE_OK, archive_write_set_format_mtree(a));
210 assertEqualIntA(a, ARCHIVE_OK,
211 archive_write_open_memory(a, buff, buffsize, &used));
212
213 struct archive_entry *ae;
214 /* Entry 1: "." with filetype AE_IFREG (not AE_IFDIR). */
215 assert((ae = archive_entry_new()) != NULL);
216 archive_entry_set_pathname(ae, "."),
217 archive_entry_set_filetype(ae, AE_IFREG);
218 archive_entry_set_perm(ae, 0644);
219 assertEqualIntA(a, ARCHIVE_FAILED, archive_write_header(a, ae));
220 archive_entry_free(ae);
221
222 /* Entry 2: any child path. */
223 assert((ae = archive_entry_new()) != NULL);
224 archive_entry_set_pathname(ae, "./foo"),
225 archive_entry_set_filetype(ae, AE_IFREG);
226 archive_entry_set_perm(ae, 0644);
227 assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
228 archive_entry_free(ae);
229
230 assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a));
231 assertEqualInt(ARCHIVE_OK, archive_write_free(a));
232 free(buff);
233 }
234