mac_portacl.c revision 330500
1145522Sdarrenr/*- 2145522Sdarrenr * Copyright (c) 2003-2004 Networks Associates Technology, Inc. 353642Sguido * Copyright (c) 2006 SPARTA, Inc. 4255332Scy * All rights reserved. 553642Sguido * 680482Sdarrenr * This software was developed for the FreeBSD Project by Network 753642Sguido * Associates Laboratories, the Security Research Division of Network 8145522Sdarrenr * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), 9145522Sdarrenr * as part of the DARPA CHATS research program. 10145522Sdarrenr * 11145522Sdarrenr * This software was enhanced by SPARTA ISSO under SPAWAR contract 12145522Sdarrenr * N66001-04-C-6019 ("SEFOS"). 1392685Sdarrenr * 1453642Sguido * Redistribution and use in source and binary forms, with or without 1553642Sguido * modification, are permitted provided that the following conditions 1653642Sguido * are met: 1753642Sguido * 1. Redistributions of source code must retain the above copyright 1853642Sguido * notice, this list of conditions and the following disclaimer. 19145522Sdarrenr * 2. Redistributions in binary form must reproduce the above copyright 2053642Sguido * notice, this list of conditions and the following disclaimer in the 2153642Sguido * documentation and/or other materials provided with the distribution. 22255332Scy * 23255332Scy * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 24255332Scy * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 2553642Sguido * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26145522Sdarrenr * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 27145522Sdarrenr * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28145522Sdarrenr * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2953642Sguido * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30369277Scy * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 3153642Sguido * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 3253642Sguido * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 3353642Sguido * SUCH DAMAGE. 3453642Sguido * 3553642Sguido * $FreeBSD: stable/10/sys/security/mac_portacl/mac_portacl.c 330500 2018-03-05 12:21:36Z eugen $ 3653642Sguido */ 3753642Sguido 38145522Sdarrenr/* 3953642Sguido * Developed by the TrustedBSD Project. 40344833Scy * 4153642Sguido * Administratively limit access to local UDP/TCP ports for binding purposes. 4253642Sguido * Intended to be combined with net.inet.ip.portrange.reservedhigh to allow 43145522Sdarrenr * specific uids and gids to bind specific ports for specific purposes, 44344833Scy * while not opening the door to any user replacing an "official" service 4553642Sguido * while you're restarting it. This only affects ports explicitly bound by 4653642Sguido * the user process (either for listen/outgoing socket for TCP, or send/ 4753642Sguido * receive for UDP). This module will not limit ports bound implicitly for 4853642Sguido * out-going connections where the process hasn't explicitly selected a port: 4953642Sguido * these are automatically selected by the IP stack. 5053642Sguido * 5153642Sguido * To use this module, security.mac.enforce_socket must be enabled, and you 5253642Sguido * will probably want to twiddle the net.inet sysctl listed above. Then use 53369277Scy * sysctl(8) to modify the rules string: 5453642Sguido * 5553642Sguido * # sysctl security.mac.portacl.rules="uid:425:tcp:80,uid:425:tcp:79" 56344833Scy * 5753642Sguido * This ruleset, for example, permits uid 425 to bind TCP ports 80 (http) and 5853642Sguido * 79 (finger). User names and group names can't be used directly because 59145522Sdarrenr * the kernel only knows about uids and gids. 60145522Sdarrenr */ 61145522Sdarrenr 62255332Scy#include <sys/param.h> 63255332Scy#include <sys/domain.h> 64255332Scy#include <sys/kernel.h> 65255332Scy#include <sys/lock.h> 6653642Sguido#include <sys/malloc.h> 6753642Sguido#include <sys/module.h> 6853642Sguido#include <sys/mutex.h> 6953642Sguido#include <sys/priv.h> 7053642Sguido#include <sys/proc.h> 7153642Sguido#include <sys/protosw.h> 7253642Sguido#include <sys/queue.h> 73249266Sglebius#include <sys/systm.h> 74344833Scy#include <sys/sbuf.h> 7553642Sguido#include <sys/socket.h> 76145522Sdarrenr#include <sys/socketvar.h> 7753642Sguido#include <sys/sysctl.h> 7853642Sguido 7953642Sguido#include <netinet/in.h> 80145522Sdarrenr#include <netinet/in_pcb.h> 8153642Sguido 8253642Sguido#include <security/mac/mac_policy.h> 8353642Sguido 84369277ScySYSCTL_DECL(_security_mac); 85369246Scy 86369246Scystatic SYSCTL_NODE(_security_mac, OID_AUTO, portacl, CTLFLAG_RW, 0, 87369246Scy "TrustedBSD mac_portacl policy controls"); 88369246Scy 89369246Scystatic int portacl_enabled = 1; 90369246ScySYSCTL_INT(_security_mac_portacl, OID_AUTO, enabled, CTLFLAG_RW, 9153642Sguido &portacl_enabled, 0, "Enforce portacl policy"); 9253642SguidoTUNABLE_INT("security.mac.portacl.enabled", &portacl_enabled); 9353642Sguido 9453642Sguidostatic int portacl_suser_exempt = 1; 9553642SguidoSYSCTL_INT(_security_mac_portacl, OID_AUTO, suser_exempt, CTLFLAG_RW, 9653642Sguido &portacl_suser_exempt, 0, "Privilege permits binding of any port"); 97369272ScyTUNABLE_INT("security.mac.portacl.suser_exempt", 9853642Sguido &portacl_suser_exempt); 9953642Sguido 10053642Sguidostatic int portacl_autoport_exempt = 1; 10153642SguidoSYSCTL_INT(_security_mac_portacl, OID_AUTO, autoport_exempt, CTLFLAG_RW, 10253642Sguido &portacl_autoport_exempt, 0, "Allow automatic allocation through " 103369277Scy "binding port 0 if not IP_PORTRANGELOW"); 10453642SguidoTUNABLE_INT("security.mac.portacl.autoport_exempt", 105145522Sdarrenr &portacl_autoport_exempt); 10653642Sguido 10753642Sguidostatic int portacl_port_high = 1023; 10853642SguidoSYSCTL_INT(_security_mac_portacl, OID_AUTO, port_high, CTLFLAG_RW, 10953642Sguido &portacl_port_high, 0, "Highest port to enforce for"); 110145522SdarrenrTUNABLE_INT("security.mac.portacl.port_high", &portacl_port_high); 11153642Sguido 11280482Sdarrenrstatic MALLOC_DEFINE(M_PORTACL, "portacl_rule", "Rules for mac_portacl"); 11380482Sdarrenr 114172776Sdarrenr#define MAC_RULE_STRING_LEN 1024 11580482Sdarrenr 11653642Sguido#define RULE_GID 1 11753642Sguido#define RULE_UID 2 118369245Sgit2svn#define RULE_PROTO_TCP 1 119369245Sgit2svn#define RULE_PROTO_UDP 2 120369245Sgit2svnstruct rule { 121369245Sgit2svn id_t r_id; 122369245Sgit2svn int r_idtype; 123369245Sgit2svn u_int16_t r_port; 124369245Sgit2svn int r_protocol; 12553642Sguido 126255332Scy TAILQ_ENTRY(rule) r_entries; 127170268Sdarrenr}; 128255332Scy 129170268Sdarrenr#define GID_STRING "gid" 130170268Sdarrenr#define TCP_STRING "tcp" 131170268Sdarrenr#define UID_STRING "uid" 132255332Scy#define UDP_STRING "udp" 133255332Scy 134255332Scy/* 135255332Scy * Text format for the rule string is that a rule consists of a 136255332Scy * comma-separated list of elements. Each element is in the form 137255332Scy * idtype:id:protocol:portnumber, and constitutes granting of permission 138255332Scy * for the specified binding. 139255332Scy */ 140255332Scy 141255332Scystatic struct mtx rule_mtx; 142255332Scystatic TAILQ_HEAD(rulehead, rule) rule_head; 143255332Scystatic char rule_string[MAC_RULE_STRING_LEN]; 144255332Scy 145255332Scystatic void 146255332Scytoast_rules(struct rulehead *head) 147255332Scy{ 148255332Scy struct rule *rule; 149255332Scy 150255332Scy while ((rule = TAILQ_FIRST(head)) != NULL) { 151255332Scy TAILQ_REMOVE(head, rule, r_entries); 152255332Scy free(rule, M_PORTACL); 153255332Scy } 154255332Scy} 155255332Scy 156255332Scy/* 157255332Scy * Note that there is an inherent race condition in the unload of modules 158255332Scy * and access via sysctl. 159255332Scy */ 160255332Scystatic void 161255332Scydestroy(struct mac_policy_conf *mpc) 162255332Scy{ 163255332Scy 164255332Scy mtx_destroy(&rule_mtx); 165255332Scy toast_rules(&rule_head); 166255332Scy} 167255332Scy 168255332Scystatic void 169255332Scyinit(struct mac_policy_conf *mpc) 170255332Scy{ 171255332Scy 172255332Scy mtx_init(&rule_mtx, "rule_mtx", NULL, MTX_DEF); 173255332Scy TAILQ_INIT(&rule_head); 174255332Scy} 175255332Scy 176255332Scy/* 177255332Scy * Note: parsing routines are destructive on the passed string. 178255332Scy */ 179255332Scystatic int 180255332Scyparse_rule_element(char *element, struct rule **rule) 181255332Scy{ 182255332Scy char *idtype, *id, *protocol, *portnumber, *p; 183255332Scy struct rule *new; 184255332Scy int error; 185255332Scy 186255332Scy error = 0; 187255332Scy new = malloc(sizeof(*new), M_PORTACL, M_ZERO | M_WAITOK); 188255332Scy 189255332Scy idtype = strsep(&element, ":"); 190255332Scy if (idtype == NULL) { 191255332Scy error = EINVAL; 192255332Scy goto out; 193255332Scy } 194255332Scy id = strsep(&element, ":"); 195170268Sdarrenr if (id == NULL) { 196170268Sdarrenr error = EINVAL; 197170268Sdarrenr goto out; 198255332Scy } 199255332Scy new->r_id = strtol(id, &p, 10); 200255332Scy if (*p != '\0') { 201255332Scy error = EINVAL; 202145522Sdarrenr goto out; 203255332Scy } 204255332Scy if (strcmp(idtype, UID_STRING) == 0) 205255332Scy new->r_idtype = RULE_UID; 206255332Scy else if (strcmp(idtype, GID_STRING) == 0) 207255332Scy new->r_idtype = RULE_GID; 208145522Sdarrenr else { 209255332Scy error = EINVAL; 210255332Scy goto out; 211145522Sdarrenr } 212255332Scy protocol = strsep(&element, ":"); 213255332Scy if (protocol == NULL) { 214255332Scy error = EINVAL; 215145522Sdarrenr goto out; 216255332Scy } 217255332Scy if (strcmp(protocol, TCP_STRING) == 0) 218145522Sdarrenr new->r_protocol = RULE_PROTO_TCP; 219145522Sdarrenr else if (strcmp(protocol, UDP_STRING) == 0) 220255332Scy new->r_protocol = RULE_PROTO_UDP; 221255332Scy else { 222145522Sdarrenr error = EINVAL; 223255332Scy goto out; 224255332Scy } 225255332Scy portnumber = element; 226255332Scy if (portnumber == NULL) { 227255332Scy error = EINVAL; 228255332Scy goto out; 229255332Scy } 230255332Scy new->r_port = strtol(portnumber, &p, 10); 231255332Scy if (*p != '\0') { 232255332Scy error = EINVAL; 233255332Scy goto out; 234255332Scy } 235255332Scy 236255332Scyout: 237255332Scy if (error != 0) { 238255332Scy free(new, M_PORTACL); 239255332Scy *rule = NULL; 240255332Scy } else 241255332Scy *rule = new; 242255332Scy return (error); 243255332Scy} 244255332Scy 245255332Scystatic int 246255332Scyparse_rules(char *string, struct rulehead *head) 247255332Scy{ 248255332Scy struct rule *new; 249255332Scy char *element; 250255332Scy int error; 251255332Scy 252255332Scy error = 0; 253255332Scy while ((element = strsep(&string, ",")) != NULL) { 254255332Scy if (strlen(element) == 0) 255255332Scy continue; 256255332Scy error = parse_rule_element(element, &new); 257255332Scy if (error) 258255332Scy goto out; 259255332Scy TAILQ_INSERT_TAIL(head, new, r_entries); 260255332Scy } 261255332Scyout: 262255332Scy if (error != 0) 263255332Scy toast_rules(head); 264255332Scy return (error); 265255332Scy} 266255332Scy 267255332Scy/* 268255332Scy * rule_printf() and rules_to_string() are unused currently because they rely 269255332Scy * on sbufs with auto-extension, which may sleep while holding a mutex. 270255332Scy * Instead, the non-canonical user-generated rule string is returned to the 271255332Scy * user when the rules are queried, which is faster anyway. 272255332Scy */ 273255332Scy#if 0 274255332Scystatic void 275255332Scyrule_printf(struct sbuf *sb, struct rule *rule) 276255332Scy{ 277255332Scy const char *idtype, *protocol; 278255332Scy 279255332Scy switch(rule->r_idtype) { 280255332Scy case RULE_GID: 281255332Scy idtype = GID_STRING; 282145522Sdarrenr break; 283145522Sdarrenr case RULE_UID: 284145522Sdarrenr idtype = UID_STRING; 285145522Sdarrenr break; 286170268Sdarrenr default: 287255332Scy panic("rule_printf: unknown idtype (%d)\n", rule->r_idtype); 288255332Scy } 289255332Scy 290255332Scy switch (rule->r_protocol) { 291255332Scy case RULE_PROTO_TCP: 292255332Scy protocol = TCP_STRING; 293255332Scy break; 294255332Scy case RULE_PROTO_UDP: 295255332Scy protocol = UDP_STRING; 296255332Scy break; 297255332Scy default: 298255332Scy panic("rule_printf: unknown protocol (%d)\n", 299255332Scy rule->r_protocol); 300255332Scy } 301369246Scy sbuf_printf(sb, "%s:%jd:%s:%d", idtype, (intmax_t)rule->r_id, 302255332Scy protocol, rule->r_port); 303369246Scy} 304255332Scy 305255332Scystatic char * 306255332Scyrules_to_string(void) 307255332Scy{ 308255332Scy struct rule *rule; 309255332Scy struct sbuf *sb; 310255332Scy int needcomma; 311255332Scy char *temp; 312255332Scy 313255332Scy sb = sbuf_new_auto(); 314255332Scy needcomma = 0; 315255332Scy mtx_lock(&rule_mtx); 316255332Scy for (rule = TAILQ_FIRST(&rule_head); rule != NULL; 317255332Scy rule = TAILQ_NEXT(rule, r_entries)) { 318255332Scy if (!needcomma) 319255332Scy needcomma = 1; 320255332Scy else 321255332Scy sbuf_printf(sb, ","); 322255332Scy rule_printf(sb, rule); 323255332Scy } 324255332Scy mtx_unlock(&rule_mtx); 325255332Scy sbuf_finish(sb); 326255332Scy temp = strdup(sbuf_data(sb), M_PORTACL); 327255332Scy sbuf_delete(sb); 328255332Scy return (temp); 329255332Scy} 330255332Scy#endif 331170268Sdarrenr 332170268Sdarrenr/* 333170268Sdarrenr * Note: due to races, there is not a single serializable order 334170268Sdarrenr * between parallel calls to the sysctl. 335170268Sdarrenr */ 336170268Sdarrenrstatic int 337170268Sdarrenrsysctl_rules(SYSCTL_HANDLER_ARGS) 338170268Sdarrenr{ 339255332Scy char *string, *copy_string, *new_string; 340255332Scy struct rulehead head, save_head; 341255332Scy int error; 342255332Scy 34353642Sguido new_string = NULL; 344255332Scy if (req->newptr != NULL) { 345255332Scy new_string = malloc(MAC_RULE_STRING_LEN, M_PORTACL, 34680482Sdarrenr M_WAITOK | M_ZERO); 34780482Sdarrenr mtx_lock(&rule_mtx); 34853642Sguido strcpy(new_string, rule_string); 349145522Sdarrenr mtx_unlock(&rule_mtx); 350145522Sdarrenr string = new_string; 35153642Sguido } else 35253642Sguido string = rule_string; 353255332Scy 354145522Sdarrenr error = sysctl_handle_string(oidp, string, MAC_RULE_STRING_LEN, req); 35560850Sdarrenr if (error) 356145522Sdarrenr goto out; 357145522Sdarrenr 358145522Sdarrenr if (req->newptr != NULL) { 359255332Scy copy_string = strdup(string, M_PORTACL); 360255332Scy TAILQ_INIT(&head); 36153642Sguido error = parse_rules(copy_string, &head); 36253642Sguido free(copy_string, M_PORTACL); 36353642Sguido if (error) 36453642Sguido goto out; 36553642Sguido 366255332Scy TAILQ_INIT(&save_head); 36780482Sdarrenr mtx_lock(&rule_mtx); 36880482Sdarrenr TAILQ_CONCAT(&save_head, &rule_head, r_entries); 36953642Sguido TAILQ_CONCAT(&rule_head, &head, r_entries); 37053642Sguido strcpy(rule_string, string); 37153642Sguido mtx_unlock(&rule_mtx); 372255332Scy toast_rules(&save_head); 37353642Sguido } 374255332Scyout: 375255332Scy if (new_string != NULL) 37680482Sdarrenr free(new_string, M_PORTACL); 37780482Sdarrenr return (error); 37880482Sdarrenr} 37980482Sdarrenr 38080482SdarrenrSYSCTL_PROC(_security_mac_portacl, OID_AUTO, rules, 38180482Sdarrenr CTLTYPE_STRING|CTLFLAG_RW, 0, 0, sysctl_rules, "A", "Rules"); 382145522Sdarrenr 38380482Sdarrenrstatic int 38480482Sdarrenrrules_check(struct ucred *cred, int family, int type, u_int16_t port) 38580482Sdarrenr{ 386145522Sdarrenr struct rule *rule; 38780482Sdarrenr int error; 38880482Sdarrenr 38980482Sdarrenr#if 0 39080482Sdarrenr printf("Check requested for euid %d, family %d, type %d, port %d\n", 39180482Sdarrenr cred->cr_uid, family, type, port); 392145522Sdarrenr#endif 393145522Sdarrenr 394145522Sdarrenr if (port > portacl_port_high) 395255332Scy return (0); 396255332Scy 39780482Sdarrenr error = EPERM; 39880482Sdarrenr mtx_lock(&rule_mtx); 39980482Sdarrenr for (rule = TAILQ_FIRST(&rule_head); 40080482Sdarrenr rule != NULL; 401255332Scy rule = TAILQ_NEXT(rule, r_entries)) { 402255332Scy if (type == SOCK_DGRAM && rule->r_protocol != RULE_PROTO_UDP) 403170268Sdarrenr continue; 404255332Scy if (type == SOCK_STREAM && rule->r_protocol != RULE_PROTO_TCP) 405170268Sdarrenr continue; 406255332Scy if (port != rule->r_port) 407170268Sdarrenr continue; 408170268Sdarrenr if (rule->r_idtype == RULE_UID) { 409145522Sdarrenr if (cred->cr_uid == rule->r_id) { 410255332Scy error = 0; 411255332Scy break; 41280482Sdarrenr } 413255332Scy } else if (rule->r_idtype == RULE_GID) { 41480482Sdarrenr if (cred->cr_gid == rule->r_id) { 415255332Scy error = 0; 416255332Scy break; 417255332Scy } else if (groupmember(rule->r_id, cred)) { 41880482Sdarrenr error = 0; 41953642Sguido break; 42080482Sdarrenr } 421255332Scy } else 42253642Sguido panic("rules_check: unknown rule type %d", 423255332Scy rule->r_idtype); 42480482Sdarrenr } 425255332Scy mtx_unlock(&rule_mtx); 426255332Scy 42753642Sguido if (error != 0 && portacl_suser_exempt != 0) 42853642Sguido error = priv_check_cred(cred, PRIV_NETINET_RESERVEDPORT, 0); 429255332Scy 430255332Scy return (error); 431255332Scy} 432255332Scy 433255332Scy/* 43453642Sguido * Note, this only limits the ability to explicitly bind a port, it 43553642Sguido * doesn't limit implicitly bound ports for outgoing connections where 436255332Scy * the source port is left up to the IP stack to determine automatically. 437145522Sdarrenr */ 438145522Sdarrenrstatic int 439255332Scysocket_check_bind(struct ucred *cred, struct socket *so, 440145522Sdarrenr struct label *solabel, struct sockaddr *sa) 44153642Sguido{ 44253642Sguido struct sockaddr_in *sin; 443255332Scy struct inpcb *inp; 44453642Sguido int family, type; 44553642Sguido u_int16_t port; 446255332Scy 447255332Scy /* Only run if we are enabled. */ 448145522Sdarrenr if (portacl_enabled == 0) 44953642Sguido return (0); 45053642Sguido 45153642Sguido /* Only interested in IPv4 and IPv6 sockets. */ 452170268Sdarrenr if (so->so_proto->pr_domain->dom_family != PF_INET && 453255332Scy so->so_proto->pr_domain->dom_family != PF_INET6) 454173931Sdarrenr return (0); 455170268Sdarrenr 456170268Sdarrenr /* Currently, we don't attempt to deal with SOCK_RAW, etc. */ 457170268Sdarrenr if (so->so_type != SOCK_DGRAM && 458170268Sdarrenr so->so_type != SOCK_STREAM) 459170268Sdarrenr return (0); 460170268Sdarrenr 461170268Sdarrenr /* Reject addresses we don't understand; fail closed. */ 462255332Scy if (sa->sa_family != AF_INET && sa->sa_family != AF_INET6) 463255332Scy return (EINVAL); 464255332Scy 465255332Scy family = so->so_proto->pr_domain->dom_family; 46653642Sguido type = so->so_type; 467255332Scy sin = (struct sockaddr_in *) sa; 468255332Scy port = ntohs(sin->sin_port); 469369272Scy 470145522Sdarrenr /* 47160850Sdarrenr * Sockets are frequently bound with a specific IP address but a port 47280482Sdarrenr * number of '0' to request automatic port allocation. This is often 473145522Sdarrenr * desirable as long as IP_PORTRANGELOW isn't set, which might permit 474145522Sdarrenr * automatic allocation of a "privileged" port. The autoport exempt 475145522Sdarrenr * flag exempts port 0 allocation from rule checking as long as a low 47653642Sguido * port isn't required. 47753642Sguido */ 478255332Scy if (portacl_autoport_exempt && port == 0) { 47960850Sdarrenr inp = sotoinpcb(so); 48060850Sdarrenr if ((inp->inp_flags & INP_LOWPORT) == 0) 481255332Scy return (0); 482255332Scy } 483255332Scy 484255332Scy return (rules_check(cred, family, type, port)); 485255332Scy} 48653642Sguido 48753642Sguidostatic struct mac_policy_ops portacl_ops = 48853642Sguido{ 489255332Scy .mpo_destroy = destroy, 490255332Scy .mpo_init = init, 491255332Scy .mpo_socket_check_bind = socket_check_bind, 492255332Scy}; 493255332Scy 494255332ScyMAC_POLICY_SET(&portacl_ops, mac_portacl, "TrustedBSD MAC/portacl", 495255332Scy MPC_LOADTIME_FLAG_UNLOADOK, NULL); 496173931SdarrenrMODULE_VERSION(mac_portacl, 1); 497170268Sdarrenr