Add mac_grantbylabel

This module allows controlled privilege escallation via mac labels
securely associated with a process via mac_veriexec.

There are over 700 PRIV_* but we can compress many of them into
a single GBL_* thus constraining the size of gbl labels.

The goal is to allow a daemon to run as an unprivileged process while
still being able a set of privileged operations needed.

We add APIs to libveriexec so that userland processes can check labels
and an exec_script API that allows a suitably labeled process to run
something like a python interpreter directly if necessary;
overcomming the 'indirect' flag applied to the interpreter.

Add -l option to sbin/veriexec to report labels.

Reviewed by:	stevek
Sponsored by:	Juniper Networks, Inc.
Differential Revision:	https://reviews.freebsd.org/D41431
This commit is contained in:
Simon J. Gerraty 2023-08-24 17:41:22 -07:00
parent 52c1066f52
commit 1554ba03b6
14 changed files with 1090 additions and 39 deletions

View File

@ -333,6 +333,8 @@
..
mac_bsdextended
..
mac_grantbylabel
..
mac_lomac
..
mac_mls

View File

@ -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 \

View File

@ -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 <bsd.lib.mk>

View File

@ -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 <sys/types.h>
#include <sys/param.h>
#include <sys/errno.h>
#include <sys/mac.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <syslog.h>
#include <security/mac_grantbylabel/mac_grantbylabel.h>
#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 <sys/wait.h>
#include <err.h>
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

125
lib/libveriexec/gbl_check.c Normal file
View File

@ -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 <sys/types.h>
#include <sys/errno.h>
#include <sys/mac.h>
#include <unistd.h>
#include <fcntl.h>
#include <security/mac_grantbylabel/mac_grantbylabel.h>
/**
* @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 <stdlib.h>
#include <stdio.h>
#include <err.h>
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

View File

@ -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__ */

View File

@ -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, &params) == 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, &params) == 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 [,]<want>[,] ? */
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, &params) == 0) {
/* Does label contain [,]<want>[,] ? */
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, &params) == 0) {
/* Does label contain [,]<want>[,] ? */
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, &params);
} 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], &params);
}
if (error) {

View File

@ -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 <dirdeps.mk>

View File

@ -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

View File

@ -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 */

View File

@ -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

View File

@ -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

View File

@ -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 <sys/cdefs.h>
#include "opt_mac.h"
#include <sys/param.h>
#include <sys/capsicum.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/mac.h>
#include <sys/namei.h>
#include <sys/priv.h>
#include <sys/imgact.h>
#include <sys/sysctl.h>
#include <sys/syslog.h>
#include <security/mac/mac_policy.h>
#include "mac_grantbylabel.h"
#include <security/mac_veriexec/mac_veriexec_internal.h>
#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);

View File

@ -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 <security/mac_veriexec/mac_veriexec.h>
#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