#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_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
#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))
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)) {
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:
MAC_GRANTBYLABEL_DBG(1,
"ignoring unknown token at %s/%s",
GBL_PREFIX, cp);
break;
}
}
return gbl;
}
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;
}
static int
mac_grantbylabel_priv_grant(struct ucred *cred, int priv)
{
gbl_label_t label;
int rc;
rc = EPERM;
if ((curproc->p_flag & (P_KPROC|P_SYSTEM)))
return rc;
switch (priv) {
case PRIV_PROC_MEM_WRITE:
case PRIV_KMEM_READ:
case PRIV_KMEM_WRITE:
break;
case PRIV_VERIEXEC_DIRECT:
case PRIV_VERIEXEC_NOVERIFY:
break;
default:
if (cred->cr_uid == 0)
return rc;
break;
}
label = (gbl_label_t)(SLOT(curproc->p_textvp->v_label) |
SLOT(curproc->p_label));
switch (priv) {
case PRIV_IPC_READ:
case PRIV_IPC_WRITE:
if (label & GBL_IPC)
rc = 0;
break;
case PRIV_PROC_MEM_WRITE:
case PRIV_KMEM_READ:
case PRIV_KMEM_WRITE:
if (label & GBL_KMEM)
rc = 0;
break;
case PRIV_NETINET_BINDANY:
case PRIV_NETINET_RESERVEDPORT:
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;
case PRIV_VFS_ADMIN:
case PRIV_VFS_BLOCKRESERVE:
case PRIV_VFS_CHOWN:
case PRIV_VFS_EXEC:
case PRIV_VFS_GENERATION:
case PRIV_VFS_LOOKUP:
if (label & GBL_VACCESS)
rc = 0;
break;
case PRIV_VERIEXEC_DIRECT:
PROC_LOCK(curproc);
label = (gbl_label_t)SLOT(curproc->p_pptr->p_textvp->v_label);
if (label & GBL_VERIEXEC) {
rc = 0;
SLOT_SET(curproc->p_label, GBL_VERIEXEC);
}
PROC_UNLOCK(curproc);
break;
case PRIV_VERIEXEC_NOVERIFY:
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;
}
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);
else if (proc->p_textvp == NULL) {
PROC_UNLOCK(proc);
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);
}
static void
mac_grantbylabel_vnode_init_label(struct label *label)
{
SLOT_SET(label, 0);
}
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));
}
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);