You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
458 lines
18 KiB
458 lines
18 KiB
/*-
|
|
* 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: src/lib/libarchive/test/test_write_disk_perms.c,v 1.11 2008/12/06 06:01:50 kientzle Exp $");
|
|
|
|
#if ARCHIVE_VERSION_NUMBER >= 1009000 && (!defined(_WIN32) || defined(__CYGWIN__))
|
|
|
|
#define UMASK 022
|
|
|
|
static long _default_gid = -1;
|
|
static long _invalid_gid = -1;
|
|
static long _alt_gid = -1;
|
|
|
|
/*
|
|
* 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 void
|
|
searchgid(void)
|
|
{
|
|
static int _searched = 0;
|
|
uid_t uid = getuid();
|
|
gid_t gid = 0;
|
|
unsigned 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 | O_BINARY, 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 = -1;
|
|
/* 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 = -1;
|
|
for (gid = 0, n = 0; gid == n && n < 10000 ; n++, gid++) {
|
|
/* _alt_gid must be different than _default_gid */
|
|
if (gid == (gid_t)_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);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
#if ARCHIVE_VERSION_NUMBER < 1009000 || (defined(_WIN32) && !defined(__CYGWIN__))
|
|
skipping("archive_write_disk interface");
|
|
#else
|
|
struct archive *a;
|
|
struct archive_entry *ae;
|
|
struct stat st;
|
|
|
|
assertUmask(UMASK);
|
|
|
|
/*
|
|
* Set ownership of the current directory to the group of this
|
|
* process. Otherwise, the SGID tests below fail if the
|
|
* /tmp directory is owned by a group to which we don't belong
|
|
* and we're on a system where group ownership is inherited.
|
|
* (Because we're not allowed to SGID files with defaultgid().)
|
|
*/
|
|
assertEqualInt(0, chown(".", getuid(), getgid()));
|
|
|
|
/* 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));
|
|
archive_entry_free(ae);
|
|
|
|
/* 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));
|
|
archive_entry_free(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));
|
|
archive_entry_free(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));
|
|
archive_entry_free(ae);
|
|
assert(0 == archive_write_finish_entry(a));
|
|
|
|
/* Overwrite an existing dir. */
|
|
/* For dir, the first perms should get left. */
|
|
assertMakeDir("dir_overwrite_0744", 0744);
|
|
/* 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 & 0777) == 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));
|
|
archive_entry_free(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 & 0777) == 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));
|
|
/*
|
|
* Because we didn't ask for owner, the failure to
|
|
* restore SUID shouldn't return a failure.
|
|
* We check below to make sure SUID really wasn't set.
|
|
* See more detailed comments below.
|
|
*/
|
|
failure("Opportunistic SUID failure shouldn't return error.");
|
|
assertEqualInt(0, archive_write_finish_entry(a));
|
|
|
|
if (getuid() != 0) {
|
|
assert(archive_entry_clear(ae) != NULL);
|
|
archive_entry_copy_pathname(ae, "file_bad_suid2");
|
|
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 | ARCHIVE_EXTRACT_OWNER);
|
|
assertA(0 == archive_write_header(a, ae));
|
|
/* Owner change should fail here. */
|
|
failure("Non-opportunistic SUID failure should return error.");
|
|
assertEqualInt(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() == -1) {
|
|
/*
|
|
* Current user must belong to at least two groups or
|
|
* else we can't test setting the GID to another group.
|
|
*/
|
|
skipping("Current user can't test gid restore: must belong to more than one group.");
|
|
} else {
|
|
/*
|
|
* Write a regular file with ARCHIVE_EXTRACT_PERM & SGID bit
|
|
* but without ARCHIVE_EXTRACT_OWNER.
|
|
*/
|
|
/*
|
|
* 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 several possible behaviors for
|
|
* libarchive in this scenario:
|
|
* = Set the SGID bit. It is wrong and a security hole to
|
|
* set SGID with the wrong group. Even POSIX thinks so.
|
|
* = Implicitly set the group. I don't like this.
|
|
* = drop the SGID bit and warn (the old libarchive behavior)
|
|
* = drop the SGID bit and don't warn (the current libarchive
|
|
* behavior).
|
|
* The current behavior sees SGID/SUID restore when you
|
|
* don't ask for owner restore as an "opportunistic"
|
|
* action. That is, libarchive should do it if it can,
|
|
* but if it can't, it's not an error.
|
|
*/
|
|
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 fail because of group mismatch but the failure should be silent because we didn't ask for the group to be set.");
|
|
assertEqualIntA(a, 0, archive_write_finish_entry(a));
|
|
|
|
/*
|
|
* As above, but add _EXTRACT_OWNER to verify that it
|
|
* does succeed.
|
|
*/
|
|
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() == -1) {
|
|
/* 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 without an error.");
|
|
assertEqualIntA(a, 0, archive_write_finish_entry(a));
|
|
|
|
assert(archive_entry_clear(ae) != NULL);
|
|
archive_entry_copy_pathname(ae, "file_bad_sgid2");
|
|
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 | ARCHIVE_EXTRACT_OWNER);
|
|
assertA(0 == archive_write_header(a, ae));
|
|
failure("This SGID restore should fail with an error.");
|
|
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_VERSION_NUMBER < 2000000
|
|
archive_write_finish(a);
|
|
#else
|
|
assert(0 == archive_write_finish(a));
|
|
#endif
|
|
archive_entry_free(ae);
|
|
|
|
/* 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 & 0777) == 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));
|
|
|
|
/* Some things don't fail if you're root, so suppress this. */
|
|
if (getuid() != 0) {
|
|
/* SUID bit should NOT have been set here. */
|
|
assert(0 == stat("file_bad_suid2", &st));
|
|
failure("file_bad_suid2: 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() != -1) {
|
|
/* 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() != -1) {
|
|
/* 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));
|
|
/* SGID should NOT be set here. */
|
|
assert(0 == stat("file_bad_sgid2", &st));
|
|
failure("file_bad_sgid2: 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());
|
|
}
|
|
#endif
|
|
}
|