From owner-svn-src-projects@freebsd.org Tue Sep 3 14:05:50 2019 Return-Path: Delivered-To: svn-src-projects@mailman.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mailman.nyi.freebsd.org (Postfix) with ESMTP id 6BCC5DBF32 for ; Tue, 3 Sep 2019 14:05:50 +0000 (UTC) (envelope-from yuripv@freebsd.org) Received: from freefall.freebsd.org (freefall.freebsd.org [96.47.72.132]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) server-signature RSA-PSS (4096 bits) client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "freefall.freebsd.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 46N7yj4cYCz4NtM; Tue, 3 Sep 2019 14:05:49 +0000 (UTC) (envelope-from yuripv@freebsd.org) Received: by freefall.freebsd.org (Postfix, from userid 1452) id A8E8019C51; Tue, 3 Sep 2019 14:05:48 +0000 (UTC) X-Original-To: yuripv@localmail.freebsd.org Delivered-To: yuripv@localmail.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [96.47.72.80]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) (Client CN "mx1.freebsd.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by freefall.freebsd.org (Postfix) with ESMTPS id 082D218FA7; Thu, 28 Mar 2019 19:56:40 +0000 (UTC) (envelope-from owner-src-committers@freebsd.org) Received: from freefall.freebsd.org (freefall.freebsd.org [96.47.72.132]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) server-signature RSA-PSS (4096 bits) client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "freefall.freebsd.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 658AE88E0C; Thu, 28 Mar 2019 19:56:39 +0000 (UTC) (envelope-from owner-src-committers@freebsd.org) Received: by freefall.freebsd.org (Postfix, from userid 538) id 11BFC18F40; Thu, 28 Mar 2019 19:56:39 +0000 (UTC) Delivered-To: src-committers@localmail.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [96.47.72.80]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) (Client CN "mx1.freebsd.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by freefall.freebsd.org (Postfix) with ESMTPS id 68F6518F3C for ; Thu, 28 Mar 2019 19:56:36 +0000 (UTC) (envelope-from ngie@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) server-signature RSA-PSS (4096 bits) client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 1E53E88E05; Thu, 28 Mar 2019 19:56:36 +0000 (UTC) (envelope-from ngie@FreeBSD.org) Received: from repo.freebsd.org (repo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id E639D23F76; Thu, 28 Mar 2019 19:56:35 +0000 (UTC) (envelope-from ngie@FreeBSD.org) Received: from repo.freebsd.org ([127.0.1.37]) by repo.freebsd.org (8.15.2/8.15.2) with ESMTP id x2SJuZlX059088; Thu, 28 Mar 2019 19:56:35 GMT (envelope-from ngie@FreeBSD.org) Received: (from ngie@localhost) by repo.freebsd.org (8.15.2/8.15.2/Submit) id x2SJuZwS059085; Thu, 28 Mar 2019 19:56:35 GMT (envelope-from ngie@FreeBSD.org) Message-Id: <201903281956.x2SJuZwS059085@repo.freebsd.org> X-Authentication-Warning: repo.freebsd.org: ngie set sender to ngie@FreeBSD.org using -f From: Enji Cooper To: src-committers@freebsd.org, svn-src-projects@freebsd.org Subject: svn commit: r345649 - projects/capsicum-test/contrib/capsicum-test X-SVN-Group: projects X-SVN-Commit-Author: ngie X-SVN-Commit-Paths: projects/capsicum-test/contrib/capsicum-test X-SVN-Commit-Revision: 345649 X-SVN-Commit-Repository: base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Precedence: bulk X-Loop: FreeBSD.org Sender: owner-src-committers@freebsd.org X-Rspamd-Queue-Id: 658AE88E0C X-Spamd-Bar: -- Authentication-Results: mx1.freebsd.org X-Spamd-Result: default: False [-2.94 / 15.00]; local_wl_from(0.00)[freebsd.org]; NEURAL_HAM_MEDIUM(-0.99)[-0.994,0]; NEURAL_HAM_SHORT(-0.95)[-0.949,0]; NEURAL_HAM_LONG(-1.00)[-1.000,0]; ASN(0.00)[asn:11403, ipnet:96.47.64.0/20, country:US] Status: O X-BeenThere: svn-src-projects@freebsd.org X-Mailman-Version: 2.1.29 List-Id: "SVN commit messages for the src " projects" tree" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Date: Tue, 03 Sep 2019 14:05:50 -0000 X-Original-Date: Thu, 28 Mar 2019 19:56:35 +0000 (UTC) X-List-Received-Date: Tue, 03 Sep 2019 14:05:50 -0000 Author: ngie Date: Thu Mar 28 19:56:35 2019 New Revision: 345649 URL: https://svnweb.freebsd.org/changeset/base/345649 Log: Allow `capsicum-test` to be executed from an absolute path by stashing the execution directory This change stashes the executing directory for `capsicum-test` then uses it to determine where `mini-me*` lives, allowing the script to be executed from an absolute path. This change required refactoring how the fexecve tests were executed, as the path for `mini-me*` could no longer be determined at compile-time, but needs to be determined at runtime. As such, switch from FORK_TEST to FORK_TEST_F (with an appropriate class) and FORK_TEST_ON to FORK_F with proper setup/teardown fixtures for cleaning up the temporary script. Modified: projects/capsicum-test/contrib/capsicum-test/capsicum-test-main.cc projects/capsicum-test/contrib/capsicum-test/capsicum.h projects/capsicum-test/contrib/capsicum-test/fexecve.cc Modified: projects/capsicum-test/contrib/capsicum-test/capsicum-test-main.cc ============================================================================== --- projects/capsicum-test/contrib/capsicum-test/capsicum-test-main.cc Thu Mar 28 19:46:59 2019 (r345648) +++ projects/capsicum-test/contrib/capsicum-test/capsicum-test-main.cc Thu Mar 28 19:56:35 2019 (r345649) @@ -5,9 +5,11 @@ #endif #include #include +#include #include #include #include +#include #include #include "gtest/gtest.h" #include "capsicum-test.h" @@ -47,7 +49,27 @@ class SetupEnvironment : public ::testing::Environment bool teardown_tmpdir_; }; +std::string capsicum_test_bindir; + int main(int argc, char* argv[]) { + // Set up the test program path, so capsicum-test can find programs, like + // mini-me* when executed from an absolute path. + { + char *new_path, *old_path, *program_name; + + program_name = strdup(argv[0]); + assert(program_name); + capsicum_test_bindir = std::string(dirname(program_name)); + free(program_name); + + old_path = getenv("PATH"); + assert(old_path); + + assert(asprintf(&new_path, "%s:%s", capsicum_test_bindir.c_str(), + old_path) > 0); + assert(setenv("PATH", new_path, 1) == 0); + } + ::testing::InitGoogleTest(&argc, argv); for (int ii = 1; ii < argc; ii++) { if (strcmp(argv[ii], "-v") == 0) { Modified: projects/capsicum-test/contrib/capsicum-test/capsicum.h ============================================================================== --- projects/capsicum-test/contrib/capsicum-test/capsicum.h Thu Mar 28 19:46:59 2019 (r345648) +++ projects/capsicum-test/contrib/capsicum-test/capsicum.h Thu Mar 28 19:56:35 2019 (r345649) @@ -167,4 +167,9 @@ static inline void cap_rights_describe(const cap_right #endif /* new/old style rights manipulation */ +#ifdef __cplusplus +#include +extern std::string capsicum_test_bindir; +#endif + #endif /*__CAPSICUM_H__*/ Modified: projects/capsicum-test/contrib/capsicum-test/fexecve.cc ============================================================================== --- projects/capsicum-test/contrib/capsicum-test/fexecve.cc Thu Mar 28 19:46:59 2019 (r345648) +++ projects/capsicum-test/contrib/capsicum-test/fexecve.cc Thu Mar 28 19:56:35 2019 (r345649) @@ -1,12 +1,12 @@ -#include -#include #include #include #include +#include #include -#include #include #include +#include +#include #include @@ -14,41 +14,76 @@ #include "capsicum.h" #include "capsicum-test.h" -// We need a program to exec(), but for fexecve() to work in capability -// mode that program needs to be statically linked (otherwise ld.so will -// attempt to traverse the filesystem to load (e.g.) /lib/libc.so and -// fail). -#define EXEC_PROG "./mini-me" -#define EXEC_PROG_NOEXEC EXEC_PROG ".noexec" -#define EXEC_PROG_SETUID EXEC_PROG ".setuid" - // Arguments to use in execve() calls. -static char* argv_pass[] = {(char*)EXEC_PROG, (char*)"--pass", NULL}; -static char* argv_fail[] = {(char*)EXEC_PROG, (char*)"--fail", NULL}; -static char* argv_checkroot[] = {(char*)EXEC_PROG, (char*)"--checkroot", NULL}; static char* null_envp[] = {NULL}; class Execve : public ::testing::Test { public: - Execve() : exec_fd_(open(EXEC_PROG, O_RDONLY)) { + Execve() : exec_fd_(-1) { + // We need a program to exec(), but for fexecve() to work in capability + // mode that program needs to be statically linked (otherwise ld.so will + // attempt to traverse the filesystem to load (e.g.) /lib/libc.so and + // fail). + exec_prog_ = capsicum_test_bindir + "/mini-me"; + exec_prog_noexec_ = capsicum_test_bindir + "/mini-me.noexec"; + exec_prog_setuid_ = capsicum_test_bindir + "/mini-me.setuid"; + + exec_fd_ = open(exec_prog_.c_str(), O_RDONLY); if (exec_fd_ < 0) { - fprintf(stderr, "Error! Failed to open %s\n", EXEC_PROG); + fprintf(stderr, "Error! Failed to open %s\n", exec_prog_.c_str()); } + argv_checkroot_[0] = (char*)exec_prog_.c_str(); + argv_fail_[0] = (char*)exec_prog_.c_str(); + argv_pass_[0] = (char*)exec_prog_.c_str(); } - ~Execve() { if (exec_fd_ >= 0) close(exec_fd_); } + ~Execve() { + if (exec_fd_ >= 0) { + close(exec_fd_); + exec_fd_ = -1; + } + } protected: + char* argv_checkroot_[3] = {nullptr, (char*)"--checkroot", nullptr}; + char* argv_fail_[3] = {nullptr, (char*)"--fail", nullptr}; + char* argv_pass_[3] = {nullptr, (char*)"--pass", nullptr}; + std::string exec_prog_, exec_prog_noexec_, exec_prog_setuid_; int exec_fd_; }; +class Fexecve : public Execve { + public: + Fexecve() : Execve() {} +}; + +class FexecveWithScript : public Fexecve { + public: + FexecveWithScript() : + Fexecve(), temp_script_filename_(TmpFile("cap_sh_script")) {} + + void SetUp() override { + // First, build an executable shell script + int fd = open(temp_script_filename_, O_RDWR|O_CREAT, 0755); + EXPECT_OK(fd); + const char* contents = "#!/bin/sh\nexit 99\n"; + EXPECT_OK(write(fd, contents, strlen(contents))); + close(fd); + } + void TearDown() override { + (void)::unlink(temp_script_filename_); + } + + const char *temp_script_filename_; +}; + FORK_TEST_F(Execve, BasicFexecve) { - EXPECT_OK(fexecve_(exec_fd_, argv_pass, null_envp)); + EXPECT_OK(fexecve_(exec_fd_, argv_pass_, null_envp)); // Should not reach here, exec() takes over. EXPECT_TRUE(!"fexecve() should never return"); } FORK_TEST_F(Execve, InCapMode) { EXPECT_OK(cap_enter()); - EXPECT_OK(fexecve_(exec_fd_, argv_pass, null_envp)); + EXPECT_OK(fexecve_(exec_fd_, argv_pass_, null_envp)); // Should not reach here, exec() takes over. EXPECT_TRUE(!"fexecve() should never return"); } @@ -60,7 +95,7 @@ FORK_TEST_F(Execve, FailWithoutCap) { cap_rights_t rights; cap_rights_init(&rights, 0); EXPECT_OK(cap_rights_limit(cap_fd, &rights)); - EXPECT_EQ(-1, fexecve_(cap_fd, argv_fail, null_envp)); + EXPECT_EQ(-1, fexecve_(cap_fd, argv_fail_, null_envp)); EXPECT_EQ(ENOTCAPABLE, errno); } @@ -73,59 +108,54 @@ FORK_TEST_F(Execve, SucceedWithCap) { // rights -- just CAP_FEXECVE|CAP_READ or CAP_FEXECVE would be preferable. cap_rights_init(&rights, CAP_FEXECVE, CAP_LOOKUP, CAP_READ); EXPECT_OK(cap_rights_limit(cap_fd, &rights)); - EXPECT_OK(fexecve_(cap_fd, argv_pass, null_envp)); + EXPECT_OK(fexecve_(cap_fd, argv_pass_, null_envp)); // Should not reach here, exec() takes over. EXPECT_TRUE(!"fexecve() should have succeeded"); } -FORK_TEST(Fexecve, ExecutePermissionCheck) { - int fd = open(EXEC_PROG_NOEXEC, O_RDONLY); +FORK_TEST_F(Fexecve, ExecutePermissionCheck) { + int fd = open(exec_prog_noexec_.c_str(), O_RDONLY); EXPECT_OK(fd); if (fd >= 0) { struct stat data; EXPECT_OK(fstat(fd, &data)); EXPECT_EQ((mode_t)0, data.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)); - EXPECT_EQ(-1, fexecve_(fd, argv_fail, null_envp)); + EXPECT_EQ(-1, fexecve_(fd, argv_fail_, null_envp)); EXPECT_EQ(EACCES, errno); close(fd); } } -FORK_TEST(Fexecve, SetuidIgnored) { +FORK_TEST_F(Fexecve, SetuidIgnored) { if (geteuid() == 0) { TEST_SKIPPED("requires non-root"); return; } - int fd = open(EXEC_PROG_SETUID, O_RDONLY); + int fd = open(exec_prog_setuid_.c_str(), O_RDONLY); EXPECT_OK(fd); EXPECT_OK(cap_enter()); if (fd >= 0) { struct stat data; EXPECT_OK(fstat(fd, &data)); EXPECT_EQ((mode_t)S_ISUID, data.st_mode & S_ISUID); - EXPECT_OK(fexecve_(fd, argv_checkroot, null_envp)); + EXPECT_OK(fexecve_(fd, argv_checkroot_, null_envp)); // Should not reach here, exec() takes over. EXPECT_TRUE(!"fexecve() should have succeeded"); close(fd); } } -FORK_TEST(Fexecve, ExecveFailure) { +FORK_TEST_F(Fexecve, ExecveFailure) { EXPECT_OK(cap_enter()); - EXPECT_EQ(-1, execve(argv_fail[0], argv_fail, null_envp)); + EXPECT_EQ(-1, execve(argv_fail_[0], argv_fail_, null_envp)); EXPECT_EQ(ECAPMODE, errno); } -FORK_TEST_ON(Fexecve, CapModeScriptFail, TmpFile("cap_sh_script")) { - // First, build an executable shell script - int fd = open(TmpFile("cap_sh_script"), O_RDWR|O_CREAT, 0755); - EXPECT_OK(fd); - const char* contents = "#!/bin/sh\nexit 99\n"; - EXPECT_OK(write(fd, contents, strlen(contents))); - close(fd); +FORK_TEST_F(FexecveWithScript, CapModeScriptFail) { + int fd; // Open the script file, with CAP_FEXECVE rights. - fd = open(TmpFile("cap_sh_script"), O_RDONLY); + fd = open(temp_script_filename_, O_RDONLY); cap_rights_t rights; cap_rights_init(&rights, CAP_FEXECVE, CAP_READ, CAP_SEEK); EXPECT_OK(cap_rights_limit(fd, &rights)); @@ -133,12 +163,12 @@ FORK_TEST_ON(Fexecve, CapModeScriptFail, TmpFile("cap_ EXPECT_OK(cap_enter()); // Enter capability mode // Attempt fexecve; should fail, because "/bin/sh" is inaccessible. - EXPECT_EQ(-1, fexecve_(fd, argv_pass, null_envp)); + EXPECT_EQ(-1, fexecve_(fd, argv_pass_, null_envp)); } #ifdef HAVE_EXECVEAT TEST(Execveat, NoUpwardTraversal) { - char *abspath = realpath(EXEC_PROG, NULL); + char *abspath = realpath(exec_prog_, NULL); char cwd[1024]; getcwd(cwd, sizeof(cwd)); @@ -148,9 +178,9 @@ TEST(Execveat, NoUpwardTraversal) { EXPECT_OK(cap_enter()); // Enter capability mode. // Can't execveat() an absolute path, even relative to a dfd. EXPECT_SYSCALL_FAIL(ECAPMODE, - execveat(AT_FDCWD, abspath, argv_pass, null_envp, 0)); + execveat(AT_FDCWD, abspath, argv_pass_, null_envp, 0)); EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY, - execveat(dfd, abspath, argv_pass, null_envp, 0)); + execveat(dfd, abspath, argv_pass_, null_envp, 0)); // Can't execveat() a relative path ("..//./"). char *p = cwd + strlen(cwd); @@ -158,9 +188,9 @@ TEST(Execveat, NoUpwardTraversal) { char buffer[1024] = "../"; strcat(buffer, ++p); strcat(buffer, "/"); - strcat(buffer, EXEC_PROG); + strcat(buffer, exec_prog_); EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY, - execveat(dfd, buffer, argv_pass, null_envp, 0)); + execveat(dfd, buffer, argv_pass_, null_envp, 0)); exit(HasFailure() ? 99 : 123); } int status;