diff --git a/etc/mtree/BSD.include.dist b/etc/mtree/BSD.include.dist index dffce2469f33..a243f085a1be 100644 --- a/etc/mtree/BSD.include.dist +++ b/etc/mtree/BSD.include.dist @@ -333,6 +333,8 @@ .. mac_bsdextended .. + mac_grantbylabel + .. mac_lomac .. mac_mls diff --git a/include/Makefile b/include/Makefile index f3c9230f6d40..1d7651216cf9 100644 --- a/include/Makefile +++ b/include/Makefile @@ -67,7 +67,9 @@ LSUBDIRS= dev/acpica dev/agp dev/ciss dev/filemon dev/firewire \ netinet/netdump \ netinet/tcp_stacks \ netlink/route \ - security/mac_biba security/mac_bsdextended security/mac_lomac \ + security/mac_biba security/mac_bsdextended \ + security/mac_grantbylabel \ + security/mac_lomac \ security/mac_mls security/mac_partition \ security/mac_veriexec \ sys/disk \ diff --git a/lib/libveriexec/Makefile b/lib/libveriexec/Makefile index aa1a60640f0c..154605142074 100644 --- a/lib/libveriexec/Makefile +++ b/lib/libveriexec/Makefile @@ -8,8 +8,10 @@ INCS= libveriexec.h WARNS?= 2 SRCS= \ + exec_script.c \ + gbl_check.c \ veriexec_check.c \ - veriexec_get.c + veriexec_get.c \ .include diff --git a/lib/libveriexec/exec_script.c b/lib/libveriexec/exec_script.c new file mode 100644 index 000000000000..cdbbbab54a03 --- /dev/null +++ b/lib/libveriexec/exec_script.c @@ -0,0 +1,159 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2019-2023, Juniper Networks, Inc. + * All rights reserved. + * + * 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 ``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 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 "libveriexec.h" + +static char * +find_interpreter(const char *script) +{ + static const char ws[] = " \t\n\r"; + static char buf[MAXPATHLEN+4]; /* allow space for #! etc */ + char *cp; + int fd; + int n; + + cp = NULL; + if ((fd = open(script, O_RDONLY)) >= 0) { + if ((n = read(fd, buf, sizeof(buf))) > 0) { + if (strncmp(buf, "#!", 2) == 0) { + buf[sizeof(buf) - 1] = '\0'; + cp = &buf[2]; + if ((n = strspn(cp, ws)) > 0) + cp += n; + if ((n = strcspn(cp, ws)) > 0) { + cp[n] = '\0'; + } else { + cp = NULL; + } + } + } + close(fd); + } + return (cp); +} + +/** + * @brief exec a python or similar script + * + * Python and similar scripts must normally be signed and + * run directly rather than fed to the interpreter which + * is not normally allowed to be run directly. + * + * If direct execv of script fails due to EAUTH + * and process has GBL_VERIEXEC syslog event and run via + * interpreter. + * + * If interpreter is NULL look at first block of script + * to find ``#!`` magic. + * + * @prarm[in] interpreter + * if NULL, extract from script if necessary + * + * @prarm[in] argv + * argv for execv(2) + * argv[0] must be full path. + * Python at least requires argv[1] to also be the script path. + * + * @return + * error on failure usually EPERM or EAUTH + */ +int +execv_script(const char *interpreter, char * const *argv) +{ + const char *script; + int rc; + + script = argv[0]; + if (veriexec_check_path(script) == 0) { + rc = execv(script, argv); + } + /* still here? we might be allowed to run via interpreter */ + if (gbl_check_pid(0) & GBL_VERIEXEC) { + if (!interpreter) + interpreter = find_interpreter(script); + if (interpreter) { + syslog(LOG_NOTICE, "running %s via %s", + script, interpreter); + rc = execv(interpreter, argv); + } + } + return (rc); +} + +#if defined(MAIN) || defined(UNIT_TEST) +#include +#include + +int +main(int argc __unused, char *argv[]) +{ + const char *interp; + int c; + int s; + pid_t child; + + openlog("exec_script", LOG_PID|LOG_PERROR, LOG_DAEMON); + + interp = NULL; + while ((c = getopt(argc, argv, "i:")) != -1) { + switch (c) { + case 'i': + interp = optarg; + break; + default: + errx(1, "unknown option: -%c", c); + break; + } + } + argc -= optind; + argv += optind; + /* we need a child */ + child = fork(); + if (child < 0) + err(2, "fork"); + if (child == 0) { + c = execv_script(interp, argv); + err(2, "exec_script(%s,%s)", interp, argv[0]); + } + c = waitpid(child, &s, 0); + printf("%s: exit %d\n", argv[0], WEXITSTATUS(s)); + return (0); +} +#endif diff --git a/lib/libveriexec/gbl_check.c b/lib/libveriexec/gbl_check.c new file mode 100644 index 000000000000..c0da93d0857c --- /dev/null +++ b/lib/libveriexec/gbl_check.c @@ -0,0 +1,125 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2019-2023, Juniper Networks, Inc. + * All rights reserved. + * + * 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 ``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 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 + +/** + * @brief does path have a gbl label + * + * @return + * @li 0 if no/empty label or module not loaded + * @li value of label + */ +unsigned int +gbl_check_path(const char *path) +{ + struct mac_grantbylabel_fetch_gbl_args gbl; + int fd; + int rc; + + rc = 0; + if ((fd = open(path, O_RDONLY|O_VERIFY)) >= 0) { + gbl.u.fd = fd; + if (mac_syscall(MAC_GRANTBYLABEL_NAME, + MAC_GRANTBYLABEL_FETCH_GBL, + &gbl) == 0) { + if (gbl.gbl != GBL_EMPTY) + rc = gbl.gbl; + } + close(fd); + } + return(rc); +} + +/** + * @brief does pid have a gbl label + * + * @return + * @li 0 if no/empty label or module not loaded + * @li value of label + */ +unsigned int +gbl_check_pid(pid_t pid) +{ + struct mac_grantbylabel_fetch_gbl_args gbl; + int rc; + + rc = 0; + gbl.u.pid = pid; + if (mac_syscall(MAC_GRANTBYLABEL_NAME, + MAC_GRANTBYLABEL_FETCH_PID_GBL, &gbl) == 0) { + if (gbl.gbl != GBL_EMPTY) + rc = gbl.gbl; + } + return(rc); +} + + +#ifdef UNIT_TEST +#include +#include +#include + +int +main(int argc, char *argv[]) +{ + pid_t pid; + int pflag = 0; + int c; + unsigned int gbl; + + while ((c = getopt(argc, argv, "p")) != -1) { + switch (c) { + case 'p': + pflag = 1; + break; + default: + break; + } + } + for (; optind < argc; optind++) { + + if (pflag) { + pid = atoi(argv[optind]); + gbl = gbl_check_pid(pid); + } else { + gbl = gbl_check_path(argv[optind]); + } + printf("arg=%s, gbl=%#o\n", argv[optind], gbl); + } + return 0; +} +#endif diff --git a/lib/libveriexec/libveriexec.h b/lib/libveriexec/libveriexec.h index 2d726e76af01..32b2f20d6123 100644 --- a/lib/libveriexec/libveriexec.h +++ b/lib/libveriexec/libveriexec.h @@ -1,7 +1,7 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * - * Copyright (c) 2011, 2012, 2013, 2015, Juniper Networks, Inc. + * Copyright (c) 2011-2023, Juniper Networks, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,7 +40,16 @@ int veriexec_get_path_params(const char *, struct mac_veriexec_syscall_params *); int veriexec_check_path_label(const char *, const char *); int veriexec_check_pid_label(pid_t, const char *); +char * veriexec_get_path_label(const char *, char *, size_t); +char * veriexec_get_pid_label(pid_t, char *, size_t); +unsigned int gbl_check_path(const char *); +unsigned int gbl_check_pid(pid_t); +int execv_script(const char *, char * const *); -#define HAVE_VERIEXEC_CHECK_PID_LABEL 1 +#define HAVE_GBL_CHECK_PID 1 +#define HAVE_VERIEXEC_CHECK_PATH_LABEL 1 +#define HAVE_VERIEXEC_CHECK_PID_LABEL 1 +#define HAVE_VERIEXEC_GET_PATH_LABEL 1 +#define HAVE_VERIEXEC_GET_PID_LABEL 1 #endif /* __LIBVERIEXEC_H__ */ diff --git a/lib/libveriexec/veriexec_get.c b/lib/libveriexec/veriexec_get.c index 8d12d0a9890c..4cfa81de210c 100644 --- a/lib/libveriexec/veriexec_get.c +++ b/lib/libveriexec/veriexec_get.c @@ -59,7 +59,7 @@ veriexec_get_pid_params(pid_t pid, } /** - * @brief get veriexec params for a process + * @brief get veriexec params for a path * * @return * @li 0 if successful @@ -79,9 +79,119 @@ veriexec_get_path_params(const char *file, MAC_VERIEXEC_GET_PARAMS_PATH_SYSCALL, &args); } +/** + * @brief return label associated with a path + * + * @param[in] file + * pathname of file to lookup. + * + * @prarm[in] buf + * if not NULL and big enough copy label to buf. + * otherwise return a copy of label. + * + * @param[in] bufsz + * size of buf, must be greater than found label length. + * + * @return + * @li NULL if no label + * @li pointer to label + */ +char * +veriexec_get_path_label(const char *file, char *buf, size_t bufsz) +{ + struct mac_veriexec_syscall_params params; + char *cp; + + cp = NULL; + if (veriexec_get_path_params(file, ¶ms) == 0) { + /* Does label contain a label */ + if (params.labellen > 0) { + if (buf != NULL && bufsz > params.labellen) { + strlcpy(buf, params.label, bufsz); + cp = buf; + } else + cp = strdup(params.label); + } + } + return cp; +} + +/** + * @brief return label of a process + * + * + * @param[in] pid + * process id of interest. + * + * @prarm[in] buf + * if not NULL and big enough copy label to buf. + * otherwise return a copy of label. + * + * @param[in] bufsz + * size of buf, must be greater than found label length. + * + * @return + * @li NULL if no label + * @li pointer to label + */ +char * +veriexec_get_pid_label(pid_t pid, char *buf, size_t bufsz) +{ + struct mac_veriexec_syscall_params params; + char *cp; + + cp = NULL; + if (veriexec_get_pid_params(pid, ¶ms) == 0) { + /* Does label contain a label */ + if (params.labellen > 0) { + if (buf != NULL && bufsz > params.labellen) { + strlcpy(buf, params.label, bufsz); + cp = buf; + } else + cp = strdup(params.label); + } + } + return cp; +} + +/* + * we match + * ^want$ + * ^want, + * ,want, + * ,want$ + * + * and if want ends with / then we match that prefix too. + */ +static int +check_label_want(const char *label, size_t labellen, + const char *want, size_t wantlen) +{ + char *cp; + + /* Does label contain [,][,] ? */ + if (labellen > 0 && wantlen > 0 && + (cp = strstr(label, want)) != NULL) { + if (cp == label || cp[-1] == ',') { + if (cp[wantlen] == '\0' || cp[wantlen] == ',' || + (cp[wantlen-1] == '/' && want[wantlen-1] == '/')) + return 1; /* yes */ + } + } + return 0; /* no */ +} + /** * @brief check if a process has label that contains what we want * + * @param[in] pid + * process id of interest. + * + * @param[in] want + * the label we are looking for + * if want ends with ``/`` it is assumed a prefix + * otherwise we expect it to be followed by ``,`` or end of string. + * * @return * @li 0 if no * @li 1 if yes @@ -90,20 +200,13 @@ int veriexec_check_pid_label(pid_t pid, const char *want) { struct mac_veriexec_syscall_params params; - char *cp; size_t n; if (want != NULL && + (n = strlen(want)) > 0 && veriexec_get_pid_params(pid, ¶ms) == 0) { - /* Does label contain [,][,] ? */ - if (params.labellen > 0 && - (cp = strstr(params.label, want)) != NULL) { - if (cp == params.label || cp[-1] == ',') { - n = strlen(want); - if (cp[n] == '\0' || cp[n] == ',') - return 1; /* yes */ - } - } + return check_label_want(params.label, params.labellen, + want, n); } return 0; /* no */ } @@ -111,6 +214,14 @@ veriexec_check_pid_label(pid_t pid, const char *want) /** * @brief check if a path has label that contains what we want * + * @param[in] path + * pathname of interest. + * + * @param[in] want + * the label we are looking for + * if want ends with ``/`` it is assumed a prefix + * otherwise we expect it to be followed by ``,`` or end of string. + * * @return * @li 0 if no * @li 1 if yes @@ -119,20 +230,13 @@ int veriexec_check_path_label(const char *file, const char *want) { struct mac_veriexec_syscall_params params; - char *cp; size_t n; if (want != NULL && file != NULL && + (n = strlen(want)) > 0 && veriexec_get_path_params(file, ¶ms) == 0) { - /* Does label contain [,][,] ? */ - if (params.labellen > 0 && - (cp = strstr(params.label, want)) != NULL) { - if (cp == params.label || cp[-1] == ',') { - n = strlen(want); - if (cp[n] == '\0' || cp[n] == ',') - return 1; /* yes */ - } - } + return check_label_want(params.label, params.labellen, + want, n); } return 0; /* no */ } @@ -167,13 +271,19 @@ main(int argc, char *argv[]) { struct mac_veriexec_syscall_params params; pid_t pid; + char buf[BUFSIZ]; + const char *cp; char *want = NULL; + int lflag = 0; int pflag = 0; int error; int c; - while ((c = getopt(argc, argv, "pw:")) != -1) { + while ((c = getopt(argc, argv, "lpw:")) != -1) { switch (c) { + case 'l': + lflag = 1; + break; case 'p': pflag = 1; break; @@ -188,6 +298,12 @@ main(int argc, char *argv[]) if (pflag) { pid = atoi(argv[optind]); + if (lflag) { + cp = veriexec_get_pid_label(pid, buf, sizeof(buf)); + if (cp) + printf("pid=%d label='%s'\n", pid, cp); + continue; + } if (want) { error = veriexec_check_pid_label(pid, want); printf("pid=%d want='%s': %d\n", @@ -196,6 +312,20 @@ main(int argc, char *argv[]) } error = veriexec_get_pid_params(pid, ¶ms); } else { + if (lflag) { + cp = veriexec_get_path_label(argv[optind], + buf, sizeof(buf)); + if (cp) + printf("path='%s' label='%s'\n", + argv[optind], cp); + continue; + } + if (want) { + error = veriexec_check_path_label(argv[optind], want); + printf("path='%s' want='%s': %d\n", + argv[optind], want, error); + continue; + } error = veriexec_get_path_params(argv[optind], ¶ms); } if (error) { diff --git a/sbin/veriexec/Makefile.depend b/sbin/veriexec/Makefile.depend index 9307e12bc8dd..cbe57ebe4cf8 100644 --- a/sbin/veriexec/Makefile.depend +++ b/sbin/veriexec/Makefile.depend @@ -1,7 +1,6 @@ # Autogenerated - do NOT edit! DIRDEPS = \ - gnu/lib/csu \ include \ include/xlocale \ lib/${CSU_DIR} \ @@ -10,6 +9,7 @@ DIRDEPS = \ lib/libcompiler_rt \ lib/libsecureboot \ lib/libveriexec \ + usr.bin/yacc.host \ .include diff --git a/sbin/veriexec/veriexec.8 b/sbin/veriexec/veriexec.8 index c325f267689d..734b1cda40f6 100644 --- a/sbin/veriexec/veriexec.8 +++ b/sbin/veriexec/veriexec.8 @@ -1,5 +1,7 @@ .\"- -.\" Copyright (c) 2018, Juniper Networks, Inc. +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.\" Copyright (c) 2018-2023, Juniper Networks, Inc. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions @@ -22,7 +24,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE .\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd July 8, 2022 +.Dd August 8, 2023 .Dt VERIEXEC 8 .Os .Sh NAME @@ -39,6 +41,9 @@ .Nm .Fl i Ar state .Nm +.Fl l +.Ar file ... +.Nm .Fl x .Ar file ... .Sh DESCRIPTION @@ -67,6 +72,14 @@ and with to query the current .Ar state . .Pp +With +.Fl l +.Nm +will report any labels associated with the remaining arguments +assumed to be files. +If only a single file argument is given, the bare label (if any) +will be reported, otherwise the pathname followed by label. +.Pp The final form with .Fl x is used to test whether diff --git a/sbin/veriexec/veriexec.c b/sbin/veriexec/veriexec.c index 3899a781625a..0619b261665e 100644 --- a/sbin/veriexec/veriexec.c +++ b/sbin/veriexec/veriexec.c @@ -1,7 +1,7 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * - * Copyright (c) 2018, Juniper Networks, Inc. + * Copyright (c) 2018-2023, Juniper Networks, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -53,7 +53,7 @@ static int veriexec_usage(void) { printf("%s", - "Usage:\tveriexec [-h] [-i state] [-C] [-xv state|verbosity] [path]\n"); + "Usage:\tveriexec [-C path] [-hlxv] [-[iz] state] [path]\n"); return (0); } @@ -135,6 +135,45 @@ veriexec_state_modify(const char *arg_text) return (state); } +#ifdef HAVE_VERIEXEC_GET_PATH_LABEL +static void +veriexec_check_labels(int argc, char *argv[]) +{ + char buf[BUFSIZ]; + char *cp; + int n; + + n = (argc - optind); + for (; optind < argc; optind++) { + cp = veriexec_get_path_label(argv[optind], buf, sizeof(buf)); + if (cp) { + if (n > 1) + printf("%s: %s\n", argv[optind], cp); + else + printf("%s\n", cp); + if (cp != buf) + free(cp); + } + } + exit(EX_OK); +} +#endif + +static void +veriexec_check_paths(int argc, char *argv[]) +{ + int x; + + x = EX_OK; + for (; optind < argc; optind++) { + if (veriexec_check_path(argv[optind])) { + warn("%s", argv[optind]); + x = 2; + } + } + exit(x); +} + int main(int argc, char *argv[]) { @@ -147,7 +186,7 @@ main(int argc, char *argv[]) dev_fd = open(_PATH_DEV_VERIEXEC, O_WRONLY, 0); - while ((c = getopt(argc, argv, "hC:i:Sxvz:")) != -1) { + while ((c = getopt(argc, argv, "C:hi:lSxvz:")) != -1) { switch (c) { case 'h': /* Print usage info */ @@ -173,6 +212,11 @@ main(int argc, char *argv[]) exit((x & state) == 0); break; +#ifdef HAVE_VERIEXEC_GET_PATH_LABEL + case 'l': + veriexec_check_labels(argc, argv); + break; +#endif case 'S': /* Strictly enforce certificate validity */ ve_enforce_validity_set(1); @@ -188,13 +232,7 @@ main(int argc, char *argv[]) /* * -x says all other args are paths to check. */ - for (x = EX_OK; optind < argc; optind++) { - if (veriexec_check_path(argv[optind])) { - warn("%s", argv[optind]); - x = 2; - } - } - exit(x); + veriexec_check_paths(argc, argv); break; case 'z': /* Modify the state */ diff --git a/sys/conf/files b/sys/conf/files index 8d38b9cc8a2e..2d429c11f523 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -5147,6 +5147,7 @@ security/mac_priority/mac_priority.c optional mac_priority security/mac_seeotheruids/mac_seeotheruids.c optional mac_seeotheruids security/mac_stub/mac_stub.c optional mac_stub security/mac_test/mac_test.c optional mac_test +security/mac_grantbylabel/mac_grantbylabel.c optional mac_grantbylabel security/mac_veriexec/mac_veriexec.c optional mac_veriexec security/mac_veriexec/veriexec_fingerprint.c optional mac_veriexec security/mac_veriexec/veriexec_metadata.c optional mac_veriexec diff --git a/sys/conf/options b/sys/conf/options index 56c1a33216d5..ae94d1b60492 100644 --- a/sys/conf/options +++ b/sys/conf/options @@ -168,6 +168,7 @@ MAC_SEEOTHERUIDS opt_dontuse.h MAC_STATIC opt_mac.h MAC_STUB opt_dontuse.h MAC_TEST opt_dontuse.h +MAC_GRANTBYLABEL opt_dontuse.h MAC_VERIEXEC opt_dontuse.h MAC_VERIEXEC_SHA1 opt_dontuse.h MAC_VERIEXEC_SHA256 opt_dontuse.h diff --git a/sys/security/mac_grantbylabel/mac_grantbylabel.c b/sys/security/mac_grantbylabel/mac_grantbylabel.c new file mode 100644 index 000000000000..848131e54590 --- /dev/null +++ b/sys/security/mac_grantbylabel/mac_grantbylabel.c @@ -0,0 +1,506 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2018-2023, Juniper Networks, Inc. + * All rights reserved. + * + * 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 ``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 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 "opt_mac.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mac_grantbylabel.h" +#include + +#define MAC_GRANTBYLABEL_FULLNAME "MAC/grantbylabel" + +SYSCTL_DECL(_security_mac); +SYSCTL_NODE(_security_mac, OID_AUTO, grantbylabel, CTLFLAG_RW, 0, + "MAC/grantbylabel policy controls"); + +#ifdef MAC_DEBUG +static int mac_grantbylabel_debug; + +SYSCTL_INT(_security_mac_grantbylabel, OID_AUTO, debug, CTLFLAG_RW, + &mac_grantbylabel_debug, 0, "Debug mac_grantbylabel"); + +#define GRANTBYLABEL_DEBUG(n, x) if (mac_grantbylabel_debug >= (n)) printf x + +#define MAC_GRANTBYLABEL_DBG(_lvl, _fmt, ...) \ + do { \ + GRANTBYLABEL_DEBUG((_lvl), (MAC_GRANTBYLABEL_FULLNAME ": " \ + _fmt "\n", ##__VA_ARGS__)); \ + } while(0) +#else +#define MAC_GRANTBYLABEL_DBG(_lvl, _fmt, ...) +#endif + + +/* label token prefix */ +#define GBL_PREFIX "gbl/" + +static int mac_grantbylabel_slot; + +#define SLOT(l) \ + mac_label_get((l), mac_grantbylabel_slot) +#define SLOT_SET(l, v) \ + mac_label_set((l), mac_grantbylabel_slot, (v)) + + +/** + * @brief parse label into bitmask + * + * We are only interested in tokens prefixed by GBL_PREFIX ("gbl/"). + * + * @return 32bit mask + */ +static gbl_label_t +gbl_parse_label(const char *label) +{ + gbl_label_t gbl; + char *cp; + + if (!(label && *label)) + return GBL_EMPTY; + gbl = 0; + for (cp = strstr(label, GBL_PREFIX); cp; cp = strstr(cp, GBL_PREFIX)) { + /* check we didn't find "fugbl/" */ + if (cp > label && cp[-1] != ',') { + cp += sizeof(GBL_PREFIX); + continue; + } + cp += sizeof(GBL_PREFIX) - 1; + switch (*cp) { + case 'b': + if (strncmp(cp, "bind", 4) == 0) + gbl |= GBL_BIND; + break; + case 'd': + if (strncmp(cp, "daemon", 6) == 0) + gbl |= (GBL_BIND|GBL_IPC|GBL_NET|GBL_PROC| + GBL_SYSCTL|GBL_VACCESS); + break; + case 'i': + if (strncmp(cp, "ipc", 3) == 0) + gbl |= GBL_IPC; + break; + case 'k': + if (strncmp(cp, "kmem", 4) == 0) + gbl |= GBL_KMEM; + break; + case 'n': + if (strncmp(cp, "net", 3) == 0) + gbl |= GBL_NET; + break; + case 'p': + if (strncmp(cp, "proc", 4) == 0) + gbl |= GBL_PROC; + break; + case 'r': + if (strncmp(cp, "rtsock", 6) == 0) + gbl |= GBL_RTSOCK; + break; + case 's': + if (strncmp(cp, "sysctl", 6) == 0) + gbl |= GBL_SYSCTL; + break; + case 'v': + if (strncmp(cp, "vaccess", 7) == 0) + gbl |= GBL_VACCESS; + else if (strncmp(cp, "veriexec", 8) == 0) + gbl |= GBL_VERIEXEC; + break; + default: /* ignore unknown? */ + MAC_GRANTBYLABEL_DBG(1, + "ignoring unknown token at %s/%s", + GBL_PREFIX, cp); + break; + } + } + + return gbl; +} + + +/** + * @brief get the v_label for a vnode + * + * Lookup the label if not already set in v_label + * + * @return 32bit mask or 0 on error + */ +static gbl_label_t +gbl_get_vlabel(struct vnode *vp, struct ucred *cred) +{ + struct vattr va; + const char *label; + gbl_label_t gbl; + int error; + + gbl = SLOT(vp->v_label); + if (gbl == 0) { + error = VOP_GETATTR(vp, &va, cred); + if (error == 0) { + label = mac_veriexec_metadata_get_file_label(va.va_fsid, + va.va_fileid, va.va_gen, FALSE); + if (label) { + MAC_GRANTBYLABEL_DBG(1, + "label=%s dev=%ju, file %ju.%lu", + label, + (uintmax_t)va.va_fsid, + (uintmax_t)va.va_fileid, + va.va_gen); + gbl = gbl_parse_label(label); + } else { + gbl = GBL_EMPTY; + MAC_GRANTBYLABEL_DBG(2, "no label dev=%ju, file %ju.%lu", + (uintmax_t)va.va_fsid, + (uintmax_t)va.va_fileid, + va.va_gen); + } + } + } + return gbl; +} + + +/** + * @brief grant priv if warranted + * + * If the cred is root, we have nothing to do. + * Otherwise see if the current process has a label + * that grants it the requested priv. + */ +static int +mac_grantbylabel_priv_grant(struct ucred *cred, int priv) +{ + gbl_label_t label; + int rc; + + rc = EPERM; /* default response */ + + if ((curproc->p_flag & (P_KPROC|P_SYSTEM))) + return rc; /* not interested */ + + switch (priv) { + case PRIV_KMEM_READ: + case PRIV_KMEM_WRITE: + break; + case PRIV_VERIEXEC_DIRECT: + case PRIV_VERIEXEC_NOVERIFY: + /* XXX might want to skip in FIPS mode */ + break; + default: + if (cred->cr_uid == 0) + return rc; /* not interested */ + break; + } + + label = (gbl_label_t)(SLOT(curproc->p_textvp->v_label) | + SLOT(curproc->p_label)); + + /* + * We look at the extra privs granted + * via process label. + */ + switch (priv) { + case PRIV_IPC_READ: + case PRIV_IPC_WRITE: + if (label & GBL_IPC) + rc = 0; + break; + case PRIV_KMEM_READ: + case PRIV_KMEM_WRITE: + if (label & GBL_KMEM) + rc = 0; + break; + case PRIV_NETINET_BINDANY: + case PRIV_NETINET_RESERVEDPORT: /* socket bind low port */ + case PRIV_NETINET_REUSEPORT: + if (label & GBL_BIND) + rc = 0; + break; + case PRIV_NETINET_ADDRCTRL6: + case PRIV_NET_LAGG: + case PRIV_NET_SETIFFIB: + case PRIV_NET_SETIFVNET: + case PRIV_NETINET_SETHDROPTS: + case PRIV_NET_VXLAN: + case PRIV_NETINET_GETCRED: + case PRIV_NETINET_IPSEC: + case PRIV_NETINET_RAW: + if (label & GBL_NET) + rc = 0; + break; + case PRIV_NETINET_MROUTE: + case PRIV_NET_ROUTE: + if (label & GBL_RTSOCK) + rc = 0; + break; + case PRIV_PROC_LIMIT: + case PRIV_PROC_SETRLIMIT: + if (label & GBL_PROC) + rc = 0; + break; + case PRIV_SYSCTL_WRITE: + if (label & GBL_SYSCTL) + rc = 0; + break; + case PRIV_VFS_READ: + case PRIV_VFS_WRITE: + if (label & GBL_KMEM) + rc = 0; + /* FALLTHROUGH */ + case PRIV_VFS_ADMIN: + case PRIV_VFS_BLOCKRESERVE: + case PRIV_VFS_CHOWN: + case PRIV_VFS_EXEC: /* vaccess file and accmode & VEXEC */ + case PRIV_VFS_GENERATION: + case PRIV_VFS_LOOKUP: /* vaccess DIR */ + if (label & GBL_VACCESS) + rc = 0; + break; + case PRIV_VERIEXEC_DIRECT: + /* + * We are here because we are attempting to direct exec + * something with the 'indirect' flag set. + * We need to check parent label for this one. + */ + PROC_LOCK(curproc); + label = (gbl_label_t)SLOT(curproc->p_pptr->p_textvp->v_label); + if (label & GBL_VERIEXEC) { + rc = 0; + /* + * Of course the only reason to be running an + * interpreter this way is to bypass O_VERIFY + * so we can run unsigned script. + * We set GBL_VERIEXEC on p_label for + * PRIV_VERIEXEC_NOVERIFY below + */ + SLOT_SET(curproc->p_label, GBL_VERIEXEC); + } + PROC_UNLOCK(curproc); + break; + case PRIV_VERIEXEC_NOVERIFY: + /* we look at p_label! see above */ + label = (gbl_label_t)SLOT(curproc->p_label); + if (label & GBL_VERIEXEC) + rc = 0; + break; + default: + break; + } + MAC_GRANTBYLABEL_DBG(rc ? 1 : 2, + "pid=%d priv=%d, label=%#o rc=%d", + curproc->p_pid, priv, label, rc); + + return rc; +} + + +/* + * If proc->p_textvp does not yet have a label, + * fetch file info from mac_veriexec + * and set label (if any) else set. + * If there is no label set it to GBL_EMPTY. + */ +static int +mac_grantbylabel_proc_check_resource(struct ucred *cred, + struct proc *proc) +{ + gbl_label_t gbl; + + if (!SLOT(proc->p_textvp->v_label)) { + gbl = gbl_get_vlabel(proc->p_textvp, cred); + if (gbl == 0) + gbl = GBL_EMPTY; + SLOT_SET(proc->p_textvp->v_label, gbl); + } + return 0; +} + +static int +mac_grantbylabel_syscall(struct thread *td, int call, void *arg) +{ + cap_rights_t rights; + struct mac_grantbylabel_fetch_gbl_args gbl_args; + struct file *fp; + struct proc *proc; + int error; + int proc_locked; + + switch (call) { + case MAC_GRANTBYLABEL_FETCH_GBL: + case MAC_GRANTBYLABEL_FETCH_PID_GBL: + error = copyin(arg, &gbl_args, sizeof(gbl_args)); + if (error) + return error; + gbl_args.gbl = 0; + break; + default: + return EOPNOTSUPP; + break; + } + proc_locked = 0; + switch (call) { + case MAC_GRANTBYLABEL_FETCH_GBL: + error = getvnode(td, gbl_args.u.fd, + cap_rights_init(&rights), &fp); + if (error) + return (error); + + if (fp->f_type != DTYPE_VNODE) { + error = EINVAL; + goto cleanup_file; + } + + vn_lock(fp->f_vnode, LK_SHARED | LK_RETRY); + gbl_args.gbl = gbl_get_vlabel(fp->f_vnode, td->td_ucred); + if (gbl_args.gbl == 0) + error = EOPNOTSUPP; + else + error = 0; + VOP_UNLOCK(fp->f_vnode); +cleanup_file: + fdrop(fp, td); + break; + case MAC_GRANTBYLABEL_FETCH_PID_GBL: + error = 0; + if (gbl_args.u.pid == 0 + || gbl_args.u.pid == curproc->p_pid) { + proc = curproc; + } else { + proc = pfind(gbl_args.u.pid); + if (proc == NULL) + return (EINVAL); + proc_locked = 1; + } + gbl_args.gbl = (SLOT(proc->p_textvp->v_label) | + SLOT(proc->p_label)); + if (proc_locked) + PROC_UNLOCK(proc); + break; + } + if (error == 0) { + error = copyout(&gbl_args, arg, sizeof(gbl_args)); + } + return error; +} + + +static void +mac_grantbylabel_proc_init_label(struct label *label) +{ + + SLOT_SET(label, 0); /* not yet set! */ +} + +static void +mac_grantbylabel_vnode_init_label(struct label *label) +{ + + SLOT_SET(label, 0); /* not yet set! */ +} + +/** + * @brief set v_label if needed + */ +static int +mac_grantbylabel_vnode_check_exec(struct ucred *cred __unused, + struct vnode *vp __unused, struct label *label __unused, + struct image_params *imgp, struct label *execlabel __unused) +{ + gbl_label_t gbl; + + gbl = SLOT(vp->v_label); + if (gbl == 0) { + gbl = gbl_get_vlabel(vp, cred); + if (gbl == 0) + gbl = GBL_EMPTY; + MAC_GRANTBYLABEL_DBG(1, "vnode_check_exec label=%#o", gbl); + SLOT_SET(vp->v_label, gbl); + } + return 0; +} + +static void +mac_grantbylabel_copy_label(struct label *src, struct label *dest) +{ + SLOT_SET(dest, SLOT(src)); +} + +/** + * @brief if interpreting copy script v_label to proc p_label + */ +static int +mac_grantbylabel_vnode_execve_will_transition(struct ucred *old, + struct vnode *vp, struct label *vplabel, + struct label *interpvplabel, struct image_params *imgp, + struct label *execlabel) +{ + gbl_label_t gbl; + + if (imgp->interpreted) { + gbl = SLOT(interpvplabel); + if (gbl) { + SLOT_SET(imgp->proc->p_label, gbl); + } + MAC_GRANTBYLABEL_DBG(1, "execve_will_transition label=%#o", gbl); + } + return 0; +} + + +static struct mac_policy_ops mac_grantbylabel_ops = +{ + .mpo_proc_check_resource = mac_grantbylabel_proc_check_resource, + .mpo_priv_grant = mac_grantbylabel_priv_grant, + .mpo_syscall = mac_grantbylabel_syscall, + .mpo_proc_init_label = mac_grantbylabel_proc_init_label, + .mpo_vnode_check_exec = mac_grantbylabel_vnode_check_exec, + .mpo_vnode_copy_label = mac_grantbylabel_copy_label, + .mpo_vnode_execve_will_transition = mac_grantbylabel_vnode_execve_will_transition, + .mpo_vnode_init_label = mac_grantbylabel_vnode_init_label, +}; + +MAC_POLICY_SET(&mac_grantbylabel_ops, mac_grantbylabel, + MAC_GRANTBYLABEL_FULLNAME, + MPC_LOADTIME_FLAG_NOTLATE, &mac_grantbylabel_slot); +MODULE_VERSION(mac_grantbylabel, 1); +MODULE_DEPEND(mac_grantbylabel, mac_veriexec, MAC_VERIEXEC_VERSION, + MAC_VERIEXEC_VERSION, MAC_VERIEXEC_VERSION); diff --git a/sys/security/mac_grantbylabel/mac_grantbylabel.h b/sys/security/mac_grantbylabel/mac_grantbylabel.h new file mode 100644 index 000000000000..3362805c4063 --- /dev/null +++ b/sys/security/mac_grantbylabel/mac_grantbylabel.h @@ -0,0 +1,63 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2018-2023, Juniper Networks, Inc. + * All rights reserved. + * + * 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 ``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 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 _SECURITY_MAC_GRANTBYLABEL_H +#define _SECURITY_MAC_GRANTBYLABEL_H + +#include + +#define MAC_GRANTBYLABEL_NAME "mac_grantbylabel" + +/* the bits we use to represent tokens */ +#define GBL_EMPTY (1<<0) +#define GBL_BIND (1<<1) +#define GBL_IPC (1<<2) +#define GBL_NET (1<<3) +#define GBL_PROC (1<<4) +#define GBL_RTSOCK (1<<5) +#define GBL_SYSCTL (1<<6) +#define GBL_VACCESS (1<<7) +#define GBL_VERIEXEC (1<<8) +#define GBL_KMEM (1<<9) +#define GBL_MAX 9 + +/* this should suffice for now */ +typedef uint32_t gbl_label_t; + +#define MAC_GRANTBYLABEL_FETCH_GBL 1 +#define MAC_GRANTBYLABEL_FETCH_PID_GBL 2 + +struct mac_grantbylabel_fetch_gbl_args { + union { + int fd; + pid_t pid; + } u; + gbl_label_t gbl; +}; + +#endif