xref: /freebsd/contrib/capsicum-test/fexecve.cc (revision 2d936e6c)
18ac5aef8SEnji Cooper #include <sys/types.h>
28ac5aef8SEnji Cooper #include <sys/stat.h>
3*2d936e6cSAlex Richardson #include <sys/wait.h>
48ac5aef8SEnji Cooper #include <errno.h>
58ac5aef8SEnji Cooper #include <fcntl.h>
68ac5aef8SEnji Cooper #include <limits.h>
78ac5aef8SEnji Cooper #include <stdlib.h>
88ac5aef8SEnji Cooper #include <string.h>
98ac5aef8SEnji Cooper #include <unistd.h>
108ac5aef8SEnji Cooper 
118ac5aef8SEnji Cooper #include <sstream>
128ac5aef8SEnji Cooper 
138ac5aef8SEnji Cooper #include "syscalls.h"
148ac5aef8SEnji Cooper #include "capsicum.h"
158ac5aef8SEnji Cooper #include "capsicum-test.h"
168ac5aef8SEnji Cooper 
178ac5aef8SEnji Cooper // Arguments to use in execve() calls.
188ac5aef8SEnji Cooper static char* null_envp[] = {NULL};
198ac5aef8SEnji Cooper 
208ac5aef8SEnji Cooper class Execve : public ::testing::Test {
218ac5aef8SEnji Cooper  public:
Execve()228ac5aef8SEnji Cooper   Execve() : exec_fd_(-1) {
238ac5aef8SEnji Cooper     // We need a program to exec(), but for fexecve() to work in capability
248ac5aef8SEnji Cooper     // mode that program needs to be statically linked (otherwise ld.so will
258ac5aef8SEnji Cooper     // attempt to traverse the filesystem to load (e.g.) /lib/libc.so and
268ac5aef8SEnji Cooper     // fail).
278ac5aef8SEnji Cooper     exec_prog_ = capsicum_test_bindir + "/mini-me";
288ac5aef8SEnji Cooper     exec_prog_noexec_ = capsicum_test_bindir + "/mini-me.noexec";
298ac5aef8SEnji Cooper     exec_prog_setuid_ = capsicum_test_bindir + "/mini-me.setuid";
308ac5aef8SEnji Cooper 
318ac5aef8SEnji Cooper     exec_fd_ = open(exec_prog_.c_str(), O_RDONLY);
328ac5aef8SEnji Cooper     if (exec_fd_ < 0) {
338ac5aef8SEnji Cooper       fprintf(stderr, "Error! Failed to open %s\n", exec_prog_.c_str());
348ac5aef8SEnji Cooper     }
358ac5aef8SEnji Cooper     argv_checkroot_[0] = (char*)exec_prog_.c_str();
368ac5aef8SEnji Cooper     argv_fail_[0] = (char*)exec_prog_.c_str();
378ac5aef8SEnji Cooper     argv_pass_[0] = (char*)exec_prog_.c_str();
388ac5aef8SEnji Cooper   }
~Execve()398ac5aef8SEnji Cooper   ~Execve() {
408ac5aef8SEnji Cooper     if (exec_fd_ >= 0) {
418ac5aef8SEnji Cooper       close(exec_fd_);
428ac5aef8SEnji Cooper       exec_fd_ = -1;
438ac5aef8SEnji Cooper     }
448ac5aef8SEnji Cooper   }
458ac5aef8SEnji Cooper protected:
468ac5aef8SEnji Cooper   char* argv_checkroot_[3] = {nullptr, (char*)"--checkroot", nullptr};
478ac5aef8SEnji Cooper   char* argv_fail_[3] = {nullptr, (char*)"--fail", nullptr};
488ac5aef8SEnji Cooper   char* argv_pass_[3] = {nullptr, (char*)"--pass", nullptr};
498ac5aef8SEnji Cooper   std::string exec_prog_, exec_prog_noexec_, exec_prog_setuid_;
508ac5aef8SEnji Cooper   int exec_fd_;
518ac5aef8SEnji Cooper };
528ac5aef8SEnji Cooper 
538ac5aef8SEnji Cooper class Fexecve : public Execve {
548ac5aef8SEnji Cooper  public:
Fexecve()558ac5aef8SEnji Cooper   Fexecve() : Execve() {}
568ac5aef8SEnji Cooper };
578ac5aef8SEnji Cooper 
588ac5aef8SEnji Cooper class FexecveWithScript : public Fexecve {
598ac5aef8SEnji Cooper  public:
FexecveWithScript()608ac5aef8SEnji Cooper   FexecveWithScript() :
618ac5aef8SEnji Cooper     Fexecve(), temp_script_filename_(TmpFile("cap_sh_script")) {}
628ac5aef8SEnji Cooper 
SetUp()638ac5aef8SEnji Cooper   void SetUp() override {
648ac5aef8SEnji Cooper     // First, build an executable shell script
658ac5aef8SEnji Cooper     int fd = open(temp_script_filename_, O_RDWR|O_CREAT, 0755);
668ac5aef8SEnji Cooper     EXPECT_OK(fd);
678ac5aef8SEnji Cooper     const char* contents = "#!/bin/sh\nexit 99\n";
688ac5aef8SEnji Cooper     EXPECT_OK(write(fd, contents, strlen(contents)));
698ac5aef8SEnji Cooper     close(fd);
708ac5aef8SEnji Cooper   }
TearDown()718ac5aef8SEnji Cooper   void TearDown() override {
728ac5aef8SEnji Cooper     (void)::unlink(temp_script_filename_);
738ac5aef8SEnji Cooper   }
748ac5aef8SEnji Cooper 
758ac5aef8SEnji Cooper   const char *temp_script_filename_;
768ac5aef8SEnji Cooper };
778ac5aef8SEnji Cooper 
FORK_TEST_F(Execve,BasicFexecve)788ac5aef8SEnji Cooper FORK_TEST_F(Execve, BasicFexecve) {
798ac5aef8SEnji Cooper   EXPECT_OK(fexecve_(exec_fd_, argv_pass_, null_envp));
808ac5aef8SEnji Cooper   // Should not reach here, exec() takes over.
818ac5aef8SEnji Cooper   EXPECT_TRUE(!"fexecve() should never return");
828ac5aef8SEnji Cooper }
838ac5aef8SEnji Cooper 
FORK_TEST_F(Execve,InCapMode)848ac5aef8SEnji Cooper FORK_TEST_F(Execve, InCapMode) {
858ac5aef8SEnji Cooper   EXPECT_OK(cap_enter());
868ac5aef8SEnji Cooper   EXPECT_OK(fexecve_(exec_fd_, argv_pass_, null_envp));
878ac5aef8SEnji Cooper   // Should not reach here, exec() takes over.
888ac5aef8SEnji Cooper   EXPECT_TRUE(!"fexecve() should never return");
898ac5aef8SEnji Cooper }
908ac5aef8SEnji Cooper 
FORK_TEST_F(Execve,FailWithoutCap)918ac5aef8SEnji Cooper FORK_TEST_F(Execve, FailWithoutCap) {
928ac5aef8SEnji Cooper   EXPECT_OK(cap_enter());
938ac5aef8SEnji Cooper   int cap_fd = dup(exec_fd_);
948ac5aef8SEnji Cooper   EXPECT_OK(cap_fd);
958ac5aef8SEnji Cooper   cap_rights_t rights;
968ac5aef8SEnji Cooper   cap_rights_init(&rights, 0);
978ac5aef8SEnji Cooper   EXPECT_OK(cap_rights_limit(cap_fd, &rights));
988ac5aef8SEnji Cooper   EXPECT_EQ(-1, fexecve_(cap_fd, argv_fail_, null_envp));
998ac5aef8SEnji Cooper   EXPECT_EQ(ENOTCAPABLE, errno);
1008ac5aef8SEnji Cooper }
1018ac5aef8SEnji Cooper 
FORK_TEST_F(Execve,SucceedWithCap)1028ac5aef8SEnji Cooper FORK_TEST_F(Execve, SucceedWithCap) {
1038ac5aef8SEnji Cooper   EXPECT_OK(cap_enter());
1048ac5aef8SEnji Cooper   int cap_fd = dup(exec_fd_);
1058ac5aef8SEnji Cooper   EXPECT_OK(cap_fd);
1068ac5aef8SEnji Cooper   cap_rights_t rights;
1078ac5aef8SEnji Cooper   // TODO(drysdale): would prefer that Linux Capsicum not need all of these
1088ac5aef8SEnji Cooper   // rights -- just CAP_FEXECVE|CAP_READ or CAP_FEXECVE would be preferable.
1098ac5aef8SEnji Cooper   cap_rights_init(&rights, CAP_FEXECVE, CAP_LOOKUP, CAP_READ);
1108ac5aef8SEnji Cooper   EXPECT_OK(cap_rights_limit(cap_fd, &rights));
1118ac5aef8SEnji Cooper   EXPECT_OK(fexecve_(cap_fd, argv_pass_, null_envp));
1128ac5aef8SEnji Cooper   // Should not reach here, exec() takes over.
1138ac5aef8SEnji Cooper   EXPECT_TRUE(!"fexecve() should have succeeded");
1148ac5aef8SEnji Cooper }
1158ac5aef8SEnji Cooper 
FORK_TEST_F(Fexecve,ExecutePermissionCheck)1168ac5aef8SEnji Cooper FORK_TEST_F(Fexecve, ExecutePermissionCheck) {
1178ac5aef8SEnji Cooper   int fd = open(exec_prog_noexec_.c_str(), O_RDONLY);
1188ac5aef8SEnji Cooper   EXPECT_OK(fd);
1198ac5aef8SEnji Cooper   if (fd >= 0) {
1208ac5aef8SEnji Cooper     struct stat data;
1218ac5aef8SEnji Cooper     EXPECT_OK(fstat(fd, &data));
1228ac5aef8SEnji Cooper     EXPECT_EQ((mode_t)0, data.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH));
1238ac5aef8SEnji Cooper     EXPECT_EQ(-1, fexecve_(fd, argv_fail_, null_envp));
1248ac5aef8SEnji Cooper     EXPECT_EQ(EACCES, errno);
1258ac5aef8SEnji Cooper     close(fd);
1268ac5aef8SEnji Cooper   }
1278ac5aef8SEnji Cooper }
1288ac5aef8SEnji Cooper 
FORK_TEST_F(Fexecve,SetuidIgnoredIfNonRoot)129*2d936e6cSAlex Richardson FORK_TEST_F(Fexecve, SetuidIgnoredIfNonRoot) {
1308ac5aef8SEnji Cooper   if (geteuid() == 0) {
131*2d936e6cSAlex Richardson     GTEST_SKIP() << "requires non-root";
1328ac5aef8SEnji Cooper   }
1338ac5aef8SEnji Cooper   int fd = open(exec_prog_setuid_.c_str(), O_RDONLY);
1348ac5aef8SEnji Cooper   EXPECT_OK(fd);
1358ac5aef8SEnji Cooper   EXPECT_OK(cap_enter());
1368ac5aef8SEnji Cooper   if (fd >= 0) {
1378ac5aef8SEnji Cooper     struct stat data;
1388ac5aef8SEnji Cooper     EXPECT_OK(fstat(fd, &data));
1398ac5aef8SEnji Cooper     EXPECT_EQ((mode_t)S_ISUID, data.st_mode & S_ISUID);
1408ac5aef8SEnji Cooper     EXPECT_OK(fexecve_(fd, argv_checkroot_, null_envp));
1418ac5aef8SEnji Cooper     // Should not reach here, exec() takes over.
1428ac5aef8SEnji Cooper     EXPECT_TRUE(!"fexecve() should have succeeded");
1438ac5aef8SEnji Cooper     close(fd);
1448ac5aef8SEnji Cooper   }
1458ac5aef8SEnji Cooper }
1468ac5aef8SEnji Cooper 
FORK_TEST_F(Fexecve,ExecveFailure)1478ac5aef8SEnji Cooper FORK_TEST_F(Fexecve, ExecveFailure) {
1488ac5aef8SEnji Cooper   EXPECT_OK(cap_enter());
1498ac5aef8SEnji Cooper   EXPECT_EQ(-1, execve(argv_fail_[0], argv_fail_, null_envp));
1508ac5aef8SEnji Cooper   EXPECT_EQ(ECAPMODE, errno);
1518ac5aef8SEnji Cooper }
1528ac5aef8SEnji Cooper 
FORK_TEST_F(FexecveWithScript,CapModeScriptFail)1538ac5aef8SEnji Cooper FORK_TEST_F(FexecveWithScript, CapModeScriptFail) {
1548ac5aef8SEnji Cooper   int fd;
1558ac5aef8SEnji Cooper 
1568ac5aef8SEnji Cooper   // Open the script file, with CAP_FEXECVE rights.
1578ac5aef8SEnji Cooper   fd = open(temp_script_filename_, O_RDONLY);
1588ac5aef8SEnji Cooper   cap_rights_t rights;
1598ac5aef8SEnji Cooper   cap_rights_init(&rights, CAP_FEXECVE, CAP_READ, CAP_SEEK);
1608ac5aef8SEnji Cooper   EXPECT_OK(cap_rights_limit(fd, &rights));
1618ac5aef8SEnji Cooper 
1628ac5aef8SEnji Cooper   EXPECT_OK(cap_enter());  // Enter capability mode
1638ac5aef8SEnji Cooper 
1648ac5aef8SEnji Cooper   // Attempt fexecve; should fail, because "/bin/sh" is inaccessible.
1658ac5aef8SEnji Cooper   EXPECT_EQ(-1, fexecve_(fd, argv_pass_, null_envp));
1668ac5aef8SEnji Cooper }
1678ac5aef8SEnji Cooper 
1688ac5aef8SEnji Cooper #ifdef HAVE_EXECVEAT
1698ac5aef8SEnji Cooper class Execveat : public Execve {
1708ac5aef8SEnji Cooper  public:
Execveat()1718ac5aef8SEnji Cooper   Execveat() : Execve() {}
1728ac5aef8SEnji Cooper };
1738ac5aef8SEnji Cooper 
TEST_F(Execveat,NoUpwardTraversal)1748ac5aef8SEnji Cooper TEST_F(Execveat, NoUpwardTraversal) {
175*2d936e6cSAlex Richardson   char *abspath = realpath(exec_prog_.c_str(), NULL);
1768ac5aef8SEnji Cooper   char cwd[1024];
1778ac5aef8SEnji Cooper   getcwd(cwd, sizeof(cwd));
1788ac5aef8SEnji Cooper 
1798ac5aef8SEnji Cooper   int dfd = open(".", O_DIRECTORY|O_RDONLY);
1808ac5aef8SEnji Cooper   pid_t child = fork();
1818ac5aef8SEnji Cooper   if (child == 0) {
1828ac5aef8SEnji Cooper     EXPECT_OK(cap_enter());  // Enter capability mode.
1838ac5aef8SEnji Cooper     // Can't execveat() an absolute path, even relative to a dfd.
1848ac5aef8SEnji Cooper     EXPECT_SYSCALL_FAIL(ECAPMODE,
1858ac5aef8SEnji Cooper                         execveat(AT_FDCWD, abspath, argv_pass_, null_envp, 0));
1868ac5aef8SEnji Cooper     EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY,
1878ac5aef8SEnji Cooper                         execveat(dfd, abspath, argv_pass_, null_envp, 0));
1888ac5aef8SEnji Cooper 
1898ac5aef8SEnji Cooper     // Can't execveat() a relative path ("../<dir>/./<exe>").
1908ac5aef8SEnji Cooper     char *p = cwd + strlen(cwd);
1918ac5aef8SEnji Cooper     while (*p != '/') p--;
1928ac5aef8SEnji Cooper     char buffer[1024] = "../";
1938ac5aef8SEnji Cooper     strcat(buffer, ++p);
1948ac5aef8SEnji Cooper     strcat(buffer, "/");
195*2d936e6cSAlex Richardson     strcat(buffer, exec_prog_.c_str());
1968ac5aef8SEnji Cooper     EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY,
1978ac5aef8SEnji Cooper                         execveat(dfd, buffer, argv_pass_, null_envp, 0));
1988ac5aef8SEnji Cooper     exit(HasFailure() ? 99 : 123);
1998ac5aef8SEnji Cooper   }
2008ac5aef8SEnji Cooper   int status;
2018ac5aef8SEnji Cooper   EXPECT_EQ(child, waitpid(child, &status, 0));
2028ac5aef8SEnji Cooper   EXPECT_TRUE(WIFEXITED(status)) << "0x" << std::hex << status;
2038ac5aef8SEnji Cooper   EXPECT_EQ(123, WEXITSTATUS(status));
2048ac5aef8SEnji Cooper   free(abspath);
2058ac5aef8SEnji Cooper   close(dfd);
2068ac5aef8SEnji Cooper }
2078ac5aef8SEnji Cooper #endif
208