diff --git a/lib/libcasper/services/cap_fileargs/Makefile b/lib/libcasper/services/cap_fileargs/Makefile index fd815a87aa05..04787c01db35 100644 --- a/lib/libcasper/services/cap_fileargs/Makefile +++ b/lib/libcasper/services/cap_fileargs/Makefile @@ -21,6 +21,9 @@ LIBADD= nv CFLAGS+=-I${.CURDIR} +HAS_TESTS= +SUBDIR.${MK_TESTS}+= tests + MAN+= cap_fileargs.3 MLINKS+=cap_fileargs.3 libcap_fileargs.3 diff --git a/lib/libcasper/services/cap_fileargs/tests/Makefile b/lib/libcasper/services/cap_fileargs/tests/Makefile new file mode 100644 index 000000000000..51674c5f778e --- /dev/null +++ b/lib/libcasper/services/cap_fileargs/tests/Makefile @@ -0,0 +1,16 @@ +# $FreeBSD$ + +.include + +ATF_TESTS_C= fileargs_test + +.if ${MK_CASPER} != "no" +LIBADD+= casper +LIBADD+= cap_fileargs +CFLAGS+=-DWITH_CASPER +.endif +LIBADD+= nv + +WARNS?= 3 + +.include diff --git a/lib/libcasper/services/cap_fileargs/tests/fileargs_test.c b/lib/libcasper/services/cap_fileargs/tests/fileargs_test.c new file mode 100644 index 000000000000..ad889bb2986f --- /dev/null +++ b/lib/libcasper/services/cap_fileargs/tests/fileargs_test.c @@ -0,0 +1,606 @@ +/*- + * Copyright (c) 2021 Mariusz Zaborski + * + * 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``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 FOUNDATION OR CONTRIBUTORS + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#define MAX_FILES 200 + +static char *files[MAX_FILES]; +static int fds[MAX_FILES]; + +#define TEST_FILE "/etc/passwd" + +static void +prepare_files(size_t num, bool create) +{ + const char template[] = "testsfiles.XXXXXXXX"; + size_t i; + + for (i = 0; i < num; i++) { + files[i] = calloc(1, sizeof(template)); + ATF_REQUIRE(files[i] != NULL); + strncpy(files[i], template, sizeof(template) - 1); + + if (create) { + fds[i] = mkstemp(files[i]); + ATF_REQUIRE(fds[i] >= 0); + } else { + fds[i] = -1; + ATF_REQUIRE(mktemp(files[i]) != NULL); + } + } +} + +static void +clear_files(void) +{ + size_t i; + + + for (i = 0; files[i] != NULL; i++) { + unlink(files[i]); + free(files[i]); + if (fds[i] != -1) + close(fds[i]); + } +} + +static int +test_file_open(fileargs_t *fa, const char *file, int *fdp) +{ + int fd; + + fd = fileargs_open(fa, file); + if (fd < 0) + return (errno); + + if (fdp != NULL) { + *fdp = fd; + } + + return (0); +} + +static int +test_file_fopen(fileargs_t *fa, const char *file, const char *mode, + FILE **retfile) +{ + FILE *pfile; + + pfile = fileargs_fopen(fa, file, mode); + if (pfile == NULL) + return (errno); + + if (retfile != NULL) { + *retfile = pfile; + } + + return (0); +} + +static int +test_file_lstat(fileargs_t *fa, const char *file) +{ + struct stat fasb, origsb; + bool equals; + + if (fileargs_lstat(fa, file, &fasb) < 0) + return (errno); + + ATF_REQUIRE(lstat(file, &origsb) == 0); + + equals = true; + equals &= (origsb.st_dev == fasb.st_dev); + equals &= (origsb.st_ino == fasb.st_ino); + equals &= (origsb.st_nlink == fasb.st_nlink); + equals &= (origsb.st_flags == fasb.st_flags); + equals &= (memcmp(&origsb.st_ctim, &fasb.st_ctim, + sizeof(fasb.st_ctim)) == 0); + equals &= (memcmp(&origsb.st_birthtim, &fasb.st_birthtim, + sizeof(fasb.st_birthtim)) == 0); + if (!equals) { + return (EINVAL); + } + + return (0); +} + +static int +test_file_mode(int fd, int mode) +{ + int flags; + + flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) + return (errno); + + if ((flags & O_ACCMODE) != mode) + return (errno); + + return (0); +} + +static bool +test_file_cap(int fd, cap_rights_t *rights) +{ + cap_rights_t fdrights; + + ATF_REQUIRE(cap_rights_get(fd, &fdrights) == 0); + + return (cap_rights_contains(&fdrights, rights)); +} + +static int +test_file_write(int fd) +{ + char buf; + + buf = 't'; + if (write(fd, &buf, sizeof(buf)) != sizeof(buf)) { + return (errno); + } + + return (0); +} + +static int +test_file_read(int fd) +{ + char buf; + + if (read(fd, &buf, sizeof(buf)) < 0) { + return (errno); + } + + return (0); +} + +static int +test_file_fwrite(FILE *pfile) +{ + char buf; + + buf = 't'; + if (fwrite(&buf, sizeof(buf), 1, pfile) != sizeof(buf)) + return (errno); + + return (0); +} + +static int +test_file_fread(FILE *pfile) +{ + char buf; + int ret, serrno; + + errno = 0; + ret = fread(&buf, sizeof(buf), 1, pfile); + serrno = errno; + if (ret < 0) { + return (serrno); + } else if (ret == 0 && feof(pfile) == 0) { + return (serrno != 0 ? serrno : EINVAL); + } + + return (0); +} + +ATF_TC_WITH_CLEANUP(fileargs__open_read); +ATF_TC_HEAD(fileargs__open_read, tc) {} +ATF_TC_BODY(fileargs__open_read, tc) +{ + cap_rights_t rights, norights; + fileargs_t *fa; + size_t i; + int fd; + + prepare_files(MAX_FILES, true); + + cap_rights_init(&rights, CAP_READ | CAP_FCNTL); + cap_rights_init(&norights, CAP_WRITE); + fa = fileargs_init(MAX_FILES, files, O_RDONLY, 0, &rights, + FA_OPEN); + ATF_REQUIRE(fa != NULL); + + for (i = 0; i < MAX_FILES; i++) { + /* ALLOWED */ + /* We open file twice to check if we can. */ + ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0); + ATF_REQUIRE(close(fd) == 0); + + ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0); + ATF_REQUIRE(test_file_mode(fd, O_RDONLY) == 0); + ATF_REQUIRE(test_file_cap(fd, &rights) == true); + ATF_REQUIRE(test_file_read(fd) == 0); + + /* DISALLOWED */ + ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_open(fa, TEST_FILE, NULL) == ENOTCAPABLE); + ATF_REQUIRE(test_file_cap(fd, &norights) == false); + ATF_REQUIRE(test_file_write(fd) == ENOTCAPABLE); + + /* CLOSE */ + ATF_REQUIRE(close(fd) == 0); + } +} +ATF_TC_CLEANUP(fileargs__open_read, tc) +{ + clear_files(); +} + +ATF_TC_WITH_CLEANUP(fileargs__open_write); +ATF_TC_HEAD(fileargs__open_write, tc) {} +ATF_TC_BODY(fileargs__open_write, tc) +{ + cap_rights_t rights, norights; + fileargs_t *fa; + size_t i; + int fd; + + prepare_files(MAX_FILES, true); + + cap_rights_init(&rights, CAP_WRITE | CAP_FCNTL); + cap_rights_init(&norights, CAP_READ); + fa = fileargs_init(MAX_FILES, files, O_WRONLY, 0, &rights, + FA_OPEN); + ATF_REQUIRE(fa != NULL); + + for (i = 0; i < MAX_FILES; i++) { + /* ALLOWED */ + /* We open file twice to check if we can. */ + ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0); + ATF_REQUIRE(close(fd) == 0); + + ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0); + ATF_REQUIRE(test_file_mode(fd, O_WRONLY) == 0); + ATF_REQUIRE(test_file_cap(fd, &rights) == true); + ATF_REQUIRE(test_file_write(fd) == 0); + + /* DISALLOWED */ + ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_open(fa, TEST_FILE, NULL) == ENOTCAPABLE); + ATF_REQUIRE(test_file_cap(fd, &norights) == false); + ATF_REQUIRE(test_file_read(fd) == ENOTCAPABLE); + + /* CLOSE */ + ATF_REQUIRE(close(fd) == 0); + } +} +ATF_TC_CLEANUP(fileargs__open_write, tc) +{ + clear_files(); +} + +ATF_TC_WITH_CLEANUP(fileargs__open_create); +ATF_TC_HEAD(fileargs__open_create, tc) {} +ATF_TC_BODY(fileargs__open_create, tc) +{ + cap_rights_t rights, norights; + fileargs_t *fa; + size_t i; + int fd; + + prepare_files(MAX_FILES, false); + + cap_rights_init(&rights, CAP_WRITE | CAP_FCNTL | CAP_READ); + cap_rights_init(&norights, CAP_FCHMOD); + fa = fileargs_init(MAX_FILES, files, O_RDWR | O_CREAT, 666, + &rights, FA_OPEN); + ATF_REQUIRE(fa != NULL); + + for (i = 0; i < MAX_FILES; i++) { + /* ALLOWED */ + ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0); + + ATF_REQUIRE(test_file_mode(fd, O_RDWR) == 0); + ATF_REQUIRE(test_file_cap(fd, &rights) == true); + ATF_REQUIRE(test_file_write(fd) == 0); + ATF_REQUIRE(test_file_read(fd) == 0); + + /* DISALLOWED */ + ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_open(fa, TEST_FILE, NULL) == ENOTCAPABLE); + ATF_REQUIRE(test_file_cap(fd, &norights) == false); + + /* CLOSE */ + ATF_REQUIRE(close(fd) == 0); + } +} +ATF_TC_CLEANUP(fileargs__open_create, tc) +{ + clear_files(); +} + +ATF_TC_WITH_CLEANUP(fileargs__open_with_casper); +ATF_TC_HEAD(fileargs__open_with_casper, tc) {} +ATF_TC_BODY(fileargs__open_with_casper, tc) +{ + cap_channel_t *capcas; + cap_rights_t rights; + fileargs_t *fa; + size_t i; + int fd; + + prepare_files(MAX_FILES, true); + + capcas = cap_init(); + ATF_REQUIRE(capcas != NULL); + + cap_rights_init(&rights, CAP_READ); + fa = fileargs_cinit(capcas, MAX_FILES, files, O_RDONLY, 0, &rights, + FA_OPEN); + ATF_REQUIRE(fa != NULL); + + for (i = 0; i < MAX_FILES; i++) { + /* ALLOWED */ + ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0); + ATF_REQUIRE(test_file_read(fd) == 0); + + /* CLOSE */ + ATF_REQUIRE(close(fd) == 0); + } +} +ATF_TC_CLEANUP(fileargs__open_with_casper, tc) +{ + clear_files(); +} + +ATF_TC_WITH_CLEANUP(fileargs__fopen_read); +ATF_TC_HEAD(fileargs__fopen_read, tc) {} +ATF_TC_BODY(fileargs__fopen_read, tc) +{ + cap_rights_t rights, norights; + fileargs_t *fa; + size_t i; + FILE *pfile; + int fd; + + prepare_files(MAX_FILES, true); + + cap_rights_init(&rights, CAP_READ | CAP_FCNTL); + cap_rights_init(&norights, CAP_WRITE); + fa = fileargs_init(MAX_FILES, files, O_RDONLY, 0, &rights, + FA_OPEN); + ATF_REQUIRE(fa != NULL); + + for (i = 0; i < MAX_FILES; i++) { + /* ALLOWED */ + /* We fopen file twice to check if we can. */ + ATF_REQUIRE(test_file_fopen(fa, files[i], "r", &pfile) == 0); + ATF_REQUIRE(fclose(pfile) == 0); + + ATF_REQUIRE(test_file_fopen(fa, files[i], "r", &pfile) == 0); + fd = fileno(pfile); + ATF_REQUIRE(test_file_mode(fd, O_RDONLY) == 0); + ATF_REQUIRE(test_file_cap(fd, &rights) == true); + ATF_REQUIRE(test_file_fread(pfile) == 0); + + /* DISALLOWED */ + ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_fopen(fa, TEST_FILE, "r", NULL) == + ENOTCAPABLE); + ATF_REQUIRE(test_file_cap(fd, &norights) == false); + ATF_REQUIRE(test_file_fwrite(pfile) == EBADF); + + /* CLOSE */ + ATF_REQUIRE(fclose(pfile) == 0); + } +} +ATF_TC_CLEANUP(fileargs__fopen_read, tc) +{ + clear_files(); +} + +ATF_TC_WITH_CLEANUP(fileargs__fopen_write); +ATF_TC_HEAD(fileargs__fopen_write, tc) {} +ATF_TC_BODY(fileargs__fopen_write, tc) +{ + cap_rights_t rights, norights; + fileargs_t *fa; + size_t i; + FILE *pfile; + int fd; + + prepare_files(MAX_FILES, true); + + cap_rights_init(&rights, CAP_WRITE | CAP_FCNTL); + cap_rights_init(&norights, CAP_READ); + fa = fileargs_init(MAX_FILES, files, O_WRONLY, 0, &rights, + FA_OPEN); + ATF_REQUIRE(fa != NULL); + + for (i = 0; i < MAX_FILES; i++) { + /* ALLOWED */ + /* We fopen file twice to check if we can. */ + ATF_REQUIRE(test_file_fopen(fa, files[i], "w", &pfile) == 0); + ATF_REQUIRE(fclose(pfile) == 0); + + ATF_REQUIRE(test_file_fopen(fa, files[i], "w", &pfile) == 0); + fd = fileno(pfile); + ATF_REQUIRE(test_file_mode(fd, O_WRONLY) == 0); + ATF_REQUIRE(test_file_cap(fd, &rights) == true); + ATF_REQUIRE(test_file_fwrite(pfile) == 0); + + /* DISALLOWED */ + ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_fopen(fa, TEST_FILE, "w", NULL) == + ENOTCAPABLE); + ATF_REQUIRE(test_file_cap(fd, &norights) == false); + ATF_REQUIRE(test_file_fread(pfile) == EBADF); + + /* CLOSE */ + ATF_REQUIRE(fclose(pfile) == 0); + } +} +ATF_TC_CLEANUP(fileargs__fopen_write, tc) +{ + clear_files(); +} + +ATF_TC_WITH_CLEANUP(fileargs__fopen_create); +ATF_TC_HEAD(fileargs__fopen_create, tc) {} +ATF_TC_BODY(fileargs__fopen_create, tc) +{ + cap_rights_t rights; + fileargs_t *fa; + size_t i; + FILE *pfile; + int fd; + + prepare_files(MAX_FILES, false); + + cap_rights_init(&rights, CAP_READ | CAP_WRITE | CAP_FCNTL); + fa = fileargs_init(MAX_FILES, files, O_RDWR | O_CREAT, 0, &rights, + FA_OPEN); + ATF_REQUIRE(fa != NULL); + + for (i = 0; i < MAX_FILES; i++) { + /* ALLOWED */ + /* We fopen file twice to check if we can. */ + ATF_REQUIRE(test_file_fopen(fa, files[i], "w+", &pfile) == 0); + fd = fileno(pfile); + ATF_REQUIRE(test_file_mode(fd, O_RDWR) == 0); + ATF_REQUIRE(test_file_cap(fd, &rights) == true); + ATF_REQUIRE(test_file_fwrite(pfile) == 0); + ATF_REQUIRE(test_file_fread(pfile) == 0); + + /* DISALLOWED */ + ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_fopen(fa, TEST_FILE, "w+", NULL) == + ENOTCAPABLE); + + /* CLOSE */ + ATF_REQUIRE(fclose(pfile) == 0); + } +} +ATF_TC_CLEANUP(fileargs__fopen_create, tc) +{ + clear_files(); +} + +ATF_TC_WITH_CLEANUP(fileargs__lstat); +ATF_TC_HEAD(fileargs__lstat, tc) {} +ATF_TC_BODY(fileargs__lstat, tc) +{ + fileargs_t *fa; + size_t i; + int fd; + + prepare_files(MAX_FILES, true); + + fa = fileargs_init(MAX_FILES, files, 0, 0, NULL, FA_LSTAT); + ATF_REQUIRE(fa != NULL); + + for (i = 0; i < MAX_FILES; i++) { + /* ALLOWED */ + ATF_REQUIRE(test_file_lstat(fa, files[i]) == 0); + + /* DISALLOWED */ + ATF_REQUIRE(test_file_open(fa, files[i], &fd) == ENOTCAPABLE); + ATF_REQUIRE(test_file_lstat(fa, TEST_FILE) == ENOTCAPABLE); + ATF_REQUIRE(test_file_open(fa, TEST_FILE, &fd) == ENOTCAPABLE); + } +} +ATF_TC_CLEANUP(fileargs__lstat, tc) +{ + clear_files(); +} + +ATF_TC_WITH_CLEANUP(fileargs__open_lstat); +ATF_TC_HEAD(fileargs__open_lstat, tc) {} +ATF_TC_BODY(fileargs__open_lstat, tc) +{ + cap_rights_t rights, norights; + fileargs_t *fa; + size_t i; + int fd; + + prepare_files(MAX_FILES, true); + + cap_rights_init(&rights, CAP_READ | CAP_FCNTL); + cap_rights_init(&norights, CAP_WRITE); + fa = fileargs_init(MAX_FILES, files, O_RDONLY, 0, &rights, + FA_OPEN | FA_LSTAT); + ATF_REQUIRE(fa != NULL); + + for (i = 0; i < MAX_FILES; i++) { + /* ALLOWED */ + /* We open file twice to check if we can. */ + ATF_REQUIRE(test_file_lstat(fa, files[i]) == 0); + ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0); + ATF_REQUIRE(close(fd) == 0); + + ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0); + ATF_REQUIRE(test_file_lstat(fa, files[i]) == 0); + ATF_REQUIRE(test_file_mode(fd, O_RDONLY) == 0); + ATF_REQUIRE(test_file_cap(fd, &rights) == true); + ATF_REQUIRE(test_file_read(fd) == 0); + + /* DISALLOWED */ + ATF_REQUIRE(test_file_open(fa, TEST_FILE, NULL) == ENOTCAPABLE); + ATF_REQUIRE(test_file_cap(fd, &norights) == false); + ATF_REQUIRE(test_file_write(fd) == ENOTCAPABLE); + + /* CLOSE */ + ATF_REQUIRE(close(fd) == 0); + } +} +ATF_TC_CLEANUP(fileargs__open_lstat, tc) +{ + clear_files(); +} + +ATF_TP_ADD_TCS(tp) +{ + + ATF_TP_ADD_TC(tp, fileargs__open_create); + ATF_TP_ADD_TC(tp, fileargs__open_read); + ATF_TP_ADD_TC(tp, fileargs__open_write); + ATF_TP_ADD_TC(tp, fileargs__open_with_casper); + + ATF_TP_ADD_TC(tp, fileargs__fopen_create); + ATF_TP_ADD_TC(tp, fileargs__fopen_read); + ATF_TP_ADD_TC(tp, fileargs__fopen_write); + + ATF_TP_ADD_TC(tp, fileargs__lstat); + + ATF_TP_ADD_TC(tp, fileargs__open_lstat); + + return (atf_no_error()); +}