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