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 71 static struct mtx ugidfw_mtx; 72 73 SYSCTL_DECL(_security_mac); 74 75 static SYSCTL_NODE(_security_mac, OID_AUTO, bsdextended, CTLFLAG_RW, 0, 76 "TrustedBSD extended BSD MAC policy controls"); 77 78 static int ugidfw_enabled = 1; 79 SYSCTL_INT(_security_mac_bsdextended, OID_AUTO, enabled, CTLFLAG_RW, 80 &ugidfw_enabled, 0, "Enforce extended BSD policy"); 81 TUNABLE_INT("security.mac.bsdextended.enabled", &ugidfw_enabled); 82 83 static MALLOC_DEFINE(M_MACBSDEXTENDED, "mac_bsdextended", 84 "BSD Extended MAC rule"); 85 86 #define MAC_BSDEXTENDED_MAXRULES 250 87 static struct mac_bsdextended_rule *rules[MAC_BSDEXTENDED_MAXRULES]; 88 static int rule_count = 0; 89 static int rule_slots = 0; 90 static int rule_version = MB_VERSION; 91 92 SYSCTL_INT(_security_mac_bsdextended, OID_AUTO, rule_count, CTLFLAG_RD, 93 &rule_count, 0, "Number of defined rules\n"); 94 SYSCTL_INT(_security_mac_bsdextended, OID_AUTO, rule_slots, CTLFLAG_RD, 95 &rule_slots, 0, "Number of used rule slots\n"); 96 SYSCTL_INT(_security_mac_bsdextended, OID_AUTO, rule_version, CTLFLAG_RD, 97 &rule_version, 0, "Version number for API\n"); 98 99 /* 100 * This is just used for logging purposes, eventually we would like to log 101 * much more then failed requests. 102 */ 103 static int ugidfw_logging; 104 SYSCTL_INT(_security_mac_bsdextended, OID_AUTO, logging, CTLFLAG_RW, 105 &ugidfw_logging, 0, "Log failed authorization requests"); 106 107 /* 108 * This tunable is here for compatibility. It will allow the user to switch 109 * between the new mode (first rule matches) and the old functionality (all 110 * rules match). 111 */ 112 static int ugidfw_firstmatch_enabled; 113 SYSCTL_INT(_security_mac_bsdextended, OID_AUTO, firstmatch_enabled, 114 CTLFLAG_RW, &ugidfw_firstmatch_enabled, 1, 115 "Disable/enable match first rule functionality"); 116 117 static int 118 ugidfw_rule_valid(struct mac_bsdextended_rule *rule) 119 { 120 121 if ((rule->mbr_subject.mbs_flags | MBS_ALL_FLAGS) != MBS_ALL_FLAGS) 122 return (EINVAL); 123 if ((rule->mbr_subject.mbs_neg | MBS_ALL_FLAGS) != MBS_ALL_FLAGS) 124 return (EINVAL); 125 if ((rule->mbr_object.mbo_flags | MBO_ALL_FLAGS) != MBO_ALL_FLAGS) 126 return (EINVAL); 127 if ((rule->mbr_object.mbo_neg | MBO_ALL_FLAGS) != MBO_ALL_FLAGS) 128 return (EINVAL); 129 if ((rule->mbr_object.mbo_neg | MBO_TYPE_DEFINED) && 130 (rule->mbr_object.mbo_type | MBO_ALL_TYPE) != MBO_ALL_TYPE) 131 return (EINVAL); 132 if ((rule->mbr_mode | MBI_ALLPERM) != MBI_ALLPERM) 133 return (EINVAL); 134 return (0); 135 } 136 137 static int 138 sysctl_rule(SYSCTL_HANDLER_ARGS) 139 { 140 struct mac_bsdextended_rule temprule, *ruleptr; 141 u_int namelen; 142 int error, index, *name; 143 144 error = 0; 145 name = (int *)arg1; 146 namelen = arg2; 147 if (namelen != 1) 148 return (EINVAL); 149 index = name[0]; 150 if (index >= MAC_BSDEXTENDED_MAXRULES) 151 return (ENOENT); 152 153 ruleptr = NULL; 154 if (req->newptr && req->newlen != 0) { 155 error = SYSCTL_IN(req, &temprule, sizeof(temprule)); 156 if (error) 157 return (error); 158 ruleptr = malloc(sizeof(*ruleptr), M_MACBSDEXTENDED, 159 M_WAITOK | M_ZERO); 160 } 161 162 mtx_lock(&ugidfw_mtx); 163 if (req->oldptr) { 164 if (index < 0 || index > rule_slots + 1) { 165 error = ENOENT; 166 goto out; 167 } 168 if (rules[index] == NULL) { 169 error = ENOENT; 170 goto out; 171 } 172 temprule = *rules[index]; 173 } 174 if (req->newptr && req->newlen == 0) { 175 KASSERT(ruleptr == NULL, ("sysctl_rule: ruleptr != NULL")); 176 ruleptr = rules[index]; 177 if (ruleptr == NULL) { 178 error = ENOENT; 179 goto out; 180 } 181 rule_count--; 182 rules[index] = NULL; 183 } else if (req->newptr) { 184 error = ugidfw_rule_valid(&temprule); 185 if (error) 186 goto out; 187 if (rules[index] == NULL) { 188 *ruleptr = temprule; 189 rules[index] = ruleptr; 190 ruleptr = NULL; 191 if (index + 1 > rule_slots) 192 rule_slots = index + 1; 193 rule_count++; 194 } else 195 *rules[index] = temprule; 196 } 197 out: 198 mtx_unlock(&ugidfw_mtx); 199 if (ruleptr != NULL) 200 free(ruleptr, M_MACBSDEXTENDED); 201 if (req->oldptr && error == 0) 202 error = SYSCTL_OUT(req, &temprule, sizeof(temprule)); 203 return (error); 204 } 205 206 static SYSCTL_NODE(_security_mac_bsdextended, OID_AUTO, rules, 207 CTLFLAG_MPSAFE | CTLFLAG_RW, sysctl_rule, "BSD extended MAC rules"); 208 209 static void 210 ugidfw_init(struct mac_policy_conf *mpc) 211 { 212 213 mtx_init(&ugidfw_mtx, "mac_bsdextended lock", NULL, MTX_DEF); 214 } 215 216 static void 217 ugidfw_destroy(struct mac_policy_conf *mpc) 218 { 219 int i; 220 221 for (i = 0; i < MAC_BSDEXTENDED_MAXRULES; i++) { 222 if (rules[i] != NULL) 223 free(rules[i], M_MACBSDEXTENDED); 224 } 225 mtx_destroy(&ugidfw_mtx); 226 } 227 228 static int 229 ugidfw_rulecheck(struct mac_bsdextended_rule *rule, 230 struct ucred *cred, struct vnode *vp, struct vattr *vap, int acc_mode) 231 { 232 int mac_granted, match, priv_granted; 233 int i; 234 235 /* 236 * Is there a subject match? 237 */ 238 mtx_assert(&ugidfw_mtx, MA_OWNED); 239 if (rule->mbr_subject.mbs_flags & MBS_UID_DEFINED) { 240 match = ((cred->cr_uid <= rule->mbr_subject.mbs_uid_max && 241 cred->cr_uid >= rule->mbr_subject.mbs_uid_min) || 242 (cred->cr_ruid <= rule->mbr_subject.mbs_uid_max && 243 cred->cr_ruid >= rule->mbr_subject.mbs_uid_min) || 244 (cred->cr_svuid <= rule->mbr_subject.mbs_uid_max && 245 cred->cr_svuid >= rule->mbr_subject.mbs_uid_min)); 246 if (rule->mbr_subject.mbs_neg & MBS_UID_DEFINED) 247 match = !match; 248 if (!match) 249 return (0); 250 } 251 252 if (rule->mbr_subject.mbs_flags & MBS_GID_DEFINED) { 253 match = ((cred->cr_rgid <= rule->mbr_subject.mbs_gid_max && 254 cred->cr_rgid >= rule->mbr_subject.mbs_gid_min) || 255 (cred->cr_svgid <= rule->mbr_subject.mbs_gid_max && 256 cred->cr_svgid >= rule->mbr_subject.mbs_gid_min)); 257 if (!match) { 258 for (i = 0; i < cred->cr_ngroups; i++) { 259 if (cred->cr_groups[i] 260 <= rule->mbr_subject.mbs_gid_max && 261 cred->cr_groups[i] 262 >= rule->mbr_subject.mbs_gid_min) { 263 match = 1; 264 break; 265 } 266 } 267 } 268 if (rule->mbr_subject.mbs_neg & MBS_GID_DEFINED) 269 match = !match; 270 if (!match) 271 return (0); 272 } 273 274 if (rule->mbr_subject.mbs_flags & MBS_PRISON_DEFINED) { 275 match = 276 (cred->cr_prison->pr_id == rule->mbr_subject.mbs_prison); 277 if (rule->mbr_subject.mbs_neg & MBS_PRISON_DEFINED) 278 match = !match; 279 if (!match) 280 return (0); 281 } 282 283 /* 284 * Is there an object match? 285 */ 286 if (rule->mbr_object.mbo_flags & MBO_UID_DEFINED) { 287 match = (vap->va_uid <= rule->mbr_object.mbo_uid_max && 288 vap->va_uid >= rule->mbr_object.mbo_uid_min); 289 if (rule->mbr_object.mbo_neg & MBO_UID_DEFINED) 290 match = !match; 291 if (!match) 292 return (0); 293 } 294 295 if (rule->mbr_object.mbo_flags & MBO_GID_DEFINED) { 296 match = (vap->va_gid <= rule->mbr_object.mbo_gid_max && 297 vap->va_gid >= rule->mbr_object.mbo_gid_min); 298 if (rule->mbr_object.mbo_neg & MBO_GID_DEFINED) 299 match = !match; 300 if (!match) 301 return (0); 302 } 303 304 if (rule->mbr_object.mbo_flags & MBO_FSID_DEFINED) { 305 match = (bcmp(&(vp->v_mount->mnt_stat.f_fsid), 306 &(rule->mbr_object.mbo_fsid), 307 sizeof(rule->mbr_object.mbo_fsid)) == 0); 308 if (rule->mbr_object.mbo_neg & MBO_FSID_DEFINED) 309 match = !match; 310 if (!match) 311 return (0); 312 } 313 314 if (rule->mbr_object.mbo_flags & MBO_SUID) { 315 match = (vap->va_mode & S_ISUID); 316 if (rule->mbr_object.mbo_neg & MBO_SUID) 317 match = !match; 318 if (!match) 319 return (0); 320 } 321 322 if (rule->mbr_object.mbo_flags & MBO_SGID) { 323 match = (vap->va_mode & S_ISGID); 324 if (rule->mbr_object.mbo_neg & MBO_SGID) 325 match = !match; 326 if (!match) 327 return (0); 328 } 329 330 if (rule->mbr_object.mbo_flags & MBO_UID_SUBJECT) { 331 match = (vap->va_uid == cred->cr_uid || 332 vap->va_uid == cred->cr_ruid || 333 vap->va_uid == cred->cr_svuid); 334 if (rule->mbr_object.mbo_neg & MBO_UID_SUBJECT) 335 match = !match; 336 if (!match) 337 return (0); 338 } 339 340 if (rule->mbr_object.mbo_flags & MBO_GID_SUBJECT) { 341 match = (groupmember(vap->va_gid, cred) || 342 vap->va_gid == cred->cr_rgid || 343 vap->va_gid == cred->cr_svgid); 344 if (rule->mbr_object.mbo_neg & MBO_GID_SUBJECT) 345 match = !match; 346 if (!match) 347 return (0); 348 } 349 350 if (rule->mbr_object.mbo_flags & MBO_TYPE_DEFINED) { 351 switch (vap->va_type) { 352 case VREG: 353 match = (rule->mbr_object.mbo_type & MBO_TYPE_REG); 354 break; 355 case VDIR: 356 match = (rule->mbr_object.mbo_type & MBO_TYPE_DIR); 357 break; 358 case VBLK: 359 match = (rule->mbr_object.mbo_type & MBO_TYPE_BLK); 360 break; 361 case VCHR: 362 match = (rule->mbr_object.mbo_type & MBO_TYPE_CHR); 363 break; 364 case VLNK: 365 match = (rule->mbr_object.mbo_type & MBO_TYPE_LNK); 366 break; 367 case VSOCK: 368 match = (rule->mbr_object.mbo_type & MBO_TYPE_SOCK); 369 break; 370 case VFIFO: 371 match = (rule->mbr_object.mbo_type & MBO_TYPE_FIFO); 372 break; 373 default: 374 match = 0; 375 } 376 if (rule->mbr_object.mbo_neg & MBO_TYPE_DEFINED) 377 match = !match; 378 if (!match) 379 return (0); 380 } 381 382 /* 383 * MBI_APPEND should not be here as it should get converted to 384 * MBI_WRITE. 385 */ 386 priv_granted = 0; 387 mac_granted = rule->mbr_mode; 388 if ((acc_mode & MBI_ADMIN) && (mac_granted & MBI_ADMIN) == 0 && 389 priv_check_cred(cred, PRIV_VFS_ADMIN, 0) == 0) 390 priv_granted |= MBI_ADMIN; 391 if ((acc_mode & MBI_EXEC) && (mac_granted & MBI_EXEC) == 0 && 392 priv_check_cred(cred, (vap->va_type == VDIR) ? PRIV_VFS_LOOKUP : 393 PRIV_VFS_EXEC, 0) == 0) 394 priv_granted |= MBI_EXEC; 395 if ((acc_mode & MBI_READ) && (mac_granted & MBI_READ) == 0 && 396 priv_check_cred(cred, PRIV_VFS_READ, 0) == 0) 397 priv_granted |= MBI_READ; 398 if ((acc_mode & MBI_STAT) && (mac_granted & MBI_STAT) == 0 && 399 priv_check_cred(cred, PRIV_VFS_STAT, 0) == 0) 400 priv_granted |= MBI_STAT; 401 if ((acc_mode & MBI_WRITE) && (mac_granted & MBI_WRITE) == 0 && 402 priv_check_cred(cred, PRIV_VFS_WRITE, 0) == 0) 403 priv_granted |= MBI_WRITE; 404 /* 405 * Is the access permitted? 406 */ 407 if (((mac_granted | priv_granted) & acc_mode) != acc_mode) { 408 if (ugidfw_logging) 409 log(LOG_AUTHPRIV, "mac_bsdextended: %d:%d request %d" 410 " on %d:%d failed. \n", cred->cr_ruid, 411 cred->cr_rgid, acc_mode, vap->va_uid, 412 vap->va_gid); 413 return (EACCES); 414 } 415 416 /* 417 * If the rule matched, permits access, and first match is enabled, 418 * return success. 419 */ 420 if (ugidfw_firstmatch_enabled) 421 return (EJUSTRETURN); 422 else 423 return (0); 424 } 425 426 int 427 ugidfw_check(struct ucred *cred, struct vnode *vp, struct vattr *vap, 428 int acc_mode) 429 { 430 int error, i; 431 432 /* 433 * Since we do not separately handle append, map append to write. 434 */ 435 if (acc_mode & MBI_APPEND) { 436 acc_mode &= ~MBI_APPEND; 437 acc_mode |= MBI_WRITE; 438 } 439 mtx_lock(&ugidfw_mtx); 440 for (i = 0; i < rule_slots; i++) { 441 if (rules[i] == NULL) 442 continue; 443 error = ugidfw_rulecheck(rules[i], cred, 444 vp, vap, acc_mode); 445 if (error == EJUSTRETURN) 446 break; 447 if (error) { 448 mtx_unlock(&ugidfw_mtx); 449 return (error); 450 } 451 } 452 mtx_unlock(&ugidfw_mtx); 453 return (0); 454 } 455 456 int 457 ugidfw_check_vp(struct ucred *cred, struct vnode *vp, int acc_mode) 458 { 459 int error; 460 struct vattr vap; 461 462 if (!ugidfw_enabled) 463 return (0); 464 error = VOP_GETATTR(vp, &vap, cred); 465 if (error) 466 return (error); 467 return (ugidfw_check(cred, vp, &vap, acc_mode)); 468 } 469 470 int 471 ugidfw_accmode2mbi(accmode_t accmode) 472 { 473 int mbi; 474 475 mbi = 0; 476 if (accmode & VEXEC) 477 mbi |= MBI_EXEC; 478 if (accmode & VWRITE) 479 mbi |= MBI_WRITE; 480 if (accmode & VREAD) 481 mbi |= MBI_READ; 482 if (accmode & VADMIN_PERMS) 483 mbi |= MBI_ADMIN; 484 if (accmode & VSTAT_PERMS) 485 mbi |= MBI_STAT; 486 if (accmode & VAPPEND) 487 mbi |= MBI_APPEND; 488 return (mbi); 489 } 490 491 static struct mac_policy_ops ugidfw_ops = 492 { 493 .mpo_destroy = ugidfw_destroy, 494 .mpo_init = ugidfw_init, 495 .mpo_system_check_acct = ugidfw_system_check_acct, 496 .mpo_system_check_auditctl = ugidfw_system_check_auditctl, 497 .mpo_system_check_swapon = ugidfw_system_check_swapon, 498 .mpo_vnode_check_access = ugidfw_vnode_check_access, 499 .mpo_vnode_check_chdir = ugidfw_vnode_check_chdir, 500 .mpo_vnode_check_chroot = ugidfw_vnode_check_chroot, 501 .mpo_vnode_check_create = ugidfw_check_create_vnode, 502 .mpo_vnode_check_deleteacl = ugidfw_vnode_check_deleteacl, 503 .mpo_vnode_check_deleteextattr = ugidfw_vnode_check_deleteextattr, 504 .mpo_vnode_check_exec = ugidfw_vnode_check_exec, 505 .mpo_vnode_check_getacl = ugidfw_vnode_check_getacl, 506 .mpo_vnode_check_getextattr = ugidfw_vnode_check_getextattr, 507 .mpo_vnode_check_link = ugidfw_vnode_check_link, 508 .mpo_vnode_check_listextattr = ugidfw_vnode_check_listextattr, 509 .mpo_vnode_check_lookup = ugidfw_vnode_check_lookup, 510 .mpo_vnode_check_open = ugidfw_vnode_check_open, 511 .mpo_vnode_check_readdir = ugidfw_vnode_check_readdir, 512 .mpo_vnode_check_readlink = ugidfw_vnode_check_readdlink, 513 .mpo_vnode_check_rename_from = ugidfw_vnode_check_rename_from, 514 .mpo_vnode_check_rename_to = ugidfw_vnode_check_rename_to, 515 .mpo_vnode_check_revoke = ugidfw_vnode_check_revoke, 516 .mpo_vnode_check_setacl = ugidfw_check_setacl_vnode, 517 .mpo_vnode_check_setextattr = ugidfw_vnode_check_setextattr, 518 .mpo_vnode_check_setflags = ugidfw_vnode_check_setflags, 519 .mpo_vnode_check_setmode = ugidfw_vnode_check_setmode, 520 .mpo_vnode_check_setowner = ugidfw_vnode_check_setowner, 521 .mpo_vnode_check_setutimes = ugidfw_vnode_check_setutimes, 522 .mpo_vnode_check_stat = ugidfw_vnode_check_stat, 523 .mpo_vnode_check_unlink = ugidfw_vnode_check_unlink, 524 }; 525 526 MAC_POLICY_SET(&ugidfw_ops, mac_bsdextended, "TrustedBSD MAC/BSD Extended", 527 MPC_LOADTIME_FLAG_UNLOADOK, NULL); 528