/*- * Copyright (c) 2003-2007 Tim Kientzle * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "test.h" __FBSDID("$FreeBSD$"); #define UMASK 022 static gid_t _default_gid = 0; static gid_t _invalid_gid = 0; static gid_t _alt_gid = 0; /* * To fully test SGID restores, we need three distinct GIDs to work * with: * * the GID that files are created with by default (for the * current user in the current directory) * * An "alt gid" that this user can create files with * * An "invalid gid" that this user is not permitted to create * files with. * The second fails if this user doesn't belong to at least two groups; * the third fails if the current user is root. */ static int searchgid(void) { static int _searched = 0; uid_t uid = getuid(); gid_t gid = 0; int n; struct stat st; int fd; /* If we've already looked this up, we're done. */ if (_searched) return; _searched = 1; /* Create a file on disk in the current default dir. */ fd = open("test_gid", O_CREAT, 0664); failure("Couldn't create a file for gid testing."); assert(fd > 0); /* See what GID it ended up with. This is our "valid" GID. */ assert(fstat(fd, &st) == 0); _default_gid = st.st_gid; /* Find a GID for which fchown() fails. This is our "invalid" GID. */ _invalid_gid = 0; /* This loop stops when we wrap the gid or examine 10,000 gids. */ for (gid = 1, n = 1; gid == n && n < 10000 ; n++, gid++) { if (fchown(fd, uid, gid) != 0) { _invalid_gid = gid; break; } } /* * Find a GID for which fchown() succeeds, but which isn't the * default. This is the "alternate" gid. */ _alt_gid = 0; for (gid = 1, n = 1; gid == n && n < 10000 ; n++, gid++) { /* _alt_gid must be different than _default_gid */ if (gid == _default_gid) continue; if (fchown(fd, uid, gid) == 0) { _alt_gid = gid; break; } } close(fd); } static int altgid(void) { searchgid(); return (_alt_gid); } static int invalidgid(void) { searchgid(); return (_invalid_gid); } static int defaultgid(void) { searchgid(); return (_default_gid); } /* * Exercise permission and ownership restores. * In particular, try to exercise a bunch of border cases related * to files/dirs that already exist, SUID/SGID bits, etc. */ DEFINE_TEST(test_write_disk_perms) { struct archive *a; struct archive_entry *ae; struct stat st; /* Create an archive_write_disk object. */ assert((a = archive_write_disk_new()) != NULL); /* Write a regular file to it. */ assert((ae = archive_entry_new()) != NULL); archive_entry_copy_pathname(ae, "file_0755"); archive_entry_set_mode(ae, S_IFREG | 0777); assert(0 == archive_write_header(a, ae)); assert(0 == archive_write_finish_entry(a)); /* Write a regular file, then write over it. */ /* For files, the perms should get updated. */ assert((ae = archive_entry_new()) != NULL); archive_entry_copy_pathname(ae, "file_overwrite_0144"); archive_entry_set_mode(ae, S_IFREG | 0777); assert(0 == archive_write_header(a, ae)); assert(0 == archive_write_finish_entry(a)); /* Check that file was created with different perms. */ assert(0 == stat("file_overwrite_0144", &st)); failure("file_overwrite_0144: st.st_mode=%o", st.st_mode); assert((st.st_mode & 07777) != 0144); /* Overwrite, this should change the perms. */ assert((ae = archive_entry_new()) != NULL); archive_entry_copy_pathname(ae, "file_overwrite_0144"); archive_entry_set_mode(ae, S_IFREG | 0144); assert(0 == archive_write_header(a, ae)); assert(0 == archive_write_finish_entry(a)); /* Write a regular dir. */ assert((ae = archive_entry_new()) != NULL); archive_entry_copy_pathname(ae, "dir_0514"); archive_entry_set_mode(ae, S_IFDIR | 0514); assert(0 == archive_write_header(a, ae)); assert(0 == archive_write_finish_entry(a)); /* Overwrite an existing dir. */ /* For dir, the first perms should get left. */ assert(mkdir("dir_overwrite_0744", 0744) == 0); /* Check original perms. */ assert(0 == stat("dir_overwrite_0744", &st)); failure("dir_overwrite_0744: st.st_mode=%o", st.st_mode); assert((st.st_mode & 07777) == 0744); /* Overwrite shouldn't edit perms. */ assert((ae = archive_entry_new()) != NULL); archive_entry_copy_pathname(ae, "dir_overwrite_0744"); archive_entry_set_mode(ae, S_IFDIR | 0777); assert(0 == archive_write_header(a, ae)); assert(0 == archive_write_finish_entry(a)); /* Make sure they're unchanged. */ assert(0 == stat("dir_overwrite_0744", &st)); failure("dir_overwrite_0744: st.st_mode=%o", st.st_mode); assert((st.st_mode & 07777) == 0744); /* Write a regular file with SUID bit, but don't use _EXTRACT_PERM. */ assert((ae = archive_entry_new()) != NULL); archive_entry_copy_pathname(ae, "file_no_suid"); archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0777); archive_write_disk_set_options(a, 0); assert(0 == archive_write_header(a, ae)); assert(0 == archive_write_finish_entry(a)); /* Write a regular file with ARCHIVE_EXTRACT_PERM. */ assert(archive_entry_clear(ae) != NULL); archive_entry_copy_pathname(ae, "file_0777"); archive_entry_set_mode(ae, S_IFREG | 0777); archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM); assert(0 == archive_write_header(a, ae)); assert(0 == archive_write_finish_entry(a)); /* Write a regular file with ARCHIVE_EXTRACT_PERM & SUID bit */ assert(archive_entry_clear(ae) != NULL); archive_entry_copy_pathname(ae, "file_4742"); archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0742); archive_entry_set_uid(ae, getuid()); archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM); assert(0 == archive_write_header(a, ae)); assert(0 == archive_write_finish_entry(a)); /* * Write a regular file with ARCHIVE_EXTRACT_PERM & SUID bit, * but wrong uid. POSIX says you shouldn't restore SUID bit * unless the UID could be restored. */ assert(archive_entry_clear(ae) != NULL); archive_entry_copy_pathname(ae, "file_bad_suid"); archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0742); archive_entry_set_uid(ae, getuid() + 1); archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM); assertA(0 == archive_write_header(a, ae)); assertA(ARCHIVE_WARN == archive_write_finish_entry(a)); /* Write a regular file with ARCHIVE_EXTRACT_PERM & SGID bit */ assert(archive_entry_clear(ae) != NULL); archive_entry_copy_pathname(ae, "file_perm_sgid"); archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742); archive_entry_set_gid(ae, defaultgid()); archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM); assert(0 == archive_write_header(a, ae)); failure("Setting SGID bit should succeed here."); assertEqualIntA(a, 0, archive_write_finish_entry(a)); if (altgid() == 0) { /* * Current user must belong to at least two groups or * else we can't test setting the GID to another group. */ printf("Current user can't test gid restore: must belong to more than one group.\n"); } else { /* Write a regular file with ARCHIVE_EXTRACT_PERM & SGID bit */ /* * This is a weird case: The user has asked for permissions to * be restored but not asked for ownership to be restored. As * a result, the default file creation will create a file with * the wrong group. There are two reasonable behaviors: warn * and drop the SGID bit (the current libarchive behavior) or * try to set the group. It is completely wrong to set the * SGID bit with the wrong group (which is, incidentally, * exactly what gtar 1.15 does). */ assert(archive_entry_clear(ae) != NULL); archive_entry_copy_pathname(ae, "file_alt_sgid"); archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742); archive_entry_set_uid(ae, getuid()); archive_entry_set_gid(ae, altgid()); archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM); assert(0 == archive_write_header(a, ae)); failure("Setting SGID bit should not succeed here."); assertEqualIntA(a, ARCHIVE_WARN, archive_write_finish_entry(a)); /* As above, but add _EXTRACT_OWNER. */ assert(archive_entry_clear(ae) != NULL); archive_entry_copy_pathname(ae, "file_alt_sgid_owner"); archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742); archive_entry_set_uid(ae, getuid()); archive_entry_set_gid(ae, altgid()); archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_OWNER); assert(0 == archive_write_header(a, ae)); failure("Setting SGID bit should succeed here."); assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a)); } /* * Write a regular file with ARCHIVE_EXTRACT_PERM & SGID bit, * but wrong GID. POSIX says you shouldn't restore SGID bit * unless the GID could be restored. */ if (invalidgid() == 0) { /* This test always fails for root. */ printf("Running as root: Can't test SGID failures.\n"); } else { assert(archive_entry_clear(ae) != NULL); archive_entry_copy_pathname(ae, "file_bad_sgid"); archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742); archive_entry_set_gid(ae, invalidgid()); archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM); assertA(0 == archive_write_header(a, ae)); failure("This SGID restore should fail."); assertEqualIntA(a, ARCHIVE_WARN, archive_write_finish_entry(a)); } /* Set ownership should fail if we're not root. */ if (getuid() == 0) { printf("Running as root: Can't test setuid failures.\n"); } else { assert(archive_entry_clear(ae) != NULL); archive_entry_copy_pathname(ae, "file_bad_owner"); archive_entry_set_mode(ae, S_IFREG | 0744); archive_entry_set_uid(ae, getuid() + 1); archive_write_disk_set_options(a, ARCHIVE_EXTRACT_OWNER); assertA(0 == archive_write_header(a, ae)); assertEqualIntA(a,ARCHIVE_WARN,archive_write_finish_entry(a)); } #if ARCHIVE_API_VERSION > 1 assert(0 == archive_write_finish(a)); #else archive_write_finish(a); #endif /* Test the entries on disk. */ assert(0 == stat("file_0755", &st)); failure("file_0755: st.st_mode=%o", st.st_mode); assert((st.st_mode & 07777) == 0755); assert(0 == stat("file_overwrite_0144", &st)); failure("file_overwrite_0144: st.st_mode=%o", st.st_mode); assert((st.st_mode & 07777) == 0144); assert(0 == stat("dir_0514", &st)); failure("dir_0514: st.st_mode=%o", st.st_mode); assert((st.st_mode & 07777) == 0514); assert(0 == stat("dir_overwrite_0744", &st)); failure("dir_overwrite_0744: st.st_mode=%o", st.st_mode); assert((st.st_mode & 07777) == 0744); assert(0 == stat("file_no_suid", &st)); failure("file_0755: st.st_mode=%o", st.st_mode); assert((st.st_mode & 07777) == 0755); assert(0 == stat("file_0777", &st)); failure("file_0777: st.st_mode=%o", st.st_mode); assert((st.st_mode & 07777) == 0777); /* SUID bit should get set here. */ assert(0 == stat("file_4742", &st)); failure("file_4742: st.st_mode=%o", st.st_mode); assert((st.st_mode & 07777) == (S_ISUID | 0742)); /* SUID bit should NOT have been set here. */ assert(0 == stat("file_bad_suid", &st)); failure("file_bad_suid: st.st_mode=%o", st.st_mode); assert((st.st_mode & 07777) == (0742)); /* SGID should be set here. */ assert(0 == stat("file_perm_sgid", &st)); failure("file_perm_sgid: st.st_mode=%o", st.st_mode); assert((st.st_mode & 07777) == (S_ISGID | 0742)); if (altgid() != 0) { /* SGID should not be set here. */ assert(0 == stat("file_alt_sgid", &st)); failure("file_alt_sgid: st.st_mode=%o", st.st_mode); assert((st.st_mode & 07777) == (0742)); /* SGID should be set here. */ assert(0 == stat("file_alt_sgid_owner", &st)); failure("file_alt_sgid: st.st_mode=%o", st.st_mode); assert((st.st_mode & 07777) == (S_ISGID | 0742)); } if (invalidgid() != 0) { /* SGID should NOT be set here. */ assert(0 == stat("file_bad_sgid", &st)); failure("file_bad_sgid: st.st_mode=%o", st.st_mode); assert((st.st_mode & 07777) == (0742)); } if (getuid() != 0) { assert(0 == stat("file_bad_owner", &st)); failure("file_bad_owner: st.st_mode=%o", st.st_mode); assert((st.st_mode & 07777) == (0744)); failure("file_bad_owner: st.st_uid=%d getuid()=%d", st.st_uid, getuid()); /* The entry had getuid()+1, but because we're * not root, we should not have been able to set that. */ assert(st.st_uid == getuid()); } }