diff --git a/contrib/capsicum-test/copy_file_range.cc b/contrib/capsicum-test/copy_file_range.cc new file mode 100644 index 000000000000..b785eb7f1e97 --- /dev/null +++ b/contrib/capsicum-test/copy_file_range.cc @@ -0,0 +1,228 @@ +#include +#include + +#include + +#include "capsicum.h" +#include "capsicum-test.h" +#include "syscalls.h" + +#define TOPDIR "cap_copy_file_range" +#define INFILE "infile" +#define OUTFILE "outfile" + +/* Test that copy_file_range() checks capabilities correctly. + * When used without offset arguments, copy_file_range() should + * require only CAP_READ on the source and CAP_WRITE on the destination + * file descriptors, respectively. + * When used with offset arguments, copy_file_range() should + * additionally require CAP_SEEK. + */ +class CopyFileRangeTest : public ::testing::Test { + public: + CopyFileRangeTest() { + int rc = mkdir(TmpFile(TOPDIR), 0755); + EXPECT_OK(rc); + if (rc < 0) { + EXPECT_EQ(EEXIST, errno); + } + wd_ = open(TmpFile(TOPDIR), O_DIRECTORY); + EXPECT_OK(wd_); + CreateFile(TmpFile(TOPDIR "/" INFILE)); + CreateFile(TmpFile(TOPDIR "/" OUTFILE)); + } + ~CopyFileRangeTest() { + close(wd_); + unlink(TmpFile(TOPDIR "/" INFILE)); + unlink(TmpFile(TOPDIR "/" OUTFILE)); + rmdir(TmpFile(TOPDIR)); + } + + private: + void CreateFile(const char *filename) { + int fd = open(filename, O_CREAT|O_RDWR, 0644); + const char *contents = "lorem ipsum dolor sit amet"; + EXPECT_OK(fd); + for (int i = 0; i < 100; i++) { + EXPECT_OK(write(fd, contents, strlen(contents))); + } + close(fd); + } + + protected: + int wd_; + + int openInFile(cap_rights_t *rights) { + int fd = openat(wd_, INFILE, O_RDONLY); + EXPECT_OK(fd); + EXPECT_OK(cap_rights_limit(fd, rights)); + return fd; + } + int openOutFile(cap_rights_t *rights) { + int fd = openat(wd_, OUTFILE, O_WRONLY); + EXPECT_OK(fd); + EXPECT_OK(cap_rights_limit(fd, rights)); + return fd; + } +}; + +TEST_F(CopyFileRangeTest, WriteReadNeg) { + cap_rights_t rights_in, rights_out; + + cap_rights_init(&rights_in, CAP_WRITE); + cap_rights_init(&rights_out, CAP_READ); + + int fd_in = openInFile(&rights_in); + int fd_out = openOutFile(&rights_out); + off_t off_in = 0, off_out = 0; + + EXPECT_NOTCAPABLE(copy_file_range(fd_in, NULL, fd_out, NULL, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, &off_in, fd_out, NULL, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, NULL, fd_out, &off_out, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, &off_in, fd_out, &off_out, 8, 0)); + off_in = 20; + off_out = 20; + EXPECT_NOTCAPABLE(copy_file_range(fd_in, &off_in, fd_out, NULL, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, NULL, fd_out, &off_out, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, &off_in, fd_out, &off_out, 8, 0)); + close(fd_in); + close(fd_out); +} + +TEST_F(CopyFileRangeTest, ReadReadNeg) { + cap_rights_t rights_in, rights_out; + + cap_rights_init(&rights_in, CAP_READ); + cap_rights_init(&rights_out, CAP_READ); + + int fd_in = openInFile(&rights_in); + int fd_out = openOutFile(&rights_out); + off_t off_in = 0, off_out = 0; + + EXPECT_NOTCAPABLE(copy_file_range(fd_in, NULL, fd_out, NULL, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, &off_in, fd_out, NULL, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, NULL, fd_out, &off_out, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, &off_in, fd_out, &off_out, 8, 0)); + off_in = 20; + off_out = 20; + EXPECT_NOTCAPABLE(copy_file_range(fd_in, &off_in, fd_out, NULL, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, NULL, fd_out, &off_out, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, &off_in, fd_out, &off_out, 8, 0)); + close(fd_in); + close(fd_out); +} + +TEST_F(CopyFileRangeTest, WriteWriteNeg) { + cap_rights_t rights_in, rights_out; + + cap_rights_init(&rights_in, CAP_WRITE); + cap_rights_init(&rights_out, CAP_WRITE); + + int fd_in = openInFile(&rights_in); + int fd_out = openOutFile(&rights_out); + off_t off_in = 0, off_out = 0; + + EXPECT_NOTCAPABLE(copy_file_range(fd_in, NULL, fd_out, NULL, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, &off_in, fd_out, NULL, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, NULL, fd_out, &off_out, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, &off_in, fd_out, &off_out, 8, 0)); + off_in = 20; + off_out = 20; + EXPECT_NOTCAPABLE(copy_file_range(fd_in, &off_in, fd_out, NULL, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, NULL, fd_out, &off_out, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, &off_in, fd_out, &off_out, 8, 0)); + close(fd_in); + close(fd_out); +} + +TEST_F(CopyFileRangeTest, ReadWrite) { + cap_rights_t rights_in, rights_out; + + cap_rights_init(&rights_in, CAP_READ); + cap_rights_init(&rights_out, CAP_WRITE); + + int fd_in = openInFile(&rights_in); + int fd_out = openOutFile(&rights_out); + off_t off_in = 0, off_out = 0; + + EXPECT_OK(copy_file_range(fd_in, NULL, fd_out, NULL, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, &off_in, fd_out, NULL, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, NULL, fd_out, &off_out, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, &off_in, fd_out, &off_out, 8, 0)); + off_in = 20; + off_out = 20; + EXPECT_NOTCAPABLE(copy_file_range(fd_in, &off_in, fd_out, NULL, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, NULL, fd_out, &off_out, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, &off_in, fd_out, &off_out, 8, 0)); + close(fd_in); + close(fd_out); +} + +TEST_F(CopyFileRangeTest, ReadSeekWrite) { + cap_rights_t rights_in, rights_out; + + cap_rights_init(&rights_in, CAP_READ, CAP_SEEK); + cap_rights_init(&rights_out, CAP_WRITE); + + int fd_in = openInFile(&rights_in); + int fd_out = openOutFile(&rights_out); + off_t off_in = 0, off_out = 0; + + EXPECT_OK(copy_file_range(fd_in, NULL, fd_out, NULL, 8, 0)); + EXPECT_OK(copy_file_range(fd_in, &off_in, fd_out, NULL, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, NULL, fd_out, &off_out, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, &off_in, fd_out, &off_out, 8, 0)); + off_in = 20; + off_out = 20; + EXPECT_OK(copy_file_range(fd_in, &off_in, fd_out, NULL, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, NULL, fd_out, &off_out, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, &off_in, fd_out, &off_out, 8, 0)); + close(fd_in); + close(fd_out); +} + +TEST_F(CopyFileRangeTest, ReadWriteSeek) { + cap_rights_t rights_in, rights_out; + + cap_rights_init(&rights_in, CAP_READ); + cap_rights_init(&rights_out, CAP_WRITE, CAP_SEEK); + + int fd_in = openInFile(&rights_in); + int fd_out = openOutFile(&rights_out); + off_t off_in = 0, off_out = 0; + + EXPECT_OK(copy_file_range(fd_in, NULL, fd_out, NULL, 8, 0)); + EXPECT_OK(copy_file_range(fd_in, NULL, fd_out, &off_out, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, &off_in, fd_out, NULL, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, &off_in, fd_out, &off_out, 8, 0)); + off_in = 20; + off_out = 20; + EXPECT_OK(copy_file_range(fd_in, NULL, fd_out, &off_out, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, &off_in, fd_out, NULL, 8, 0)); + EXPECT_NOTCAPABLE(copy_file_range(fd_in, &off_in, fd_out, &off_out, 8, 0)); + close(fd_in); + close(fd_out); +} + +TEST_F(CopyFileRangeTest, ReadSeekWriteSeek) { + cap_rights_t rights_in, rights_out; + + cap_rights_init(&rights_in, CAP_READ, CAP_SEEK); + cap_rights_init(&rights_out, CAP_WRITE, CAP_SEEK); + + int fd_in = openInFile(&rights_in); + int fd_out = openOutFile(&rights_out); + off_t off_in = 0, off_out = 0; + + EXPECT_OK(copy_file_range(fd_in, NULL, fd_out, NULL, 8, 0)); + EXPECT_OK(copy_file_range(fd_in, &off_in, fd_out, NULL, 8, 0)); + EXPECT_OK(copy_file_range(fd_in, NULL, fd_out, &off_out, 8, 0)); + EXPECT_OK(copy_file_range(fd_in, &off_in, fd_out, &off_out, 8, 0)); + off_in = 20; + off_out = 20; + EXPECT_OK(copy_file_range(fd_in, NULL, fd_out, &off_out, 8, 0)); + EXPECT_OK(copy_file_range(fd_in, &off_in, fd_out, NULL, 8, 0)); + EXPECT_OK(copy_file_range(fd_in, &off_in, fd_out, &off_out, 8, 0)); + close(fd_in); + close(fd_out); +} diff --git a/contrib/capsicum-test/makefile b/contrib/capsicum-test/makefile index ad697f160e2e..e55393bbf131 100644 --- a/contrib/capsicum-test/makefile +++ b/contrib/capsicum-test/makefile @@ -1,36 +1,36 @@ all: capsicum-test smoketest mini-me mini-me.noexec mini-me.setuid $(EXTRA_PROGS) -OBJECTS=capsicum-test-main.o capsicum-test.o capability-fd.o fexecve.o procdesc.o capmode.o fcntl.o ioctl.o openat.o sysctl.o select.o mqueue.o socket.o sctp.o capability-fd-pair.o linux.o overhead.o rename.o +OBJECTS=capsicum-test-main.o capsicum-test.o capability-fd.o copy_file_range.o fexecve.o procdesc.o capmode.o fcntl.o ioctl.o openat.o sysctl.o select.o mqueue.o socket.o sctp.o capability-fd-pair.o linux.o overhead.o rename.o GTEST_DIR=gtest-1.10.0 GTEST_INCS=-I$(GTEST_DIR)/include -I$(GTEST_DIR) GTEST_FLAGS=-DGTEST_USE_OWN_TR1_TUPLE=1 -DGTEST_HAS_TR1_TUPLE=1 CXXFLAGS+=$(ARCHFLAG) -Wall -g $(GTEST_INCS) $(GTEST_FLAGS) --std=c++11 CFLAGS+=$(ARCHFLAG) -Wall -g capsicum-test: $(OBJECTS) libgtest.a $(LOCAL_LIBS) $(CXX) $(CXXFLAGS) -g -o $@ $(OBJECTS) libgtest.a -lpthread -lrt $(LIBSCTP) $(LIBCAPRIGHTS) $(EXTRA_LIBS) # Small statically-linked program for fexecve tests # (needs to be statically linked so that execve()ing it # doesn't involve ld.so traversing the filesystem). mini-me: mini-me.c $(CC) $(CFLAGS) -static -o $@ $< mini-me.noexec: mini-me cp mini-me $@ && chmod -x $@ mini-me.setuid: mini-me rm -f $@ && cp mini-me $@&& sudo chown root $@ && sudo chmod u+s $@ # Simple C test of Capsicum syscalls SMOKETEST_OBJECTS=smoketest.o smoketest: $(SMOKETEST_OBJECTS) $(LOCAL_LIBS) $(CC) $(CFLAGS) -o $@ $(SMOKETEST_OBJECTS) $(LIBCAPRIGHTS) test: capsicum-test mini-me mini-me.noexec mini-me.setuid $(EXTRA_PROGS) ./capsicum-test gtest-all.o: $(CXX) $(CXXFLAGS) $(ARCHFLAG) -I$(GTEST_DIR)/include -I$(GTEST_DIR) $(GTEST_FLAGS) -c ${GTEST_DIR}/src/gtest-all.cc libgtest.a: gtest-all.o $(AR) -rv libgtest.a gtest-all.o clean: rm -rf gtest-all.o libgtest.a capsicum-test mini-me mini-me.noexec smoketest $(SMOKETEST_OBJECTS) $(OBJECTS) $(LOCAL_CLEAN) $(EXTRA_PROGS) diff --git a/tests/sys/capsicum/Makefile b/tests/sys/capsicum/Makefile index 542c6720521c..81cb4fa1ceee 100644 --- a/tests/sys/capsicum/Makefile +++ b/tests/sys/capsicum/Makefile @@ -1,61 +1,62 @@ .include TESTSDIR= ${TESTSBASE}/sys/capsicum ATF_TESTS_C+= bindat_connectat ATF_TESTS_C+= ioctls_test CFLAGS+= -I${SRCTOP}/tests .if ${MK_GOOGLETEST} != no .PATH: ${SRCTOP}/contrib/capsicum-test GTESTS+= capsicum-test GTESTS_WRAPPER_SH.capsicum-test= functional SRCS.capsicum-test+= \ capsicum-test-main.cc \ capsicum-test.cc \ capability-fd.cc \ + copy_file_range.cc \ fexecve.cc \ procdesc.cc \ capmode.cc \ fcntl.cc \ ioctl.cc \ openat.cc \ sysctl.cc \ select.cc \ mqueue.cc \ socket.cc \ sctp.cc \ capability-fd-pair.cc \ overhead.cc \ rename.cc LIBADD.capsicum-test+= gtest pthread procstat TEST_METADATA.capsicum-test= required_user="unprivileged" .for p in mini-me mini-me.noexec mini-me.setuid PROGS+= $p NO_SHARED.$p= SRCS.$p= mini-me.c .endfor .if ${MK_ASAN} != "no" || ${MK_UBSAN} != "no" # mini-me.o is linked into a static binary so we can't use sanitizers. # Note: We have to set CFLAGS here since it will be built as part of # _PROGS_COMMON_OBJS and therefore NO_SHARED.$p does not disable ASAN/UBSAN. CFLAGS.mini-me.c+= -fno-sanitize=address -fno-sanitize=undefined .endif BINDIR= ${TESTSDIR} BINMODE.mini-me.noexec= ${NOBINMODE} BINMODE.mini-me.setuid= 4555 WARNS.capsicum-test= 3 .endif # MK_GOOGLETEST .include