| /* |
| * Copyright 2015-2021 Rivoreo |
| * |
| * 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 <sys/types.h> |
| #include <sys/param.h> |
| #include <sys/module.h> |
| #include <sys/kernel.h> |
| #include <security/mac/mac_policy.h> |
| #include <sys/systm.h> |
| #include <sys/sbuf.h> |
| #include <sys/sysctl.h> |
| #include <sys/ucred.h> |
| #include <sys/priv.h> |
| |
| #define PRIV_SET_MASK_T unsigned long int |
| #define PRIV_BITS_PER_MASK (sizeof(PRIV_SET_MASK_T)*8) |
| |
| struct privilege_set { |
| PRIV_SET_MASK_T bits[howmany(_PRIV_HIGHEST, PRIV_BITS_PER_MASK)]; |
| }; |
| |
| #define PRIV_SET_GET_MASK(I) ((PRIV_SET_MASK_T)1 << ((I)%PRIV_BITS_PER_MASK)) |
| #define PRIV_ISSET(I,P) ((P)->bits[(I)/PRIV_BITS_PER_MASK] & PRIV_SET_GET_MASK(I)) |
| #define PRIV_CLEAR(I,P) ((P)->bits[(I)/PRIV_BITS_PER_MASK] &= ~PRIV_SET_GET_MASK(I)) |
| #define PRIV_SET(I,P) ((P)->bits[(I)/PRIV_BITS_PER_MASK] |= PRIV_SET_GET_MASK(I)) |
| |
| static int priv_slot; |
| #define GET_PRIV_SET(L) ((struct privilege_set *)mac_label_get((L),priv_slot)) |
| |
| static MALLOC_DEFINE(M_MAC_PRIV, "mac_priv_pool", "MAC/Privilege privilege set pool"); |
| |
| SYSCTL_DECL(_security_mac); |
| |
| static SYSCTL_NODE(_security_mac, OID_AUTO, priv, CTLFLAG_RW, NULL, |
| "mac_priv policy controls"); |
| |
| static int priv_enabled = -1; |
| SYSCTL_INT(_security_mac_priv, OID_AUTO, enabled, CTLFLAG_RW, |
| &priv_enabled, 0, "Enable mac_priv policy"); |
| TUNABLE_INT("security.mac.priv.enabled", &priv_enabled); |
| |
| static char init_priv[256]; |
| SYSCTL_STRING(_security_mac_priv, OID_AUTO, init_priv, CTLFLAG_RDTUN, |
| init_priv, 0, "Default privilege set for init process"); |
| |
| static const char *privilege_names[_PRIV_HIGHEST] = { |
| #define PRIVILEGE(N) [PRIV_##N] = #N |
| PRIVILEGE(ACCT), |
| PRIVILEGE(MAXFILES), |
| PRIVILEGE(MAXPROC), |
| PRIVILEGE(KTRACE), |
| PRIVILEGE(SETDUMPER), |
| PRIVILEGE(REBOOT), |
| PRIVILEGE(SWAPON), |
| PRIVILEGE(SWAPOFF), |
| PRIVILEGE(MSGBUF), |
| PRIVILEGE(IO), |
| PRIVILEGE(KEYBOARD), |
| PRIVILEGE(DRIVER), |
| PRIVILEGE(ADJTIME), |
| PRIVILEGE(NTP_ADJTIME), |
| PRIVILEGE(CLOCK_SETTIME), |
| PRIVILEGE(SETTIMEOFDAY), |
| PRIVILEGE(AUDIT_CONTROL), |
| PRIVILEGE(AUDIT_FAILSTOP), |
| PRIVILEGE(AUDIT_GETAUDIT), |
| PRIVILEGE(AUDIT_SETAUDIT), |
| PRIVILEGE(AUDIT_SUBMIT), |
| PRIVILEGE(CRED_SETUID), |
| PRIVILEGE(CRED_SETEUID), |
| PRIVILEGE(CRED_SETGID), |
| PRIVILEGE(CRED_SETEGID), |
| PRIVILEGE(CRED_SETGROUPS), |
| PRIVILEGE(CRED_SETREUID), |
| PRIVILEGE(CRED_SETREGID), |
| PRIVILEGE(CRED_SETRESUID), |
| PRIVILEGE(CRED_SETRESGID), |
| PRIVILEGE(SEEOTHERGIDS), |
| PRIVILEGE(SEEOTHERUIDS), |
| PRIVILEGE(DEBUG_DIFFCRED), |
| PRIVILEGE(DEBUG_SUGID), |
| PRIVILEGE(DEBUG_UNPRIV), |
| PRIVILEGE(DEBUG_DENIED), |
| PRIVILEGE(DTRACE_KERNEL), |
| PRIVILEGE(DTRACE_PROC), |
| PRIVILEGE(DTRACE_USER), |
| PRIVILEGE(FIRMWARE_LOAD), |
| PRIVILEGE(JAIL_ATTACH), |
| PRIVILEGE(JAIL_SET), |
| PRIVILEGE(JAIL_REMOVE), |
| PRIVILEGE(KENV_SET), |
| PRIVILEGE(KENV_UNSET), |
| PRIVILEGE(KLD_LOAD), |
| PRIVILEGE(KLD_UNLOAD), |
| PRIVILEGE(MAC_PARTITION), |
| PRIVILEGE(MAC_PRIVS), |
| PRIVILEGE(PROC_LIMIT), |
| PRIVILEGE(PROC_SETLOGIN), |
| PRIVILEGE(PROC_SETRLIMIT), |
| PRIVILEGE(PROC_SETLOGINCLASS), |
| PRIVILEGE(IPC_READ), |
| PRIVILEGE(IPC_WRITE), |
| PRIVILEGE(IPC_ADMIN), |
| PRIVILEGE(IPC_MSGSIZE), |
| PRIVILEGE(MQ_ADMIN), |
| PRIVILEGE(PMC_MANAGE), |
| PRIVILEGE(PMC_SYSTEM), |
| PRIVILEGE(SCHED_DIFFCRED), |
| PRIVILEGE(SCHED_SETPRIORITY), |
| PRIVILEGE(SCHED_RTPRIO), |
| PRIVILEGE(SCHED_SETPOLICY), |
| PRIVILEGE(SCHED_SET), |
| PRIVILEGE(SCHED_SETPARAM), |
| PRIVILEGE(SCHED_CPUSET), |
| PRIVILEGE(SCHED_CPUSET_INTR), |
| PRIVILEGE(SEM_WRITE), |
| PRIVILEGE(SIGNAL_DIFFCRED), |
| PRIVILEGE(SIGNAL_SUGID), |
| PRIVILEGE(SYSCTL_DEBUG), |
| PRIVILEGE(SYSCTL_WRITE), |
| PRIVILEGE(SYSCTL_WRITEJAIL), |
| PRIVILEGE(TTY_CONSOLE), |
| PRIVILEGE(TTY_DRAINWAIT), |
| PRIVILEGE(TTY_DTRWAIT), |
| PRIVILEGE(TTY_EXCLUSIVE), |
| PRIVILEGE(TTY_STI), |
| PRIVILEGE(TTY_SETA), |
| PRIVILEGE(UFS_EXTATTRCTL), |
| PRIVILEGE(UFS_QUOTAOFF), |
| PRIVILEGE(UFS_QUOTAON), |
| PRIVILEGE(UFS_SETUSE), |
| PRIVILEGE(ZFS_POOL_CONFIG), |
| PRIVILEGE(ZFS_INJECT), |
| PRIVILEGE(ZFS_JAIL), |
| PRIVILEGE(NFS_DAEMON), |
| PRIVILEGE(NFS_LOCKD), |
| PRIVILEGE(VFS_READ), |
| PRIVILEGE(VFS_WRITE), |
| PRIVILEGE(VFS_ADMIN), |
| PRIVILEGE(VFS_EXEC), |
| PRIVILEGE(VFS_LOOKUP), |
| PRIVILEGE(VFS_BLOCKRESERVE), |
| PRIVILEGE(VFS_CHFLAGS_DEV), |
| PRIVILEGE(VFS_CHOWN), |
| PRIVILEGE(VFS_CHROOT), |
| PRIVILEGE(VFS_RETAINSUGID), |
| PRIVILEGE(VFS_EXCEEDQUOTA), |
| PRIVILEGE(VFS_EXTATTR_SYSTEM), |
| PRIVILEGE(VFS_FCHROOT), |
| PRIVILEGE(VFS_FHOPEN), |
| PRIVILEGE(VFS_FHSTAT), |
| PRIVILEGE(VFS_FHSTATFS), |
| PRIVILEGE(VFS_GENERATION), |
| PRIVILEGE(VFS_GETFH), |
| PRIVILEGE(VFS_GETQUOTA), |
| PRIVILEGE(VFS_LINK), |
| PRIVILEGE(VFS_MKNOD_BAD), |
| PRIVILEGE(VFS_MKNOD_DEV), |
| PRIVILEGE(VFS_MKNOD_WHT), |
| PRIVILEGE(VFS_MOUNT), |
| PRIVILEGE(VFS_MOUNT_OWNER), |
| PRIVILEGE(VFS_MOUNT_EXPORTED), |
| PRIVILEGE(VFS_MOUNT_PERM), |
| PRIVILEGE(VFS_MOUNT_SUIDDIR), |
| PRIVILEGE(VFS_MOUNT_NONUSER), |
| PRIVILEGE(VFS_SETGID), |
| PRIVILEGE(VFS_SETQUOTA), |
| PRIVILEGE(VFS_STICKYFILE), |
| PRIVILEGE(VFS_SYSFLAGS), |
| PRIVILEGE(VFS_UNMOUNT), |
| PRIVILEGE(VFS_STAT), |
| PRIVILEGE(VM_MADV_PROTECT), |
| PRIVILEGE(VM_MLOCK), |
| PRIVILEGE(VM_MUNLOCK), |
| PRIVILEGE(VM_SWAP_NOQUOTA), |
| PRIVILEGE(VM_SWAP_NORLIMIT), |
| PRIVILEGE(DEVFS_RULE), |
| PRIVILEGE(DEVFS_SYMLINK), |
| PRIVILEGE(RANDOM_RESEED), |
| PRIVILEGE(NET_BRIDGE), |
| PRIVILEGE(NET_GRE), |
| #ifdef PRIV_NET_PPP |
| PRIVILEGE(NET_PPP), |
| #endif |
| PRIVILEGE(NET_BPF), |
| PRIVILEGE(NET_RAW), |
| PRIVILEGE(NET_ROUTE), |
| PRIVILEGE(NET_TAP), |
| PRIVILEGE(NET_SETIFMTU), |
| PRIVILEGE(NET_SETIFFLAGS), |
| PRIVILEGE(NET_SETIFCAP), |
| PRIVILEGE(NET_SETIFNAME), |
| PRIVILEGE(NET_SETIFMETRIC), |
| PRIVILEGE(NET_SETIFPHYS), |
| PRIVILEGE(NET_SETIFMAC), |
| PRIVILEGE(NET_ADDMULTI), |
| PRIVILEGE(NET_DELMULTI), |
| PRIVILEGE(NET_HWIOCTL), |
| PRIVILEGE(NET_SETLLADDR), |
| PRIVILEGE(NET_ADDIFGROUP), |
| PRIVILEGE(NET_DELIFGROUP), |
| PRIVILEGE(NET_IFCREATE), |
| PRIVILEGE(NET_IFDESTROY), |
| PRIVILEGE(NET_ADDIFADDR), |
| PRIVILEGE(NET_DELIFADDR), |
| PRIVILEGE(NET_LAGG), |
| PRIVILEGE(NET_GIF), |
| PRIVILEGE(NET_SETIFVNET), |
| PRIVILEGE(NET_SETIFDESCR), |
| PRIVILEGE(NET_SETIFFIB), |
| PRIVILEGE(NET_VXLAN), |
| #ifdef PRIV_NET_SETLANPCP |
| PRIVILEGE(NET_SETLANPCP), |
| #endif |
| #ifdef PRIV_NET_SETVLANPCP |
| PRIVILEGE(NET_SETVLANPCP), |
| #endif |
| #ifdef PRIV_NETATALK_RESERVEDPORT |
| PRIVILEGE(NETATALK_RESERVEDPORT), |
| #endif |
| PRIVILEGE(NETATM_CFG), |
| PRIVILEGE(NETATM_ADD), |
| PRIVILEGE(NETATM_DEL), |
| PRIVILEGE(NETATM_SET), |
| PRIVILEGE(NETBLUETOOTH_RAW), |
| PRIVILEGE(NETGRAPH_CONTROL), |
| PRIVILEGE(NETGRAPH_TTY), |
| PRIVILEGE(NETINET_RESERVEDPORT), |
| PRIVILEGE(NETINET_IPFW), |
| PRIVILEGE(NETINET_DIVERT), |
| PRIVILEGE(NETINET_PF), |
| PRIVILEGE(NETINET_DUMMYNET), |
| PRIVILEGE(NETINET_CARP), |
| PRIVILEGE(NETINET_MROUTE), |
| PRIVILEGE(NETINET_RAW), |
| PRIVILEGE(NETINET_GETCRED), |
| PRIVILEGE(NETINET_IPSEC), |
| PRIVILEGE(NETINET_REUSEPORT), |
| PRIVILEGE(NETINET_SETHDROPTS), |
| PRIVILEGE(NETINET_BINDANY), |
| #ifdef PRIV_NETINET_HASHKEY |
| PRIVILEGE(NETINET_HASHKEY), |
| #endif |
| #ifdef PRIV_NETIPX_RESERVEDPORT |
| PRIVILEGE(NETIPX_RESERVEDPORT), |
| #endif |
| #ifdef PRIV_NETIPX_RAW |
| PRIVILEGE(NETIPX_RAW), |
| #endif |
| PRIVILEGE(NETNCP), |
| PRIVILEGE(NETSMB), |
| PRIVILEGE(DDB_CAPTURE), |
| PRIVILEGE(NNPFS_DEBUG), |
| PRIVILEGE(CPUCTL_WRMSR), |
| PRIVILEGE(CPUCTL_UPDATE), |
| PRIVILEGE(AFS_ADMIN), |
| PRIVILEGE(AFS_DAEMON), |
| PRIVILEGE(RCTL_GET_RACCT), |
| PRIVILEGE(RCTL_GET_RULES), |
| PRIVILEGE(RCTL_GET_LIMITS), |
| PRIVILEGE(RCTL_ADD_RULE), |
| PRIVILEGE(RCTL_REMOVE_RULE), |
| PRIVILEGE(KMEM_READ), |
| PRIVILEGE(KMEM_WRITE), |
| #undef PRIVILEGE |
| }; |
| |
| static void priv_destroy(struct mac_policy_conf *conf) { |
| } |
| |
| static void priv_init(struct mac_policy_conf *conf) { |
| if(priv_enabled < 0) priv_enabled = 1; |
| } |
| |
| static void priv_init_label(struct label *label) { |
| //uprintf("function: priv_init_label(%p)\n", label); |
| mac_label_set(label, priv_slot, |
| (intptr_t)malloc(sizeof(struct privilege_set), M_MAC_PRIV, M_WAITOK | M_ZERO)); |
| } |
| |
| static void priv_destroy_label(struct label *label) { |
| //uprintf("function: priv_destroy_label(%p)\n", label); |
| free(GET_PRIV_SET(label), M_MAC_PRIV); |
| mac_label_set(label, priv_slot, 0); |
| } |
| |
| static void priv_cred_create_swapper(struct ucred *cred) { |
| struct privilege_set *priv_set = GET_PRIV_SET(cred->cr_label); |
| memset(priv_set, 0xff, sizeof *priv_set); |
| } |
| |
| static int get_privilege_by_name(const char *name) { |
| unsigned int i = _PRIV_LOWEST; |
| do { |
| const char *s = privilege_names[i]; |
| if(s && strcasecmp(s, name) == 0) return i; |
| } while(++i < _PRIV_HIGHEST); |
| return -1; |
| } |
| |
| static int priv_internalize_label_internal(struct label *label, char *element_data) { |
| struct privilege_set *priv_set = GET_PRIV_SET(label); |
| memset(priv_set, 0, sizeof(struct privilege_set)); |
| char *p = element_data, *end_p; |
| while(*p) { |
| int n = strtol(p, &end_p, 0); |
| if(*end_p && *end_p != '/') { |
| end_p = strchr(p, '/'); |
| if(end_p) *end_p = 0; |
| n = get_privilege_by_name(p); |
| } else if(!*end_p) { |
| end_p = NULL; |
| } else if(end_p == p) { |
| return EINVAL; |
| } |
| if(n < 0) return EINVAL; |
| PRIV_SET(n, priv_set); |
| if(!end_p) break; |
| p = end_p + 1; |
| } |
| return 0; |
| } |
| |
| static void priv_cred_create_init(struct ucred *cred) { |
| struct privilege_set *priv_set = GET_PRIV_SET(cred->cr_label); |
| |
| char *priv = malloc(4096, M_TEMP, M_WAITOK); |
| if(TUNABLE_STR_FETCH("security.mac.priv.init_priv", priv, 4096) && strcasecmp(priv, "ALL")) { |
| int e = priv_internalize_label_internal(cred->cr_label, priv); |
| if(e) { |
| printf("mac_priv: Failed to internalize privilege set '%s' for init process, error %d\n", |
| priv, e); |
| } else { |
| strlcpy(init_priv, priv, sizeof init_priv); |
| free(priv, M_TEMP); |
| if(bootverbose) { |
| printf("mac_priv: Set init process privilege set for from tunable\n"); |
| } |
| return; |
| } |
| } else { |
| strcpy(init_priv, "ALL"); |
| } |
| free(priv, M_TEMP); |
| |
| unsigned int i = _PRIV_LOWEST; |
| do { |
| const char *s = privilege_names[i]; |
| if(s) PRIV_SET(i, priv_set); |
| else PRIV_CLEAR(i, priv_set); |
| } while(++i < _PRIV_HIGHEST); |
| } |
| |
| static int priv_externalize_label(struct label *label, char *element_name, struct sbuf *sb, int *claimed) { |
| uprintf("function: priv_externalize_label(%p, %p<%s>, %p, %p<%d>\n", |
| label, element_name, element_name, sb, claimed, claimed); |
| if(!label) return 0; |
| if(strcmp(element_name, "priv")) return 0; |
| |
| (*claimed)++; |
| struct privilege_set *priv_set = GET_PRIV_SET(label); |
| unsigned int i = _PRIV_LOWEST, more = 0; |
| do { |
| if(!PRIV_ISSET(i, priv_set)) continue; |
| if(more) sbuf_putc(sb, '/'); |
| const char *s = privilege_names[i]; |
| if(s) sbuf_cat(sb, s); else sbuf_printf(sb, "%d", i); |
| more = 1; |
| } while(++i < _PRIV_HIGHEST); |
| return 0; |
| } |
| |
| static int priv_internalize_label(struct label *label, char *element_name, char *element_data, int *claimed) { |
| uprintf("function: priv_internalize_label(%p, %p<%s>, %p<%s>, %p<%d>)\n", |
| label, element_name, element_name, element_data, element_data, claimed, claimed); |
| if(!label) return 0; |
| if(strcmp(element_name, "priv")) return 0; |
| |
| (*claimed)++; |
| return priv_internalize_label_internal(label, element_data); |
| } |
| |
| static void priv_copy_label(struct label *src, struct label *dest) { |
| if(!src) return; |
| struct privilege_set *src_set = GET_PRIV_SET(src); |
| struct privilege_set *dest_set = GET_PRIV_SET(dest); |
| *dest_set = *src_set; |
| } |
| |
| static int has_more_privilege(const struct ucred *cred, struct label *new_label) { |
| struct privilege_set *old_set = cred->cr_label ? GET_PRIV_SET(cred->cr_label) : NULL; |
| struct privilege_set *new_set = GET_PRIV_SET(new_label); |
| int i = howmany(_PRIV_HIGHEST, PRIV_BITS_PER_MASK); |
| if(old_set) do { |
| if(~old_set->bits[i] & new_set->bits[i]) return 1; |
| } while(--i > 0); else do { |
| if(new_set->bits[i]) return 1; |
| } while(--i > 0); |
| return 0; |
| } |
| |
| static int priv_cred_check_relabel(struct ucred *cred, struct label *new_label) { |
| if(!has_more_privilege(cred, new_label)) return 0; |
| return priv_check_cred(cred, PRIV_MAC_PRIVS, 0); |
| } |
| |
| static void priv_cred_relabel(struct ucred *cred, struct label *new_label) { |
| uprintf("function: priv_cred_relabel(%p, %p)\n", cred, new_label); |
| struct privilege_set *old_set = GET_PRIV_SET(cred->cr_label); |
| struct privilege_set *new_set = GET_PRIV_SET(new_label); |
| *old_set = *new_set; |
| } |
| |
| static int priv_priv_check(struct ucred *cred, int priv) { |
| struct privilege_set *priv_set; |
| if(priv_enabled <= 0) return 0; |
| if(!cred->cr_label || !(priv_set = GET_PRIV_SET(cred->cr_label))) { |
| return priv_enabled < 2 ? 0 : EPERM; |
| } |
| return PRIV_ISSET(priv, priv_set) ? 0 : EPERM; |
| } |
| |
| static int priv_priv_grant(struct ucred *cred, int priv) { |
| if(priv_enabled <= 0) return EPERM; |
| if(!cred->cr_label) return EPERM; |
| struct privilege_set *priv_set = GET_PRIV_SET(cred->cr_label); |
| return priv_set && PRIV_ISSET(priv, priv_set) ? 0 : EPERM; |
| } |
| |
| /* |
| * Register functions with MAC Framework policy entry points. |
| */ |
| static struct mac_policy_ops priv_ops = { |
| .mpo_destroy = priv_destroy, |
| .mpo_init = priv_init, |
| .mpo_cred_init_label = priv_init_label, |
| .mpo_cred_destroy_label = priv_destroy_label, |
| .mpo_cred_create_swapper = priv_cred_create_swapper, |
| .mpo_cred_create_init = priv_cred_create_init, |
| .mpo_cred_externalize_label = priv_externalize_label, |
| .mpo_cred_internalize_label = priv_internalize_label, |
| .mpo_cred_copy_label = priv_copy_label, |
| .mpo_cred_check_relabel = priv_cred_check_relabel, |
| .mpo_cred_relabel = priv_cred_relabel, |
| .mpo_priv_check = priv_priv_check, |
| .mpo_priv_grant = priv_priv_grant, |
| }; |
| |
| MAC_POLICY_SET(&priv_ops, mac_priv, "MAC/Privilege", MPC_LOADTIME_FLAG_UNLOADOK, &priv_slot); |