2 * Copyright (c) 2003-2007 Tim Kientzle
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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.
26 __FBSDID("$FreeBSD$");
28 #if !defined(_WIN32) || defined(__CYGWIN__)
32 static long _default_gid = -1;
33 static long _invalid_gid = -1;
34 static long _alt_gid = -1;
37 * To fully test SGID restores, we need three distinct GIDs to work
39 * * the GID that files are created with by default (for the
40 * current user in the current directory)
41 * * An "alt gid" that this user can create files with
42 * * An "invalid gid" that this user is not permitted to create
44 * The second fails if this user doesn't belong to at least two groups;
45 * the third fails if the current user is root.
50 static int _searched = 0;
57 /* If we've already looked this up, we're done. */
62 /* Create a file on disk in the current default dir. */
63 fd = open("test_gid", O_CREAT | O_BINARY, 0664);
64 failure("Couldn't create a file for gid testing.");
67 /* See what GID it ended up with. This is our "valid" GID. */
68 assert(fstat(fd, &st) == 0);
69 _default_gid = st.st_gid;
71 /* Find a GID for which fchown() fails. This is our "invalid" GID. */
73 /* This loop stops when we wrap the gid or examine 10,000 gids. */
74 for (gid = 1, n = 1; gid == n && n < 10000 ; n++, gid++) {
75 if (fchown(fd, uid, gid) != 0) {
82 * Find a GID for which fchown() succeeds, but which isn't the
83 * default. This is the "alternate" gid.
86 for (gid = 0, n = 0; gid == n && n < 10000 ; n++, gid++) {
87 /* _alt_gid must be different than _default_gid */
88 if (gid == (gid_t)_default_gid)
90 if (fchown(fd, uid, gid) == 0) {
109 return (_invalid_gid);
116 return (_default_gid);
121 * Exercise permission and ownership restores.
122 * In particular, try to exercise a bunch of border cases related
123 * to files/dirs that already exist, SUID/SGID bits, etc.
126 DEFINE_TEST(test_write_disk_perms)
128 #if defined(_WIN32) && !defined(__CYGWIN__)
129 skipping("archive_write_disk interface");
132 struct archive_entry *ae;
135 uid_t try_to_change_uid;
140 * Set ownership of the current directory to the group of this
141 * process. Otherwise, the SGID tests below fail if the
142 * /tmp directory is owned by a group to which we don't belong
143 * and we're on a system where group ownership is inherited.
144 * (Because we're not allowed to SGID files with defaultgid().)
146 assertEqualInt(0, chown(".", getuid(), getgid()));
148 /* Create an archive_write_disk object. */
149 assert((a = archive_write_disk_new()) != NULL);
151 /* Write a regular file to it. */
152 assert((ae = archive_entry_new()) != NULL);
153 archive_entry_copy_pathname(ae, "file_0755");
154 archive_entry_set_mode(ae, S_IFREG | 0777);
155 assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
156 assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
157 archive_entry_free(ae);
159 /* Write a regular file, then write over it. */
160 /* For files, the perms should get updated. */
161 assert((ae = archive_entry_new()) != NULL);
162 archive_entry_copy_pathname(ae, "file_overwrite_0144");
163 archive_entry_set_mode(ae, S_IFREG | 0777);
164 assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
165 archive_entry_free(ae);
166 assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
167 /* Check that file was created with different perms. */
168 assertEqualInt(0, stat("file_overwrite_0144", &st));
169 failure("file_overwrite_0144: st.st_mode=%o", st.st_mode);
170 assert((st.st_mode & 07777) != 0144);
171 /* Overwrite, this should change the perms. */
172 assert((ae = archive_entry_new()) != NULL);
173 archive_entry_copy_pathname(ae, "file_overwrite_0144");
174 archive_entry_set_mode(ae, S_IFREG | 0144);
175 assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
176 archive_entry_free(ae);
177 assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
179 /* Write a regular dir. */
180 assert((ae = archive_entry_new()) != NULL);
181 archive_entry_copy_pathname(ae, "dir_0514");
182 archive_entry_set_mode(ae, S_IFDIR | 0514);
183 assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
184 archive_entry_free(ae);
185 assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
187 /* Overwrite an existing dir. */
188 /* For dir, the first perms should get left. */
189 assertMakeDir("dir_overwrite_0744", 0744);
190 /* Check original perms. */
191 assertEqualInt(0, stat("dir_overwrite_0744", &st));
192 failure("dir_overwrite_0744: st.st_mode=%o", st.st_mode);
193 assertEqualInt(st.st_mode & 0777, 0744);
194 /* Overwrite shouldn't edit perms. */
195 assert((ae = archive_entry_new()) != NULL);
196 archive_entry_copy_pathname(ae, "dir_overwrite_0744");
197 archive_entry_set_mode(ae, S_IFDIR | 0777);
198 assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
199 archive_entry_free(ae);
200 assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
201 /* Make sure they're unchanged. */
202 assertEqualInt(0, stat("dir_overwrite_0744", &st));
203 failure("dir_overwrite_0744: st.st_mode=%o", st.st_mode);
204 assertEqualInt(st.st_mode & 0777, 0744);
206 /* For dir, the owner should get left when not overwritting. */
207 assertMakeDir("dir_owner", 0744);
210 original_uid = getuid() + 1;
211 try_to_change_uid = getuid();
212 assertEqualInt(0, chown("dir_owner", original_uid, getgid()));
214 original_uid = getuid();
215 try_to_change_uid = getuid() + 1;
218 /* Check original owner. */
219 assertEqualInt(0, stat("dir_owner", &st));
220 failure("dir_owner: st.st_uid=%d", st.st_uid);
221 assertEqualInt(st.st_uid, original_uid);
222 /* Shouldn't try to edit the owner when no overwrite option is set. */
223 assert((ae = archive_entry_new()) != NULL);
224 archive_entry_copy_pathname(ae, "dir_owner");
225 archive_entry_set_mode(ae, S_IFDIR | 0744);
226 archive_entry_set_uid(ae, try_to_change_uid);
227 archive_write_disk_set_options(a,
228 ARCHIVE_EXTRACT_OWNER | ARCHIVE_EXTRACT_NO_OVERWRITE);
229 assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
230 archive_entry_free(ae);
231 assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
232 /* Make sure they're unchanged. */
233 assertEqualInt(0, stat("dir_owner", &st));
234 failure("dir_owner: st.st_uid=%d", st.st_uid);
235 assertEqualInt(st.st_uid, original_uid);
237 /* Write a regular file with SUID bit, but don't use _EXTRACT_PERM. */
238 assert((ae = archive_entry_new()) != NULL);
239 archive_entry_copy_pathname(ae, "file_no_suid");
240 archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0777);
241 archive_write_disk_set_options(a, 0);
242 assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
243 assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
245 /* Write a regular file with ARCHIVE_EXTRACT_PERM. */
246 assert(archive_entry_clear(ae) != NULL);
247 archive_entry_copy_pathname(ae, "file_0777");
248 archive_entry_set_mode(ae, S_IFREG | 0777);
249 archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM);
250 assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
251 assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
253 /* Write a regular file with ARCHIVE_EXTRACT_PERM & SUID bit */
254 assert(archive_entry_clear(ae) != NULL);
255 archive_entry_copy_pathname(ae, "file_4742");
256 archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0742);
257 archive_entry_set_uid(ae, getuid());
258 archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM);
259 assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
260 assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
263 * Write a regular file with ARCHIVE_EXTRACT_PERM & SUID bit,
264 * but wrong uid. POSIX says you shouldn't restore SUID bit
265 * unless the UID could be restored.
267 assert(archive_entry_clear(ae) != NULL);
268 archive_entry_copy_pathname(ae, "file_bad_suid");
269 archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0742);
270 archive_entry_set_uid(ae, getuid() + 1);
271 archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM);
272 assertA(0 == archive_write_header(a, ae));
274 * Because we didn't ask for owner, the failure to
275 * restore SUID shouldn't return a failure.
276 * We check below to make sure SUID really wasn't set.
277 * See more detailed comments below.
279 failure("Opportunistic SUID failure shouldn't return error.");
280 assertEqualInt(0, archive_write_finish_entry(a));
283 assert(archive_entry_clear(ae) != NULL);
284 archive_entry_copy_pathname(ae, "file_bad_suid2");
285 archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0742);
286 archive_entry_set_uid(ae, getuid() + 1);
287 archive_write_disk_set_options(a,
288 ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_OWNER);
289 assertA(0 == archive_write_header(a, ae));
290 /* Owner change should fail here. */
291 failure("Non-opportunistic SUID failure should return error.");
292 assertEqualInt(ARCHIVE_WARN, archive_write_finish_entry(a));
295 /* Write a regular file with ARCHIVE_EXTRACT_PERM & SGID bit */
296 assert(archive_entry_clear(ae) != NULL);
297 archive_entry_copy_pathname(ae, "file_perm_sgid");
298 archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742);
299 archive_entry_set_gid(ae, defaultgid());
300 archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM);
301 assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
302 failure("Setting SGID bit should succeed here.");
303 assertEqualIntA(a, 0, archive_write_finish_entry(a));
305 if (altgid() == -1) {
307 * Current user must belong to at least two groups or
308 * else we can't test setting the GID to another group.
310 skipping("Current user can't test gid restore: must belong to more than one group.");
313 * Write a regular file with ARCHIVE_EXTRACT_PERM & SGID bit
314 * but without ARCHIVE_EXTRACT_OWNER.
317 * This is a weird case: The user has asked for permissions to
318 * be restored but not asked for ownership to be restored. As
319 * a result, the default file creation will create a file with
320 * the wrong group. There are several possible behaviors for
321 * libarchive in this scenario:
322 * = Set the SGID bit. It is wrong and a security hole to
323 * set SGID with the wrong group. Even POSIX thinks so.
324 * = Implicitly set the group. I don't like this.
325 * = drop the SGID bit and warn (the old libarchive behavior)
326 * = drop the SGID bit and don't warn (the current libarchive
328 * The current behavior sees SGID/SUID restore when you
329 * don't ask for owner restore as an "opportunistic"
330 * action. That is, libarchive should do it if it can,
331 * but if it can't, it's not an error.
333 assert(archive_entry_clear(ae) != NULL);
334 archive_entry_copy_pathname(ae, "file_alt_sgid");
335 archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742);
336 archive_entry_set_uid(ae, getuid());
337 archive_entry_set_gid(ae, altgid());
338 archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM);
339 assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
340 failure("Setting SGID bit should fail because of group mismatch but the failure should be silent because we didn't ask for the group to be set.");
341 assertEqualIntA(a, 0, archive_write_finish_entry(a));
344 * As above, but add _EXTRACT_OWNER to verify that it
347 assert(archive_entry_clear(ae) != NULL);
348 archive_entry_copy_pathname(ae, "file_alt_sgid_owner");
349 archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742);
350 archive_entry_set_uid(ae, getuid());
351 archive_entry_set_gid(ae, altgid());
352 archive_write_disk_set_options(a,
353 ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_OWNER);
354 assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
355 failure("Setting SGID bit should succeed here.");
356 assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
360 * Write a regular file with ARCHIVE_EXTRACT_PERM & SGID bit,
361 * but wrong GID. POSIX says you shouldn't restore SGID bit
362 * unless the GID could be restored.
364 if (invalidgid() == -1) {
365 /* This test always fails for root. */
366 printf("Running as root: Can't test SGID failures.\n");
368 assert(archive_entry_clear(ae) != NULL);
369 archive_entry_copy_pathname(ae, "file_bad_sgid");
370 archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742);
371 archive_entry_set_gid(ae, invalidgid());
372 archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM);
373 assertA(0 == archive_write_header(a, ae));
374 failure("This SGID restore should fail without an error.");
375 assertEqualIntA(a, 0, archive_write_finish_entry(a));
377 assert(archive_entry_clear(ae) != NULL);
378 archive_entry_copy_pathname(ae, "file_bad_sgid2");
379 archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742);
380 archive_entry_set_gid(ae, invalidgid());
381 archive_write_disk_set_options(a,
382 ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_OWNER);
383 assertA(0 == archive_write_header(a, ae));
384 failure("This SGID restore should fail with an error.");
385 assertEqualIntA(a, ARCHIVE_WARN, archive_write_finish_entry(a));
388 /* Set ownership should fail if we're not root. */
390 printf("Running as root: Can't test setuid failures.\n");
392 assert(archive_entry_clear(ae) != NULL);
393 archive_entry_copy_pathname(ae, "file_bad_owner");
394 archive_entry_set_mode(ae, S_IFREG | 0744);
395 archive_entry_set_uid(ae, getuid() + 1);
396 archive_write_disk_set_options(a, ARCHIVE_EXTRACT_OWNER);
397 assertA(0 == archive_write_header(a, ae));
398 assertEqualIntA(a,ARCHIVE_WARN,archive_write_finish_entry(a));
401 assertEqualInt(ARCHIVE_OK, archive_write_free(a));
402 archive_entry_free(ae);
404 /* Test the entries on disk. */
405 assertEqualInt(0, stat("file_0755", &st));
406 failure("file_0755: st.st_mode=%o", st.st_mode);
407 assertEqualInt(st.st_mode & 07777, 0755);
409 assertEqualInt(0, stat("file_overwrite_0144", &st));
410 failure("file_overwrite_0144: st.st_mode=%o", st.st_mode);
411 assertEqualInt(st.st_mode & 07777, 0144);
413 assertEqualInt(0, stat("dir_0514", &st));
414 failure("dir_0514: st.st_mode=%o", st.st_mode);
415 assertEqualInt(st.st_mode & 07777, 0514);
417 assertEqualInt(0, stat("dir_overwrite_0744", &st));
418 failure("dir_overwrite_0744: st.st_mode=%o", st.st_mode);
419 assertEqualInt(st.st_mode & 0777, 0744);
421 assertEqualInt(0, stat("file_no_suid", &st));
422 failure("file_0755: st.st_mode=%o", st.st_mode);
423 assertEqualInt(st.st_mode & 07777, 0755);
425 assertEqualInt(0, stat("file_0777", &st));
426 failure("file_0777: st.st_mode=%o", st.st_mode);
427 assertEqualInt(st.st_mode & 07777, 0777);
429 /* SUID bit should get set here. */
430 assertEqualInt(0, stat("file_4742", &st));
431 failure("file_4742: st.st_mode=%o", st.st_mode);
432 assertEqualInt(st.st_mode & 07777, S_ISUID | 0742);
434 /* SUID bit should NOT have been set here. */
435 assertEqualInt(0, stat("file_bad_suid", &st));
436 failure("file_bad_suid: st.st_mode=%o", st.st_mode);
437 assertEqualInt(st.st_mode & 07777, 0742);
439 /* Some things don't fail if you're root, so suppress this. */
441 /* SUID bit should NOT have been set here. */
442 assertEqualInt(0, stat("file_bad_suid2", &st));
443 failure("file_bad_suid2: st.st_mode=%o", st.st_mode);
444 assertEqualInt(st.st_mode & 07777, 0742);
447 /* SGID should be set here. */
448 assertEqualInt(0, stat("file_perm_sgid", &st));
449 failure("file_perm_sgid: st.st_mode=%o", st.st_mode);
450 assertEqualInt(st.st_mode & 07777, S_ISGID | 0742);
452 if (altgid() != -1) {
453 /* SGID should not be set here. */
454 assertEqualInt(0, stat("file_alt_sgid", &st));
455 failure("file_alt_sgid: st.st_mode=%o", st.st_mode);
456 assertEqualInt(st.st_mode & 07777, 0742);
458 /* SGID should be set here. */
459 assertEqualInt(0, stat("file_alt_sgid_owner", &st));
460 failure("file_alt_sgid: st.st_mode=%o", st.st_mode);
461 assertEqualInt(st.st_mode & 07777, S_ISGID | 0742);
464 if (invalidgid() != -1) {
465 /* SGID should NOT be set here. */
466 assertEqualInt(0, stat("file_bad_sgid", &st));
467 failure("file_bad_sgid: st.st_mode=%o", st.st_mode);
468 assertEqualInt(st.st_mode & 07777, 0742);
469 /* SGID should NOT be set here. */
470 assertEqualInt(0, stat("file_bad_sgid2", &st));
471 failure("file_bad_sgid2: st.st_mode=%o", st.st_mode);
472 assertEqualInt(st.st_mode & 07777, 0742);
476 assertEqualInt(0, stat("file_bad_owner", &st));
477 failure("file_bad_owner: st.st_mode=%o", st.st_mode);
478 assertEqualInt(st.st_mode & 07777, 0744);
479 failure("file_bad_owner: st.st_uid=%d getuid()=%d",
480 st.st_uid, getuid());
481 /* The entry had getuid()+1, but because we're
482 * not root, we should not have been able to set that. */
483 assertEqualInt(st.st_uid, getuid());