Path: blob/main/sys/security/mac_grantbylabel/mac_grantbylabel.c
39478 views
/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2018-2023, Juniper Networks, Inc.4* All rights reserved.5*6* Redistribution and use in source and binary forms, with or without7* modification, are permitted provided that the following conditions8* are met:9* 1. Redistributions of source code must retain the above copyright10* notice, this list of conditions and the following disclaimer.11* 2. Redistributions in binary form must reproduce the above copyright12* notice, this list of conditions and the following disclaimer in the13* documentation and/or other materials provided with the distribution.14*15* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR16* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES17* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.18* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,19* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,20* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;21* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED22* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,23* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY24* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF25* SUCH DAMAGE.26*/2728#include <sys/cdefs.h>2930#include "opt_mac.h"3132#include <sys/param.h>33#include <sys/capsicum.h>34#include <sys/proc.h>35#include <sys/vnode.h>36#include <sys/kernel.h>37#include <sys/module.h>38#include <sys/mac.h>39#include <sys/namei.h>40#include <sys/priv.h>41#include <sys/imgact.h>42#include <sys/sysctl.h>43#include <sys/syslog.h>44#include <security/mac/mac_policy.h>4546#include "mac_grantbylabel.h"47#include <security/mac_veriexec/mac_veriexec_internal.h>4849#define MAC_GRANTBYLABEL_FULLNAME "MAC/grantbylabel"5051SYSCTL_NODE(_security_mac, OID_AUTO, grantbylabel, CTLFLAG_RW, 0,52"MAC/grantbylabel policy controls");5354#ifdef MAC_DEBUG55static int mac_grantbylabel_debug;5657SYSCTL_INT(_security_mac_grantbylabel, OID_AUTO, debug, CTLFLAG_RW,58&mac_grantbylabel_debug, 0, "Debug mac_grantbylabel");5960#define GRANTBYLABEL_DEBUG(n, x) if (mac_grantbylabel_debug >= (n)) printf x6162#define MAC_GRANTBYLABEL_DBG(_lvl, _fmt, ...) \63do { \64GRANTBYLABEL_DEBUG((_lvl), (MAC_GRANTBYLABEL_FULLNAME ": " \65_fmt "\n", ##__VA_ARGS__)); \66} while(0)67#else68#define MAC_GRANTBYLABEL_DBG(_lvl, _fmt, ...)69#endif707172/* label token prefix */73#define GBL_PREFIX "gbl/"7475static int mac_grantbylabel_slot;7677#define SLOT(l) \78mac_label_get((l), mac_grantbylabel_slot)79#define SLOT_SET(l, v) \80mac_label_set((l), mac_grantbylabel_slot, (v))818283/**84* @brief parse label into bitmask85*86* We are only interested in tokens prefixed by GBL_PREFIX ("gbl/").87*88* @return 32bit mask89*/90static gbl_label_t91gbl_parse_label(const char *label)92{93gbl_label_t gbl;94char *cp;9596if (!(label && *label))97return GBL_EMPTY;98gbl = 0;99for (cp = strstr(label, GBL_PREFIX); cp; cp = strstr(cp, GBL_PREFIX)) {100/* check we didn't find "fugbl/" */101if (cp > label && cp[-1] != ',') {102cp += sizeof(GBL_PREFIX);103continue;104}105cp += sizeof(GBL_PREFIX) - 1;106switch (*cp) {107case 'b':108if (strncmp(cp, "bind", 4) == 0)109gbl |= GBL_BIND;110break;111case 'd':112if (strncmp(cp, "daemon", 6) == 0)113gbl |= (GBL_BIND|GBL_IPC|GBL_NET|GBL_PROC|114GBL_SYSCTL|GBL_VACCESS);115break;116case 'i':117if (strncmp(cp, "ipc", 3) == 0)118gbl |= GBL_IPC;119break;120case 'k':121if (strncmp(cp, "kmem", 4) == 0)122gbl |= GBL_KMEM;123break;124case 'n':125if (strncmp(cp, "net", 3) == 0)126gbl |= GBL_NET;127break;128case 'p':129if (strncmp(cp, "proc", 4) == 0)130gbl |= GBL_PROC;131break;132case 'r':133if (strncmp(cp, "rtsock", 6) == 0)134gbl |= GBL_RTSOCK;135break;136case 's':137if (strncmp(cp, "sysctl", 6) == 0)138gbl |= GBL_SYSCTL;139break;140case 'v':141if (strncmp(cp, "vaccess", 7) == 0)142gbl |= GBL_VACCESS;143else if (strncmp(cp, "veriexec", 8) == 0)144gbl |= GBL_VERIEXEC;145break;146default: /* ignore unknown? */147MAC_GRANTBYLABEL_DBG(1,148"ignoring unknown token at %s/%s",149GBL_PREFIX, cp);150break;151}152}153154return gbl;155}156157158/**159* @brief get the v_label for a vnode160*161* Lookup the label if not already set in v_label162*163* @return 32bit mask or 0 on error164*/165static gbl_label_t166gbl_get_vlabel(struct vnode *vp, struct ucred *cred)167{168struct vattr va;169const char *label;170gbl_label_t gbl;171int error;172173gbl = SLOT(vp->v_label);174if (gbl == 0) {175error = VOP_GETATTR(vp, &va, cred);176if (error == 0) {177label = mac_veriexec_metadata_get_file_label(va.va_fsid,178va.va_fileid, va.va_gen, FALSE);179if (label) {180MAC_GRANTBYLABEL_DBG(1,181"label=%s dev=%ju, file %ju.%lu",182label,183(uintmax_t)va.va_fsid,184(uintmax_t)va.va_fileid,185va.va_gen);186gbl = gbl_parse_label(label);187} else {188gbl = GBL_EMPTY;189MAC_GRANTBYLABEL_DBG(2, "no label dev=%ju, file %ju.%lu",190(uintmax_t)va.va_fsid,191(uintmax_t)va.va_fileid,192va.va_gen);193}194}195}196return gbl;197}198199200/**201* @brief grant priv if warranted202*203* If the cred is root, we have nothing to do.204* Otherwise see if the current process has a label205* that grants it the requested priv.206*/207static int208mac_grantbylabel_priv_grant(struct ucred *cred, int priv)209{210gbl_label_t label;211int rc;212213rc = EPERM; /* default response */214215if ((curproc->p_flag & (P_KPROC|P_SYSTEM)))216return rc; /* not interested */217218switch (priv) {219case PRIV_PROC_MEM_WRITE:220case PRIV_KMEM_READ:221case PRIV_KMEM_WRITE:222break;223case PRIV_VERIEXEC_DIRECT:224case PRIV_VERIEXEC_NOVERIFY:225/* XXX might want to skip in FIPS mode */226break;227default:228if (cred->cr_uid == 0)229return rc; /* not interested */230break;231}232233label = (gbl_label_t)(SLOT(curproc->p_textvp->v_label) |234SLOT(curproc->p_label));235236/*237* We look at the extra privs granted238* via process label.239*/240switch (priv) {241case PRIV_IPC_READ:242case PRIV_IPC_WRITE:243if (label & GBL_IPC)244rc = 0;245break;246case PRIV_PROC_MEM_WRITE:247case PRIV_KMEM_READ:248case PRIV_KMEM_WRITE:249if (label & GBL_KMEM)250rc = 0;251break;252case PRIV_NETINET_BINDANY:253case PRIV_NETINET_RESERVEDPORT: /* socket bind low port */254case PRIV_NETINET_REUSEPORT:255if (label & GBL_BIND)256rc = 0;257break;258case PRIV_NETINET_ADDRCTRL6:259case PRIV_NET_LAGG:260case PRIV_NET_SETIFFIB:261case PRIV_NET_SETIFVNET:262case PRIV_NETINET_SETHDROPTS:263case PRIV_NET_VXLAN:264case PRIV_NETINET_GETCRED:265case PRIV_NETINET_IPSEC:266case PRIV_NETINET_RAW:267if (label & GBL_NET)268rc = 0;269break;270case PRIV_NETINET_MROUTE:271case PRIV_NET_ROUTE:272if (label & GBL_RTSOCK)273rc = 0;274break;275case PRIV_PROC_LIMIT:276case PRIV_PROC_SETRLIMIT:277if (label & GBL_PROC)278rc = 0;279break;280case PRIV_SYSCTL_WRITE:281if (label & GBL_SYSCTL)282rc = 0;283break;284case PRIV_VFS_READ:285case PRIV_VFS_WRITE:286if (label & GBL_KMEM)287rc = 0;288/* FALLTHROUGH */289case PRIV_VFS_ADMIN:290case PRIV_VFS_BLOCKRESERVE:291case PRIV_VFS_CHOWN:292case PRIV_VFS_EXEC: /* vaccess file and accmode & VEXEC */293case PRIV_VFS_GENERATION:294case PRIV_VFS_LOOKUP: /* vaccess DIR */295if (label & GBL_VACCESS)296rc = 0;297break;298case PRIV_VERIEXEC_DIRECT:299/*300* We are here because we are attempting to direct exec301* something with the 'indirect' flag set.302* We need to check parent label for this one.303*/304PROC_LOCK(curproc);305label = (gbl_label_t)SLOT(curproc->p_pptr->p_textvp->v_label);306if (label & GBL_VERIEXEC) {307rc = 0;308/*309* Of course the only reason to be running an310* interpreter this way is to bypass O_VERIFY311* so we can run unsigned script.312* We set GBL_VERIEXEC on p_label for313* PRIV_VERIEXEC_NOVERIFY below314*/315SLOT_SET(curproc->p_label, GBL_VERIEXEC);316}317PROC_UNLOCK(curproc);318break;319case PRIV_VERIEXEC_NOVERIFY:320/* we look at p_label! see above */321label = (gbl_label_t)SLOT(curproc->p_label);322if (label & GBL_VERIEXEC)323rc = 0;324break;325default:326break;327}328MAC_GRANTBYLABEL_DBG(rc ? 1 : 2,329"pid=%d priv=%d, label=%#o rc=%d",330curproc->p_pid, priv, label, rc);331332return rc;333}334335336/*337* If proc->p_textvp does not yet have a label,338* fetch file info from mac_veriexec339* and set label (if any) else set.340* If there is no label set it to GBL_EMPTY.341*/342static int343mac_grantbylabel_proc_check_resource(struct ucred *cred,344struct proc *proc)345{346gbl_label_t gbl;347348if (!SLOT(proc->p_textvp->v_label)) {349gbl = gbl_get_vlabel(proc->p_textvp, cred);350if (gbl == 0)351gbl = GBL_EMPTY;352SLOT_SET(proc->p_textvp->v_label, gbl);353}354return 0;355}356357static int358mac_grantbylabel_syscall(struct thread *td, int call, void *arg)359{360cap_rights_t rights;361struct mac_grantbylabel_fetch_gbl_args gbl_args;362struct file *fp;363struct proc *proc;364int error;365int proc_locked;366367switch (call) {368case MAC_GRANTBYLABEL_FETCH_GBL:369case MAC_GRANTBYLABEL_FETCH_PID_GBL:370error = copyin(arg, &gbl_args, sizeof(gbl_args));371if (error)372return error;373gbl_args.gbl = 0;374break;375default:376return EOPNOTSUPP;377break;378}379proc_locked = 0;380switch (call) {381case MAC_GRANTBYLABEL_FETCH_GBL:382error = getvnode(td, gbl_args.u.fd,383cap_rights_init(&rights), &fp);384if (error)385return (error);386387if (fp->f_type != DTYPE_VNODE) {388error = EINVAL;389goto cleanup_file;390}391392vn_lock(fp->f_vnode, LK_SHARED | LK_RETRY);393gbl_args.gbl = gbl_get_vlabel(fp->f_vnode, td->td_ucred);394if (gbl_args.gbl == 0)395error = EOPNOTSUPP;396else397error = 0;398VOP_UNLOCK(fp->f_vnode);399cleanup_file:400fdrop(fp, td);401break;402case MAC_GRANTBYLABEL_FETCH_PID_GBL:403error = 0;404if (gbl_args.u.pid == 0405|| gbl_args.u.pid == curproc->p_pid) {406proc = curproc;407} else {408proc = pfind(gbl_args.u.pid);409if (proc == NULL)410return (EINVAL);411else if (proc->p_textvp == NULL) {412PROC_UNLOCK(proc);413return (EINVAL);414}415proc_locked = 1;416}417gbl_args.gbl = (SLOT(proc->p_textvp->v_label) |418SLOT(proc->p_label));419if (proc_locked)420PROC_UNLOCK(proc);421break;422}423if (error == 0) {424error = copyout(&gbl_args, arg, sizeof(gbl_args));425}426return error;427}428429430static void431mac_grantbylabel_proc_init_label(struct label *label)432{433434SLOT_SET(label, 0); /* not yet set! */435}436437static void438mac_grantbylabel_vnode_init_label(struct label *label)439{440441SLOT_SET(label, 0); /* not yet set! */442}443444/**445* @brief set v_label if needed446*/447static int448mac_grantbylabel_vnode_check_exec(struct ucred *cred __unused,449struct vnode *vp __unused, struct label *label __unused,450struct image_params *imgp, struct label *execlabel __unused)451{452gbl_label_t gbl;453454gbl = SLOT(vp->v_label);455if (gbl == 0) {456gbl = gbl_get_vlabel(vp, cred);457if (gbl == 0)458gbl = GBL_EMPTY;459MAC_GRANTBYLABEL_DBG(1, "vnode_check_exec label=%#o", gbl);460SLOT_SET(vp->v_label, gbl);461}462return 0;463}464465static void466mac_grantbylabel_copy_label(struct label *src, struct label *dest)467{468SLOT_SET(dest, SLOT(src));469}470471/**472* @brief if interpreting copy script v_label to proc p_label473*/474static int475mac_grantbylabel_vnode_execve_will_transition(struct ucred *old,476struct vnode *vp, struct label *vplabel,477struct label *interpvplabel, struct image_params *imgp,478struct label *execlabel)479{480gbl_label_t gbl;481482if (imgp->interpreted) {483gbl = SLOT(interpvplabel);484if (gbl) {485SLOT_SET(imgp->proc->p_label, gbl);486}487MAC_GRANTBYLABEL_DBG(1, "execve_will_transition label=%#o", gbl);488}489return 0;490}491492493static struct mac_policy_ops mac_grantbylabel_ops =494{495.mpo_proc_check_resource = mac_grantbylabel_proc_check_resource,496.mpo_priv_grant = mac_grantbylabel_priv_grant,497.mpo_syscall = mac_grantbylabel_syscall,498.mpo_proc_init_label = mac_grantbylabel_proc_init_label,499.mpo_vnode_check_exec = mac_grantbylabel_vnode_check_exec,500.mpo_vnode_copy_label = mac_grantbylabel_copy_label,501.mpo_vnode_execve_will_transition = mac_grantbylabel_vnode_execve_will_transition,502.mpo_vnode_init_label = mac_grantbylabel_vnode_init_label,503};504505MAC_POLICY_SET(&mac_grantbylabel_ops, mac_grantbylabel,506MAC_GRANTBYLABEL_FULLNAME,507MPC_LOADTIME_FLAG_NOTLATE, &mac_grantbylabel_slot);508MODULE_VERSION(mac_grantbylabel, 1);509MODULE_DEPEND(mac_grantbylabel, mac_veriexec, MAC_VERIEXEC_VERSION,510MAC_VERIEXEC_VERSION, MAC_VERIEXEC_VERSION);511512513