1/*- 2 * Copyright (c) 1999-2002, 2007-2008 Robert N. M. Watson 3 * Copyright (c) 2001-2005 Networks Associates Technology, Inc. 4 * Copyright (c) 2005 Tom Rhodes 5 * Copyright (c) 2006 SPARTA, Inc. 6 * All rights reserved. 7 * 8 * This software was developed by Robert Watson for the TrustedBSD Project. 9 * It was later enhanced by Tom Rhodes for the TrustedBSD Project. 10 * 11 * This software was developed for the FreeBSD Project in part by Network 12 * Associates Laboratories, the Security Research Division of Network 13 * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), 14 * as part of the DARPA CHATS research program. 15 * 16 * This software was enhanced by SPARTA ISSO under SPAWAR contract 17 * N66001-04-C-6019 ("SEFOS"). 18 * 19 * Redistribution and use in source and binary forms, with or without 20 * modification, are permitted provided that the following conditions 21 * are met: 22 * 1. Redistributions of source code must retain the above copyright 23 * notice, this list of conditions and the following disclaimer. 24 * 2. Redistributions in binary form must reproduce the above copyright 25 * notice, this list of conditions and the following disclaimer in the 26 * documentation and/or other materials provided with the distribution. 27 * 28 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 29 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 30 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 31 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 32 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 33 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 34 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 35 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 36 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 37 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 38 * SUCH DAMAGE. 39 * 40 * $FreeBSD$ 41 */ 42 43/* 44 * Developed by the TrustedBSD Project. 45 * 46 * "BSD Extended" MAC policy, allowing the administrator to impose mandatory 47 * firewall-like rules regarding users and file system objects. 48 */ 49 50#include <sys/param.h> 51#include <sys/acl.h> 52#include <sys/kernel.h> 53#include <sys/jail.h> 54#include <sys/lock.h> 55#include <sys/malloc.h> 56#include <sys/module.h> 57#include <sys/mount.h> 58#include <sys/mutex.h> 59#include <sys/priv.h> 60#include <sys/proc.h> 61#include <sys/systm.h> 62#include <sys/vnode.h> 63#include <sys/sysctl.h> 64#include <sys/syslog.h> 65#include <sys/stat.h> 66 67#include <security/mac/mac_policy.h> 68#include <security/mac_bsdextended/mac_bsdextended.h> 69#include <security/mac_bsdextended/ugidfw_internal.h> 70 71static struct mtx ugidfw_mtx; 72 73SYSCTL_DECL(_security_mac); 74 75static SYSCTL_NODE(_security_mac, OID_AUTO, bsdextended, CTLFLAG_RW, 0, 76 "TrustedBSD extended BSD MAC policy controls"); 77 78static int ugidfw_enabled = 1; 79SYSCTL_INT(_security_mac_bsdextended, OID_AUTO, enabled, CTLFLAG_RWTUN, 80 &ugidfw_enabled, 0, "Enforce extended BSD policy"); 81 82static MALLOC_DEFINE(M_MACBSDEXTENDED, "mac_bsdextended", 83 "BSD Extended MAC rule"); 84 85#define MAC_BSDEXTENDED_MAXRULES 250 86static struct mac_bsdextended_rule *rules[MAC_BSDEXTENDED_MAXRULES]; 87static int rule_count = 0; 88static int rule_slots = 0; 89static int rule_version = MB_VERSION; 90 91SYSCTL_INT(_security_mac_bsdextended, OID_AUTO, rule_count, CTLFLAG_RD, 92 &rule_count, 0, "Number of defined rules\n"); 93SYSCTL_INT(_security_mac_bsdextended, OID_AUTO, rule_slots, CTLFLAG_RD, 94 &rule_slots, 0, "Number of used rule slots\n"); 95SYSCTL_INT(_security_mac_bsdextended, OID_AUTO, rule_version, CTLFLAG_RD, 96 &rule_version, 0, "Version number for API\n"); 97 98/* 99 * This is just used for logging purposes, eventually we would like to log 100 * much more then failed requests. 101 */ 102static int ugidfw_logging; 103SYSCTL_INT(_security_mac_bsdextended, OID_AUTO, logging, CTLFLAG_RW, 104 &ugidfw_logging, 0, "Log failed authorization requests"); 105 106/* 107 * This tunable is here for compatibility. It will allow the user to switch 108 * between the new mode (first rule matches) and the old functionality (all 109 * rules match). 110 */ 111static int ugidfw_firstmatch_enabled; 112SYSCTL_INT(_security_mac_bsdextended, OID_AUTO, firstmatch_enabled, 113 CTLFLAG_RW, &ugidfw_firstmatch_enabled, 1, 114 "Disable/enable match first rule functionality"); 115 116static int 117ugidfw_rule_valid(struct mac_bsdextended_rule *rule) 118{ 119 120 if ((rule->mbr_subject.mbs_flags | MBS_ALL_FLAGS) != MBS_ALL_FLAGS) 121 return (EINVAL); 122 if ((rule->mbr_subject.mbs_neg | MBS_ALL_FLAGS) != MBS_ALL_FLAGS) 123 return (EINVAL); 124 if ((rule->mbr_object.mbo_flags | MBO_ALL_FLAGS) != MBO_ALL_FLAGS) 125 return (EINVAL); 126 if ((rule->mbr_object.mbo_neg | MBO_ALL_FLAGS) != MBO_ALL_FLAGS) 127 return (EINVAL); 128 if (((rule->mbr_object.mbo_flags & MBO_TYPE_DEFINED) != 0) && 129 (rule->mbr_object.mbo_type | MBO_ALL_TYPE) != MBO_ALL_TYPE) 130 return (EINVAL); 131 if ((rule->mbr_mode | MBI_ALLPERM) != MBI_ALLPERM) 132 return (EINVAL); 133 return (0); 134} 135 136static int 137sysctl_rule(SYSCTL_HANDLER_ARGS) 138{ 139 struct mac_bsdextended_rule temprule, *ruleptr; 140 u_int namelen; 141 int error, index, *name; 142 143 error = 0; 144 name = (int *)arg1; 145 namelen = arg2; 146 if (namelen != 1) 147 return (EINVAL); 148 index = name[0]; 149 if (index >= MAC_BSDEXTENDED_MAXRULES) 150 return (ENOENT); 151 152 ruleptr = NULL; 153 if (req->newptr && req->newlen != 0) { 154 error = SYSCTL_IN(req, &temprule, sizeof(temprule)); 155 if (error) 156 return (error); 157 ruleptr = malloc(sizeof(*ruleptr), M_MACBSDEXTENDED, 158 M_WAITOK | M_ZERO); 159 } 160 161 mtx_lock(&ugidfw_mtx); 162 if (req->oldptr) { 163 if (index < 0 || index > rule_slots + 1) { 164 error = ENOENT; 165 goto out; 166 } 167 if (rules[index] == NULL) { 168 error = ENOENT; 169 goto out; 170 } 171 temprule = *rules[index]; 172 } 173 if (req->newptr && req->newlen == 0) { 174 KASSERT(ruleptr == NULL, ("sysctl_rule: ruleptr != NULL")); 175 ruleptr = rules[index]; 176 if (ruleptr == NULL) { 177 error = ENOENT; 178 goto out; 179 } 180 rule_count--; 181 rules[index] = NULL; 182 } else if (req->newptr) { 183 error = ugidfw_rule_valid(&temprule); 184 if (error) 185 goto out; 186 if (rules[index] == NULL) { 187 *ruleptr = temprule; 188 rules[index] = ruleptr; 189 ruleptr = NULL; 190 if (index + 1 > rule_slots) 191 rule_slots = index + 1; 192 rule_count++; 193 } else 194 *rules[index] = temprule; 195 } 196out: 197 mtx_unlock(&ugidfw_mtx); 198 if (ruleptr != NULL) 199 free(ruleptr, M_MACBSDEXTENDED); 200 if (req->oldptr && error == 0) 201 error = SYSCTL_OUT(req, &temprule, sizeof(temprule)); 202 return (error); 203} 204 205static SYSCTL_NODE(_security_mac_bsdextended, OID_AUTO, rules, 206 CTLFLAG_MPSAFE | CTLFLAG_RW, sysctl_rule, "BSD extended MAC rules"); 207 208static void 209ugidfw_init(struct mac_policy_conf *mpc) 210{ 211 212 mtx_init(&ugidfw_mtx, "mac_bsdextended lock", NULL, MTX_DEF); 213} 214 215static void 216ugidfw_destroy(struct mac_policy_conf *mpc) 217{ 218 int i; 219 220 for (i = 0; i < MAC_BSDEXTENDED_MAXRULES; i++) { 221 if (rules[i] != NULL) 222 free(rules[i], M_MACBSDEXTENDED); 223 } 224 mtx_destroy(&ugidfw_mtx); 225} 226 227static int 228ugidfw_rulecheck(struct mac_bsdextended_rule *rule, 229 struct ucred *cred, struct vnode *vp, struct vattr *vap, int acc_mode) 230{ 231 int mac_granted, match, priv_granted; 232 int i; 233 234 /* 235 * Is there a subject match? 236 */ 237 mtx_assert(&ugidfw_mtx, MA_OWNED); 238 if (rule->mbr_subject.mbs_flags & MBS_UID_DEFINED) { 239 match = ((cred->cr_uid <= rule->mbr_subject.mbs_uid_max && 240 cred->cr_uid >= rule->mbr_subject.mbs_uid_min) || 241 (cred->cr_ruid <= rule->mbr_subject.mbs_uid_max && 242 cred->cr_ruid >= rule->mbr_subject.mbs_uid_min) || 243 (cred->cr_svuid <= rule->mbr_subject.mbs_uid_max && 244 cred->cr_svuid >= rule->mbr_subject.mbs_uid_min)); 245 if (rule->mbr_subject.mbs_neg & MBS_UID_DEFINED) 246 match = !match; 247 if (!match) 248 return (0); 249 } 250 251 if (rule->mbr_subject.mbs_flags & MBS_GID_DEFINED) { 252 match = ((cred->cr_rgid <= rule->mbr_subject.mbs_gid_max && 253 cred->cr_rgid >= rule->mbr_subject.mbs_gid_min) || 254 (cred->cr_svgid <= rule->mbr_subject.mbs_gid_max && 255 cred->cr_svgid >= rule->mbr_subject.mbs_gid_min)); 256 if (!match) { 257 for (i = 0; i < cred->cr_ngroups; i++) { 258 if (cred->cr_groups[i] 259 <= rule->mbr_subject.mbs_gid_max && 260 cred->cr_groups[i] 261 >= rule->mbr_subject.mbs_gid_min) { 262 match = 1; 263 break; 264 } 265 } 266 } 267 if (rule->mbr_subject.mbs_neg & MBS_GID_DEFINED) 268 match = !match; 269 if (!match) 270 return (0); 271 } 272 273 if (rule->mbr_subject.mbs_flags & MBS_PRISON_DEFINED) { 274 match = 275 (cred->cr_prison->pr_id == rule->mbr_subject.mbs_prison); 276 if (rule->mbr_subject.mbs_neg & MBS_PRISON_DEFINED) 277 match = !match; 278 if (!match) 279 return (0); 280 } 281 282 /* 283 * Is there an object match? 284 */ 285 if (rule->mbr_object.mbo_flags & MBO_UID_DEFINED) { 286 match = (vap->va_uid <= rule->mbr_object.mbo_uid_max && 287 vap->va_uid >= rule->mbr_object.mbo_uid_min); 288 if (rule->mbr_object.mbo_neg & MBO_UID_DEFINED) 289 match = !match; 290 if (!match) 291 return (0); 292 } 293 294 if (rule->mbr_object.mbo_flags & MBO_GID_DEFINED) { 295 match = (vap->va_gid <= rule->mbr_object.mbo_gid_max && 296 vap->va_gid >= rule->mbr_object.mbo_gid_min); 297 if (rule->mbr_object.mbo_neg & MBO_GID_DEFINED) 298 match = !match; 299 if (!match) 300 return (0); 301 } 302 303 if (rule->mbr_object.mbo_flags & MBO_FSID_DEFINED) { 304 match = (fsidcmp(&vp->v_mount->mnt_stat.f_fsid, 305 &rule->mbr_object.mbo_fsid) == 0); 306 if (rule->mbr_object.mbo_neg & MBO_FSID_DEFINED) 307 match = !match; 308 if (!match) 309 return (0); 310 } 311 312 if (rule->mbr_object.mbo_flags & MBO_SUID) { 313 match = (vap->va_mode & S_ISUID); 314 if (rule->mbr_object.mbo_neg & MBO_SUID) 315 match = !match; 316 if (!match) 317 return (0); 318 } 319 320 if (rule->mbr_object.mbo_flags & MBO_SGID) { 321 match = (vap->va_mode & S_ISGID); 322 if (rule->mbr_object.mbo_neg & MBO_SGID) 323 match = !match; 324 if (!match) 325 return (0); 326 } 327 328 if (rule->mbr_object.mbo_flags & MBO_UID_SUBJECT) { 329 match = (vap->va_uid == cred->cr_uid || 330 vap->va_uid == cred->cr_ruid || 331 vap->va_uid == cred->cr_svuid); 332 if (rule->mbr_object.mbo_neg & MBO_UID_SUBJECT) 333 match = !match; 334 if (!match) 335 return (0); 336 } 337 338 if (rule->mbr_object.mbo_flags & MBO_GID_SUBJECT) { 339 match = (groupmember(vap->va_gid, cred) || 340 vap->va_gid == cred->cr_rgid || 341 vap->va_gid == cred->cr_svgid); 342 if (rule->mbr_object.mbo_neg & MBO_GID_SUBJECT) 343 match = !match; 344 if (!match) 345 return (0); 346 } 347 348 if (rule->mbr_object.mbo_flags & MBO_TYPE_DEFINED) { 349 switch (vap->va_type) { 350 case VREG: 351 match = (rule->mbr_object.mbo_type & MBO_TYPE_REG); 352 break; 353 case VDIR: 354 match = (rule->mbr_object.mbo_type & MBO_TYPE_DIR); 355 break; 356 case VBLK: 357 match = (rule->mbr_object.mbo_type & MBO_TYPE_BLK); 358 break; 359 case VCHR: 360 match = (rule->mbr_object.mbo_type & MBO_TYPE_CHR); 361 break; 362 case VLNK: 363 match = (rule->mbr_object.mbo_type & MBO_TYPE_LNK); 364 break; 365 case VSOCK: 366 match = (rule->mbr_object.mbo_type & MBO_TYPE_SOCK); 367 break; 368 case VFIFO: 369 match = (rule->mbr_object.mbo_type & MBO_TYPE_FIFO); 370 break; 371 default: 372 match = 0; 373 } 374 if (rule->mbr_object.mbo_neg & MBO_TYPE_DEFINED) 375 match = !match; 376 if (!match) 377 return (0); 378 } 379 380 /* 381 * MBI_APPEND should not be here as it should get converted to 382 * MBI_WRITE. 383 */ 384 priv_granted = 0; 385 mac_granted = rule->mbr_mode; 386 if ((acc_mode & MBI_ADMIN) && (mac_granted & MBI_ADMIN) == 0 && 387 priv_check_cred(cred, PRIV_VFS_ADMIN, 0) == 0) 388 priv_granted |= MBI_ADMIN; 389 if ((acc_mode & MBI_EXEC) && (mac_granted & MBI_EXEC) == 0 && 390 priv_check_cred(cred, (vap->va_type == VDIR) ? PRIV_VFS_LOOKUP : 391 PRIV_VFS_EXEC, 0) == 0) 392 priv_granted |= MBI_EXEC; 393 if ((acc_mode & MBI_READ) && (mac_granted & MBI_READ) == 0 && 394 priv_check_cred(cred, PRIV_VFS_READ, 0) == 0) 395 priv_granted |= MBI_READ; 396 if ((acc_mode & MBI_STAT) && (mac_granted & MBI_STAT) == 0 && 397 priv_check_cred(cred, PRIV_VFS_STAT, 0) == 0) 398 priv_granted |= MBI_STAT; 399 if ((acc_mode & MBI_WRITE) && (mac_granted & MBI_WRITE) == 0 && 400 priv_check_cred(cred, PRIV_VFS_WRITE, 0) == 0) 401 priv_granted |= MBI_WRITE; 402 /* 403 * Is the access permitted? 404 */ 405 if (((mac_granted | priv_granted) & acc_mode) != acc_mode) { 406 if (ugidfw_logging) 407 log(LOG_AUTHPRIV, "mac_bsdextended: %d:%d request %d" 408 " on %d:%d failed. \n", cred->cr_ruid, 409 cred->cr_rgid, acc_mode, vap->va_uid, 410 vap->va_gid); 411 return (EACCES); 412 } 413 414 /* 415 * If the rule matched, permits access, and first match is enabled, 416 * return success. 417 */ 418 if (ugidfw_firstmatch_enabled) 419 return (EJUSTRETURN); 420 else 421 return (0); 422} 423 424int 425ugidfw_check(struct ucred *cred, struct vnode *vp, struct vattr *vap, 426 int acc_mode) 427{ 428 int error, i; 429 430 /* 431 * Since we do not separately handle append, map append to write. 432 */ 433 if (acc_mode & MBI_APPEND) { 434 acc_mode &= ~MBI_APPEND; 435 acc_mode |= MBI_WRITE; 436 } 437 mtx_lock(&ugidfw_mtx); 438 for (i = 0; i < rule_slots; i++) { 439 if (rules[i] == NULL) 440 continue; 441 error = ugidfw_rulecheck(rules[i], cred, 442 vp, vap, acc_mode); 443 if (error == EJUSTRETURN) 444 break; 445 if (error) { 446 mtx_unlock(&ugidfw_mtx); 447 return (error); 448 } 449 } 450 mtx_unlock(&ugidfw_mtx); 451 return (0); 452} 453 454int 455ugidfw_check_vp(struct ucred *cred, struct vnode *vp, int acc_mode) 456{ 457 int error; 458 struct vattr vap; 459 460 if (!ugidfw_enabled) 461 return (0); 462 error = VOP_GETATTR(vp, &vap, cred); 463 if (error) 464 return (error); 465 return (ugidfw_check(cred, vp, &vap, acc_mode)); 466} 467 468int 469ugidfw_accmode2mbi(accmode_t accmode) 470{ 471 int mbi; 472 473 mbi = 0; 474 if (accmode & VEXEC) 475 mbi |= MBI_EXEC; 476 if (accmode & VWRITE) 477 mbi |= MBI_WRITE; 478 if (accmode & VREAD) 479 mbi |= MBI_READ; 480 if (accmode & VADMIN_PERMS) 481 mbi |= MBI_ADMIN; 482 if (accmode & VSTAT_PERMS) 483 mbi |= MBI_STAT; 484 if (accmode & VAPPEND) 485 mbi |= MBI_APPEND; 486 return (mbi); 487} 488 489static struct mac_policy_ops ugidfw_ops = 490{ 491 .mpo_destroy = ugidfw_destroy, 492 .mpo_init = ugidfw_init, 493 .mpo_system_check_acct = ugidfw_system_check_acct, 494 .mpo_system_check_auditctl = ugidfw_system_check_auditctl, 495 .mpo_system_check_swapon = ugidfw_system_check_swapon, 496 .mpo_vnode_check_access = ugidfw_vnode_check_access, 497 .mpo_vnode_check_chdir = ugidfw_vnode_check_chdir, 498 .mpo_vnode_check_chroot = ugidfw_vnode_check_chroot, 499 .mpo_vnode_check_create = ugidfw_check_create_vnode, 500 .mpo_vnode_check_deleteacl = ugidfw_vnode_check_deleteacl, 501 .mpo_vnode_check_deleteextattr = ugidfw_vnode_check_deleteextattr, 502 .mpo_vnode_check_exec = ugidfw_vnode_check_exec, 503 .mpo_vnode_check_getacl = ugidfw_vnode_check_getacl, 504 .mpo_vnode_check_getextattr = ugidfw_vnode_check_getextattr, 505 .mpo_vnode_check_link = ugidfw_vnode_check_link, 506 .mpo_vnode_check_listextattr = ugidfw_vnode_check_listextattr, 507 .mpo_vnode_check_lookup = ugidfw_vnode_check_lookup, 508 .mpo_vnode_check_open = ugidfw_vnode_check_open, 509 .mpo_vnode_check_readdir = ugidfw_vnode_check_readdir, 510 .mpo_vnode_check_readlink = ugidfw_vnode_check_readdlink, 511 .mpo_vnode_check_rename_from = ugidfw_vnode_check_rename_from, 512 .mpo_vnode_check_rename_to = ugidfw_vnode_check_rename_to, 513 .mpo_vnode_check_revoke = ugidfw_vnode_check_revoke, 514 .mpo_vnode_check_setacl = ugidfw_check_setacl_vnode, 515 .mpo_vnode_check_setextattr = ugidfw_vnode_check_setextattr, 516 .mpo_vnode_check_setflags = ugidfw_vnode_check_setflags, 517 .mpo_vnode_check_setmode = ugidfw_vnode_check_setmode, 518 .mpo_vnode_check_setowner = ugidfw_vnode_check_setowner, 519 .mpo_vnode_check_setutimes = ugidfw_vnode_check_setutimes, 520 .mpo_vnode_check_stat = ugidfw_vnode_check_stat, 521 .mpo_vnode_check_unlink = ugidfw_vnode_check_unlink, 522}; 523 524MAC_POLICY_SET(&ugidfw_ops, mac_bsdextended, "TrustedBSD MAC/BSD Extended", 525 MPC_LOADTIME_FLAG_UNLOADOK, NULL); 526