xref: /netbsd-src/external/bsd/libarchive/dist/libarchive/test/test_write_disk_secure.c (revision 43f752c9fe9b008e275c661ceffc8281c306c9ac)
1 /*-
2  * Copyright (c) 2003-2007 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 
27 #define UMASK 022
28 
29 /*
30  * Exercise security checks that should prevent certain
31  * writes.
32  */
33 
DEFINE_TEST(test_write_disk_secure)34 DEFINE_TEST(test_write_disk_secure)
35 {
36 #if defined(_WIN32) && !defined(__CYGWIN__)
37 	skipping("archive_write_disk security checks not supported on Windows");
38 #else
39 	struct archive *a;
40 	struct archive_entry *ae;
41 	struct stat st;
42 	char tmp[2048];
43 	const char *tmpdir;
44 	const char *lname =
45 	    "libarchive_test-test_write_disk_secure-absolute_symlink";
46 #if 0
47 	const char *fname =
48 	    "libarchive_test-test_write_disk_secure-absolute_symlink_path.tmp";
49 #endif
50 	const char *pname =
51 	    "libarchive_test-test_write_disk_secure-absolute_path.tmp";
52 #if defined(HAVE_LCHMOD) && defined(HAVE_SYMLINK) && \
53     defined(S_IRUSR) && defined(S_IWUSR) && defined(S_IXUSR)
54 	int working_lchmod;
55 #endif
56 
57 	/* Start with a known umask. */
58 	assertUmask(UMASK);
59 
60 	/* Create an archive_write_disk object. */
61 	assert((a = archive_write_disk_new()) != NULL);
62 
63 	/* Write a regular dir to it. */
64 	assert((ae = archive_entry_new()) != NULL);
65 	archive_entry_copy_pathname(ae, "dir");
66 	archive_entry_set_mode(ae, S_IFDIR | 0777);
67 	assert(0 == archive_write_header(a, ae));
68 	archive_entry_free(ae);
69 	assert(0 == archive_write_finish_entry(a));
70 
71 	/* Write a symlink to the dir above. */
72 	assert((ae = archive_entry_new()) != NULL);
73 	archive_entry_copy_pathname(ae, "link_to_dir");
74 	archive_entry_set_mode(ae, S_IFLNK | 0777);
75 	archive_entry_set_symlink(ae, "dir");
76 	archive_write_disk_set_options(a, 0);
77 	assert(0 == archive_write_header(a, ae));
78 	assert(0 == archive_write_finish_entry(a));
79 
80 	/*
81 	 * Without security checks, we should be able to
82 	 * extract a file through the link.
83 	 */
84 	assert(archive_entry_clear(ae) != NULL);
85 	archive_entry_copy_pathname(ae, "link_to_dir/filea");
86 	archive_entry_set_mode(ae, S_IFREG | 0777);
87 	assert(0 == archive_write_header(a, ae));
88 	assert(0 == archive_write_finish_entry(a));
89 
90 #if 0
91 	/* But with security checks enabled, this should fail. */
92 	assert(archive_entry_clear(ae) != NULL);
93 	archive_entry_copy_pathname(ae, "link_to_dir/fileb");
94 	archive_entry_set_mode(ae, S_IFREG | 0777);
95 	archive_write_disk_set_options(a, ARCHIVE_EXTRACT_SECURE_SYMLINKS);
96 	failure("Extracting a file through a symlink should fail here.");
97 	assertEqualInt(ARCHIVE_FAILED, archive_write_header(a, ae));
98 	archive_entry_free(ae);
99 	assert(0 == archive_write_finish_entry(a));
100 #endif
101 
102 	/* Write an absolute symlink to $TMPDIR. */
103 	archive_write_disk_set_options(a, ARCHIVE_EXTRACT_SECURE_SYMLINKS);
104 	assert((ae = archive_entry_new()) != NULL);
105 	if ((tmpdir = getenv("TMPDIR")) == NULL)
106 		tmpdir = "/tmp";
107 	snprintf(tmp, sizeof(tmp), "%s/%s", tmpdir, lname);
108 	archive_entry_copy_pathname(ae, tmp);
109 	archive_entry_set_mode(ae, S_IFLNK | 0777);
110 	archive_entry_set_symlink(ae, tmpdir);
111 	archive_write_disk_set_options(a, 0);
112 	assert(0 == archive_write_header(a, ae));
113 	assert(0 == archive_write_finish_entry(a));
114 
115 #if 0
116 	/* With security checks enabled, this should fail. */
117 	assert(archive_entry_clear(ae) != NULL);
118 	snprintf(tmp, sizeof(tmp), "%s/%s/%s", tmpdir, lname, fname);
119 	archive_entry_copy_pathname(ae, tmp);
120 	archive_entry_set_mode(ae, S_IFREG | 0777);
121 	archive_write_disk_set_options(a, ARCHIVE_EXTRACT_SECURE_SYMLINKS);
122 	failure("Extracting a file through an absolute symlink should fail here.");
123 	assertEqualInt(ARCHIVE_FAILED, archive_write_header(a, ae));
124 	archive_entry_free(ae);
125 	assertFileNotExists(tmp);
126 	snprintf(tmp, sizeof(tmp), "%s/%s", tmpdir, lname);
127 	assert(0 == unlink(tmp));
128 	snprintf(tmp, sizeof(tmp), "%s/%s", tmpdir, fname);
129 	unlink(tmp);
130 #endif
131 
132 	/* Create another link. */
133 	assert((ae = archive_entry_new()) != NULL);
134 	archive_entry_copy_pathname(ae, "link_to_dir2");
135 	archive_entry_set_mode(ae, S_IFLNK | 0777);
136 	archive_entry_set_symlink(ae, "dir");
137 	archive_write_disk_set_options(a, 0);
138 	assert(0 == archive_write_header(a, ae));
139 	assert(0 == archive_write_finish_entry(a));
140 
141 	/*
142 	 * With symlink check and unlink option, it should remove
143 	 * the link and create the dir.
144 	 */
145 	assert(archive_entry_clear(ae) != NULL);
146 	archive_entry_copy_pathname(ae, "link_to_dir2/filec");
147 	archive_entry_set_mode(ae, S_IFREG | 0777);
148 	archive_write_disk_set_options(a, ARCHIVE_EXTRACT_SECURE_SYMLINKS | ARCHIVE_EXTRACT_UNLINK);
149 	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
150 	archive_entry_free(ae);
151 	assert(0 == archive_write_finish_entry(a));
152 
153 	/* Create a nested symlink. */
154 	assert((ae = archive_entry_new()) != NULL);
155 	archive_entry_copy_pathname(ae, "dir/nested_link_to_dir");
156 	archive_entry_set_mode(ae, S_IFLNK | 0777);
157 	archive_entry_set_symlink(ae, "../dir");
158 	archive_write_disk_set_options(a, 0);
159 	assert(0 == archive_write_header(a, ae));
160 	assert(0 == archive_write_finish_entry(a));
161 
162 #if 0
163 	/* But with security checks enabled, this should fail. */
164 	assert(archive_entry_clear(ae) != NULL);
165 	archive_entry_copy_pathname(ae, "dir/nested_link_to_dir/filed");
166 	archive_entry_set_mode(ae, S_IFREG | 0777);
167 	archive_write_disk_set_options(a, ARCHIVE_EXTRACT_SECURE_SYMLINKS);
168 	failure("Extracting a file through a symlink should fail here.");
169 	assertEqualInt(ARCHIVE_FAILED, archive_write_header(a, ae));
170 	archive_entry_free(ae);
171 	assert(0 == archive_write_finish_entry(a));
172 #endif
173 
174 	/*
175 	 * Without security checks, extracting a dir over a link to a
176 	 * dir should follow the link.
177 	 */
178 	/* Create a symlink to a dir. */
179 	assert((ae = archive_entry_new()) != NULL);
180 	archive_entry_copy_pathname(ae, "link_to_dir3");
181 	archive_entry_set_mode(ae, S_IFLNK | 0777);
182 	archive_entry_set_symlink(ae, "dir");
183 	archive_write_disk_set_options(a, 0);
184 	assert(0 == archive_write_header(a, ae));
185 	assert(0 == archive_write_finish_entry(a));
186 	/* Extract a dir whose name matches the symlink. */
187 	assert(archive_entry_clear(ae) != NULL);
188 	archive_entry_copy_pathname(ae, "link_to_dir3");
189 	archive_entry_set_mode(ae, S_IFDIR | 0777);
190 	assert(0 == archive_write_header(a, ae));
191 	assert(0 == archive_write_finish_entry(a));
192 	/* Verify link was followed. */
193 	assertEqualInt(0, lstat("link_to_dir3", &st));
194 	assert(S_ISLNK(st.st_mode));
195 	archive_entry_free(ae);
196 
197 	/*
198 	 * As above, but a broken link, so the link should get replaced.
199 	 */
200 	/* Create a symlink to a dir. */
201 	assert((ae = archive_entry_new()) != NULL);
202 	archive_entry_copy_pathname(ae, "link_to_dir4");
203 	archive_entry_set_mode(ae, S_IFLNK | 0777);
204 	archive_entry_set_symlink(ae, "nonexistent_dir");
205 	archive_write_disk_set_options(a, 0);
206 	assert(0 == archive_write_header(a, ae));
207 	assert(0 == archive_write_finish_entry(a));
208 	/* Extract a dir whose name matches the symlink. */
209 	assert(archive_entry_clear(ae) != NULL);
210 	archive_entry_copy_pathname(ae, "link_to_dir4");
211 	archive_entry_set_mode(ae, S_IFDIR | 0777);
212 	assert(0 == archive_write_header(a, ae));
213 	assert(0 == archive_write_finish_entry(a));
214 	/* Verify link was replaced. */
215 	assertEqualInt(0, lstat("link_to_dir4", &st));
216 	assert(S_ISDIR(st.st_mode));
217 	archive_entry_free(ae);
218 
219 	/*
220 	 * As above, but a link to a non-dir, so the link should get replaced.
221 	 */
222 	/* Create a regular file and a symlink to it */
223 	assert((ae = archive_entry_new()) != NULL);
224 	archive_entry_copy_pathname(ae, "non_dir");
225 	archive_entry_set_mode(ae, S_IFREG | 0777);
226 	archive_write_disk_set_options(a, 0);
227 	assert(0 == archive_write_header(a, ae));
228 	assert(0 == archive_write_finish_entry(a));
229 	/* Create symlink to the file. */
230 	archive_entry_copy_pathname(ae, "link_to_dir5");
231 	archive_entry_set_mode(ae, S_IFLNK | 0777);
232 	archive_entry_set_symlink(ae, "non_dir");
233 	archive_write_disk_set_options(a, 0);
234 	assert(0 == archive_write_header(a, ae));
235 	assert(0 == archive_write_finish_entry(a));
236 	/* Extract a dir whose name matches the symlink. */
237 	assert(archive_entry_clear(ae) != NULL);
238 	archive_entry_copy_pathname(ae, "link_to_dir5");
239 	archive_entry_set_mode(ae, S_IFDIR | 0777);
240 	assert(0 == archive_write_header(a, ae));
241 	assert(0 == archive_write_finish_entry(a));
242 	/* Verify link was replaced. */
243 	assertEqualInt(0, lstat("link_to_dir5", &st));
244 	assert(S_ISDIR(st.st_mode));
245 	archive_entry_free(ae);
246 
247 	/*
248 	 * Without security checks, we should be able to
249 	 * extract an absolute path.
250 	 */
251 	assert((ae = archive_entry_new()) != NULL);
252 	snprintf(tmp, sizeof(tmp), "%s/%s", tmpdir, pname);
253 	archive_entry_copy_pathname(ae, tmp);
254 	archive_entry_set_mode(ae, S_IFREG | 0777);
255 	assert(0 == archive_write_header(a, ae));
256 	assert(0 == archive_write_finish_entry(a));
257 	assertFileExists(tmp);
258 	assert(0 == unlink(tmp));
259 
260 	/* But with security checks enabled, this should fail. */
261 	assert(archive_entry_clear(ae) != NULL);
262 	archive_entry_copy_pathname(ae, tmp);
263 	archive_entry_set_mode(ae, S_IFREG | 0777);
264 	archive_write_disk_set_options(a, ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS);
265 	failure("Extracting an absolute path should fail here.");
266 	assertEqualInt(ARCHIVE_FAILED, archive_write_header(a, ae));
267 	archive_entry_free(ae);
268 	assert(0 == archive_write_finish_entry(a));
269 	assertFileNotExists(tmp);
270 
271 	assertEqualInt(ARCHIVE_OK, archive_write_free(a));
272 
273 	/* Test the entries on disk. */
274 	assert(0 == lstat("dir", &st));
275 	failure("dir: st.st_mode=%o", st.st_mode);
276 	assert((st.st_mode & 0777) == 0755);
277 
278 	assert(0 == lstat("link_to_dir", &st));
279 	failure("link_to_dir: st.st_mode=%o", st.st_mode);
280 	assert(S_ISLNK(st.st_mode));
281 #if defined(HAVE_SYMLINK) && defined(HAVE_LCHMOD) && \
282     defined(S_IRUSR) && defined(S_IWUSR) && defined(S_IXUSR)
283 	/* Verify if we are able to lchmod() */
284 	if (symlink("dir", "testlink_to_dir") == 0) {
285 		if (lchmod("testlink_to_dir",
286 		    S_IRUSR | S_IWUSR | S_IXUSR) != 0) {
287 			switch (errno) {
288 				case ENOTSUP:
289 				case ENOSYS:
290 #if ENOTSUP != EOPNOTSUPP
291 				case EOPNOTSUPP:
292 #endif
293 					working_lchmod = 0;
294 					break;
295 				default:
296 					working_lchmod = 1;
297 			}
298 		} else
299 			working_lchmod = 1;
300 	} else
301 		working_lchmod = 0;
302 
303 	if (working_lchmod) {
304 		failure("link_to_dir: st.st_mode=%o", st.st_mode);
305 		assert((st.st_mode & 07777) == 0755);
306 	}
307 #endif
308 
309 	assert(0 == lstat("dir/filea", &st));
310 	failure("dir/filea: st.st_mode=%o", st.st_mode);
311 	assert((st.st_mode & 07777) == 0755);
312 
313 	failure("dir/fileb: This file should not have been created");
314 	assert(0 != lstat("dir/fileb", &st));
315 
316 	assert(0 == lstat("link_to_dir2", &st));
317 	failure("link_to_dir2 should have been re-created as a true dir");
318 	assert(S_ISDIR(st.st_mode));
319 	failure("link_to_dir2: Implicit dir creation should obey umask, but st.st_mode=%o", st.st_mode);
320 	assert((st.st_mode & 0777) == 0755);
321 
322 	assert(0 == lstat("link_to_dir2/filec", &st));
323 	assert(S_ISREG(st.st_mode));
324 	failure("link_to_dir2/filec: st.st_mode=%o", st.st_mode);
325 	assert((st.st_mode & 07777) == 0755);
326 
327 	failure("dir/filed: This file should not have been created");
328 	assert(0 != lstat("dir/filed", &st));
329 #endif
330 }
331