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