Index: lib/libcasper/services/cap_exec/Makefile =================================================================== --- /dev/null +++ lib/libcasper/services/cap_exec/Makefile @@ -0,0 +1,26 @@ +# $FreeBSD$ + +SHLIBDIR?= /lib/casper + +.include + +PACKAGE= runtime + +SHLIB_MAJOR= 1 +INCSDIR?= ${INCLUDEDIR}/casper + +.if ${MK_CASPER} != "no" +SHLIB= cap_exec + +SRCS= cap_exec.c +.endif + +LIBADD= nv + +CFLAGS+=-I${.CURDIR} +CFLAGS+= -DWITH_CASPER + +HAS_TESTS= +SUBDIR.${MK_TESTS}+= tests + +.include Index: lib/libcasper/services/cap_exec/cap_exec.h =================================================================== --- /dev/null +++ lib/libcasper/services/cap_exec/cap_exec.h @@ -0,0 +1,71 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2020 The FreeBSD Foundation + * + * This software was developed by Tiger Gao under sponsorship from + * the FreeBSD Foundation. + * + * 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 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 AUTHOR 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. + */ + +#ifndef _CAP_EXEC_H_ +#define _CAP_EXEC_H_ + +#include + +#include + +struct cap_exec; +typedef struct cap_exec cap_exec_t; + +#ifdef WITH_CASPER + +cap_exec_t *cap_exec_init(cap_channel_t *cas, int count, const char **arr); +FILE *cap_exec_open(cap_exec_t *exec, const char *command, const char *mode); +int cap_exec_close(cap_exec_t *exec, FILE *file); +void cap_exec_free(cap_exec_t *exec); + +#else + +static inline cap_exec_t * +cap_exec_init(cap_channel_t *cas __unused, int count __unused, + const char **arr __unused) +{ + return (NULL); +} + +static inline void +cap_exec_free(cap_exec_t *exec __unused) +{ + return; +} + +#define cap_exec_open(exec, command, mode) \ + popen(command, mode) + +#define cap_exec_close(exec, file) \ + pclose(file) + +#endif + +#endif Index: lib/libcasper/services/cap_exec/cap_exec.c =================================================================== --- /dev/null +++ lib/libcasper/services/cap_exec/cap_exec.c @@ -0,0 +1,282 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2020 The FreeBSD Foundation + * + * This software was developed by Tiger Gao under sponsorship from + * the FreeBSD Foundation. + * + * 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 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 AUTHOR 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "cap_exec.h" + +#include +#include +#include + +#include + +#define EXEC_MAGIC 0xEEC0EEC0 + +struct PROCESS +{ + LIST_ENTRY(PROCESS) processes; + int fd, pid; +}; + +struct cap_exec +{ + uint32_t exec_magic; + cap_channel_t *exec_chan; + LIST_HEAD(, PROCESS) exec_procs; +}; + +cap_exec_t * +cap_exec_init(cap_channel_t *cas, int count, const char **arr) +{ + nvlist_t *allowed_programs; + cap_channel_t *chan; + cap_exec_t *exec; + + assert(cas != NULL); + + chan = cap_service_open(cas, "system.exec"); + if (chan == NULL) + return (NULL); + + allowed_programs = nvlist_create(0); + for (int i = 0; i < count; ++i) + nvlist_add_null(allowed_programs, arr[i]); + + if (cap_limit_set(chan, allowed_programs) < 0) + return (NULL); + + exec = malloc(sizeof(cap_exec_t)); + if (exec != NULL) { + exec->exec_magic = EXEC_MAGIC; + exec->exec_chan = chan; + LIST_INIT(&exec->exec_procs); + } + + return (exec); +} + +void +cap_exec_free(cap_exec_t *exec) +{ + struct PROCESS *proc; + + if (exec == NULL) + return; + + assert (exec->exec_magic == EXEC_MAGIC); + while (!LIST_EMPTY(&exec->exec_procs)) { + proc = LIST_FIRST(&exec->exec_procs); + LIST_REMOVE(proc, processes); + free(proc); + } + if (exec->exec_chan != NULL) + cap_close(exec->exec_chan); + + explicit_bzero(&exec->exec_magic, sizeof(exec->exec_magic)); + free(exec); +} + +FILE * +cap_exec_open(cap_exec_t *exec, const char *command, const char *mode) +{ + nvlist_t *nvl; + int error; + int fd, pid; + + assert(exec != NULL); + assert(exec->exec_magic == EXEC_MAGIC); + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", "open"); + nvlist_add_string(nvl, "command", command); + nvl = cap_xfer_nvlist(exec->exec_chan, nvl); + if (nvl == NULL) + return (NULL); + + error = (int)dnvlist_get_number(nvl, "error", 0); + fd = dnvlist_take_descriptor(nvl, "filedesc", -1); + pid = dnvlist_take_number(nvl, "pid", -1); + nvlist_destroy(nvl); + + if (error != 0) { + close(fd); + errno = error; + return (NULL); + } + + struct PROCESS *item = malloc(sizeof(struct PROCESS)); + if (item == NULL) + return (NULL); + item->fd = fd; + item->pid = pid; + LIST_INSERT_HEAD(&exec->exec_procs, item, processes); + + return (fdopen(fd, mode)); +} + +int +cap_exec_close(cap_exec_t *exec, FILE *file) +{ + struct PROCESS *item, *temp; + nvlist_t *nvl; + int error, fd, pid; + + fd = fileno(file); + pid = -1; + LIST_FOREACH_SAFE(item, &exec->exec_procs, processes, temp) { + if (item && (item->fd == fd)) { + pid = item->pid; + break; + } + } + if (pid == -1) { + errno = ESRCH; + return (-1); + } + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", "close"); + nvlist_add_descriptor(nvl, "filedesc", fd); + nvlist_add_number(nvl, "pid", pid); + + fclose(file); + nvl = cap_xfer_nvlist(exec->exec_chan, nvl); + error = (int)dnvlist_get_number(nvl, "error", 0); + nvlist_destroy(nvl); + + if (error != 0) { + errno = error; + return (-1); + } + return (0); +} + +static int +exec_limits(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + + /* only allow limit to be set once */ + if (oldlimits != NULL) + return (ENOTCAPABLE); + (void) newlimits; + return (0); +} + +static int +exec_command_open(const nvlist_t *limits, nvlist_t *nvlin, nvlist_t *nvlout) +{ + const char *command; + char *prog, *buf; + int fd[2]; + int pid; + bool allowed; + + if (limits == NULL) + return (ENOTCAPABLE); + + command = nvlist_get_string(nvlin, "command"); + + /* parse executable */ + buf = malloc(sizeof(char)*(strlen(command) + 1)); + strcpy(buf, command); + prog = strtok(buf, " "); + + /* Check if program in allowed set */ + allowed = nvlist_exists_null(limits, prog); + free(buf); + if (!allowed) + return (ENOTCAPABLE); + + if (pipe(fd) == -1) + return (errno); + + pid = fork(); + if (pid == -1) { + return (ENOTCAPABLE); + } else if (pid == 0) { + close(fd[1]); + dup2(fd[0], STDIN_FILENO); + dup2(fd[0], STDOUT_FILENO); + close(fd[0]); + execlp("sh", "sh", "-c", command, NULL); + } else { + close(fd[0]); + nvlist_move_descriptor(nvlout, "filedesc", fd[1]); + nvlist_add_number(nvlout, "pid", pid); + } + + return (0); +} + +static int +exec_command_close(const nvlist_t *limits, nvlist_t *nvlin) +{ + int fd, pid; + + if (limits == NULL) + return (ENOTCAPABLE); + + fd = nvlist_get_descriptor(nvlin, "filedesc"); + pid = nvlist_get_number(nvlin, "pid"); + + close(fd); + waitpid(pid, NULL, 0); + + return (0); +} + +static int +exec_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin, + nvlist_t *nvlout) +{ + + if (strcmp(cmd, "open") == 0) + return (exec_command_open(limits, nvlin, nvlout)); + if (strcmp(cmd, "close") == 0) + return (exec_command_close(limits, nvlin)); + + return (EINVAL); +} +CREATE_SERVICE("system.exec", exec_limits, exec_command, 0); Index: lib/libcasper/services/cap_exec/tests/Makefile =================================================================== --- /dev/null +++ lib/libcasper/services/cap_exec/tests/Makefile @@ -0,0 +1,16 @@ +# $FreeBSD$ + +.include + +ATF_TESTS_C = cap_exec_test + +.if ${MK_CASPER} != "no" +LIBADD+= casper +LIBADD+= cap_exec +CFLAGS+= -DWITH_CASPER +.endif +LIBADD+= nv + +WARNS?= 3 + +.include Index: lib/libcasper/services/cap_exec/tests/cap_exec_test.c =================================================================== --- /dev/null +++ lib/libcasper/services/cap_exec/tests/cap_exec_test.c @@ -0,0 +1,241 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2020 + * + * 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 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 AUTHOR 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 + +ATF_TC_WITHOUT_HEAD(capexec__basic); +ATF_TC_BODY(capexec__basic, tc) +{ + cap_channel_t *capcas; + cap_exec_t *capexec; + FILE *f; + + capcas = cap_init(); + capexec = cap_exec_init(capcas, 0, NULL); + ATF_REQUIRE(capexec != NULL); + f = cap_exec_open(capexec, "true", "r"); + ATF_CHECK(f == NULL); + ATF_CHECK(errno == ENOTCAPABLE); + + cap_exec_free(capexec); + cap_close(capcas); +} + +ATF_TC_WITHOUT_HEAD(capexec__exec_command); +ATF_TC_BODY(capexec__exec_command, tc) +{ + cap_channel_t *capcas; + cap_exec_t *capexec; + const char *paths[1] = {"true"}; + FILE *f; + + capcas = cap_init(); + capexec = cap_exec_init(capcas, 1, paths); + ATF_REQUIRE(capexec != NULL); + f = cap_exec_open(capexec, "true", "r"); + ATF_CHECK(f != NULL); + + cap_exec_close(capexec, f); + cap_exec_free(capexec); + cap_close(capcas); +} + +ATF_TC_WITHOUT_HEAD(capexec__exec_command_read); +ATF_TC_BODY(capexec__exec_command_read, tc) +{ + cap_channel_t *capcas; + cap_exec_t *capexec; + const char *paths[1] = {"echo"}; + char *str; + FILE *f; + + capcas = cap_init(); + capexec = cap_exec_init(capcas, 1, paths); + ATF_REQUIRE(capexec != NULL); + f = cap_exec_open(capexec, "echo hello", "r"); + ATF_REQUIRE(f != NULL); + str = atf_utils_readline(fileno(f)); + ATF_CHECK(strcmp(str, "hello") == 0); + + free(str); + cap_exec_close(capexec, f); + cap_exec_free(capexec); + cap_close(capcas); +} + +ATF_TC_WITHOUT_HEAD(capexec__exec_command_write); +ATF_TC_BODY(capexec__exec_command_write, tc) +{ + cap_channel_t *capcas; + cap_exec_t *capexec; + const char *paths[1] = {"rot13"}; + FILE *f; + + capcas = cap_init(); + capexec = cap_exec_init(capcas, 1, paths); + ATF_REQUIRE(capexec != NULL); + f = cap_exec_open(capexec, "rot13 > output_file", "w"); + ATF_REQUIRE(f != NULL); + + fprintf(f, "uryyb"); + cap_exec_close(capexec, f); + ATF_REQUIRE(atf_utils_file_exists("output_file")); + ATF_CHECK(atf_utils_compare_file("output_file", "hello")); + + cap_exec_free(capexec); + cap_close(capcas); +} + +ATF_TC_WITHOUT_HEAD(capexec__unpermitted_command); +ATF_TC_BODY(capexec__unpermitted_command, tc) +{ + cap_channel_t *capcas; + cap_exec_t *capexec; + const char *paths[1] = {"true"}; + FILE *f; + + capcas = cap_init(); + capexec = cap_exec_init(capcas, 1, paths); + ATF_REQUIRE(capexec != NULL); + f = cap_exec_open(capexec, "false", "r"); + ATF_REQUIRE(f == NULL); + ATF_REQUIRE(errno = ENOTCAPABLE); + + cap_exec_free(capexec); + cap_close(capcas); +} + +ATF_TC_WITHOUT_HEAD(capexec__mismatched_close); +ATF_TC_BODY(capexec__mismatched_close, tc) +{ + cap_channel_t *capcas; + cap_exec_t *capexec; + FILE *f; + int ret; + + capcas = cap_init(); + capexec = cap_exec_init(capcas, 0, NULL); + + atf_utils_create_file("some_file", ""); + f = fopen("some_file", "r"); + ATF_REQUIRE(f != NULL); + + ret = cap_exec_close(capexec, f); + ATF_CHECK(ret == -1); + ATF_CHECK(errno == ESRCH); + + fclose(f); + cap_exec_free(capexec); + cap_close(capcas); +} + +ATF_TC_WITHOUT_HEAD(capexec__multiple_services); +ATF_TC_BODY(capexec__multiple_services, tc) +{ + cap_channel_t *capcas; + cap_exec_t *capexec_1, *capexec_2; + const char *paths_1[1] = {"true"}; + const char *paths_2[1] = {"false"}; + FILE *f; + + capcas = cap_init(); + capexec_1 = cap_exec_init(capcas, 1, paths_1); + capexec_2 = cap_exec_init(capcas, 1, paths_2); + ATF_REQUIRE(capexec_1 != NULL); + ATF_REQUIRE(capexec_2 != NULL); + + f = cap_exec_open(capexec_1, "true", "r"); + ATF_REQUIRE(f != NULL); + cap_exec_close(capexec_1, f); + f = cap_exec_open(capexec_2, "false", "r"); + ATF_REQUIRE(f != NULL); + cap_exec_close(capexec_2, f); + + f = cap_exec_open(capexec_1, "false", "r"); + ATF_REQUIRE(f == NULL); + ATF_REQUIRE(errno = ENOTCAPABLE); + f = cap_exec_open(capexec_2, "true", "r"); + ATF_REQUIRE(f == NULL); + ATF_REQUIRE(errno = ENOTCAPABLE); + + cap_exec_free(capexec_1); + f = cap_exec_open(capexec_2, "false", "r"); + ATF_REQUIRE(f != NULL); + cap_exec_close(capexec_2, f); + + cap_exec_free(capexec_2); + cap_close(capcas); +} + +ATF_TC_WITHOUT_HEAD(capexec__write_then_read_file); +ATF_TC_BODY(capexec__write_then_read_file, tc) +{ + cap_channel_t *capcas; + cap_exec_t *capexec; + const char *paths[1] = {"bzip2"}; + char *str; + FILE *f; + + capcas = cap_init(); + capexec = cap_exec_init(capcas, 1, paths); + ATF_REQUIRE(capexec != NULL); + + f = cap_exec_open(capexec, "bzip2 > some_file", "w"); + ATF_REQUIRE(f != NULL); + fprintf(f, "hello"); + cap_exec_close(capexec, f); + + f = cap_exec_open(capexec, "bzip2 -d < some_file", "r"); + ATF_REQUIRE(f != NULL); + str = atf_utils_readline(fileno(f)); + ATF_CHECK(strcmp(str, "hello") == 0); + cap_exec_close(capexec, f); + + cap_exec_free(capexec); + cap_close(capcas); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, capexec__basic); + ATF_TP_ADD_TC(tp, capexec__exec_command); + ATF_TP_ADD_TC(tp, capexec__exec_command_read); + ATF_TP_ADD_TC(tp, capexec__exec_command_write); + ATF_TP_ADD_TC(tp, capexec__unpermitted_command); + ATF_TP_ADD_TC(tp, capexec__mismatched_close); + ATF_TP_ADD_TC(tp, capexec__multiple_services); + ATF_TP_ADD_TC(tp, capexec__write_then_read_file); + + return (atf_no_error()); +}