/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static mod_hash_t *tpc_name_hash; /* hash of cache entries by name */ static kmutex_t tpc_lock; static tsol_tpc_t *tpc_unlab; /* * tnrhc_table and tnrhc_table_v6 are similar to the IP forwarding tables * in organization and search. The tnrhc_table[_v6] is an array of 33/129 * pointers to the 33/129 tnrhc tables indexed by the prefix length. * A largest prefix match search is done by find_rhc and it walks the * tables from the most specific to the least specific table. Table 0 * corresponds to the single entry for 0.0.0.0/0 or ::0/0. */ tnrhc_hash_t *tnrhc_table[TSOL_MASK_TABLE_SIZE]; tnrhc_hash_t *tnrhc_table_v6[TSOL_MASK_TABLE_SIZE_V6]; kmutex_t tnrhc_g_lock; static void tsol_create_i_tmpls(void); static void tsol_create_i_tnrh(const tnaddr_t *); /* List of MLPs on valid on shared addresses */ static tsol_mlp_list_t shared_mlps; /* * Convert length for a mask to the mask. */ static ipaddr_t tsol_plen_to_mask(uint_t masklen) { return (masklen == 0 ? 0 : htonl(IP_HOST_MASK << (IP_ABITS - masklen))); } /* * Convert a prefix length to the mask for that prefix. * Returns the argument bitmask. */ static void tsol_plen_to_mask_v6(uint_t plen, in6_addr_t *bitmask) { uint32_t *ptr; ASSERT(plen <= IPV6_ABITS); ptr = (uint32_t *)bitmask; while (plen >= 32) { *ptr++ = 0xffffffffU; plen -= 32; } if (plen > 0) *ptr++ = htonl(0xffffffff << (32 - plen)); while (ptr < (uint32_t *)(bitmask + 1)) *ptr++ = 0; } boolean_t tnrhc_init_table(tnrhc_hash_t *table[], short prefix_len, int kmflag) { int i; mutex_enter(&tnrhc_g_lock); if (table[prefix_len] == NULL) { table[prefix_len] = (tnrhc_hash_t *) kmem_zalloc(TNRHC_SIZE * sizeof (tnrhc_hash_t), kmflag); if (table[prefix_len] == NULL) { mutex_exit(&tnrhc_g_lock); return (B_FALSE); } for (i = 0; i < TNRHC_SIZE; i++) { mutex_init(&table[prefix_len][i].tnrh_lock, NULL, MUTEX_DEFAULT, 0); } } mutex_exit(&tnrhc_g_lock); return (B_TRUE); } void tcache_init(void) { tnaddr_t address; /* * Note: unable to use mod_hash_create_strhash here, since it's * assymetric. It assumes that the user has allocated exactly * strlen(key) + 1 bytes for the key when inserted, and attempts to * kmem_free that memory on a delete. */ tpc_name_hash = mod_hash_create_extended("tnrhtpc_by_name", 256, mod_hash_null_keydtor, mod_hash_null_valdtor, mod_hash_bystr, NULL, mod_hash_strkey_cmp, KM_SLEEP); mutex_init(&tpc_lock, NULL, MUTEX_DEFAULT, NULL); mutex_init(&tnrhc_g_lock, NULL, MUTEX_DEFAULT, NULL); /* label_init always called before tcache_init */ ASSERT(l_admin_low != NULL && l_admin_high != NULL); /* Initialize the zeroth table prior to loading the 0.0.0.0 entry */ (void) tnrhc_init_table(tnrhc_table, 0, KM_SLEEP); (void) tnrhc_init_table(tnrhc_table_v6, 0, KM_SLEEP); /* * create an internal host template called "_unlab" */ tsol_create_i_tmpls(); /* * create a host entry, 0.0.0.0 = _unlab */ bzero(&address, sizeof (tnaddr_t)); address.ta_family = AF_INET; tsol_create_i_tnrh(&address); /* * create a host entry, ::0 = _unlab */ address.ta_family = AF_INET6; tsol_create_i_tnrh(&address); rw_init(&shared_mlps.mlpl_rwlock, NULL, RW_DEFAULT, NULL); } /* Called only by the TNRHC_RELE macro when the refcount goes to zero. */ void tnrhc_free(tsol_tnrhc_t *tnrhc) { /* * We assert rhc_invalid here to make sure that no new thread could * possibly end up finding this entry. If it could, then the * mutex_destroy would panic. */ DTRACE_PROBE1(tx__tndb__l3__tnrhcfree, tsol_tnrhc_t *, tnrhc); ASSERT(tnrhc->rhc_next == NULL && tnrhc->rhc_invalid); mutex_exit(&tnrhc->rhc_lock); mutex_destroy(&tnrhc->rhc_lock); if (tnrhc->rhc_tpc != NULL) TPC_RELE(tnrhc->rhc_tpc); kmem_free(tnrhc, sizeof (*tnrhc)); } /* Called only by the TPC_RELE macro when the refcount goes to zero. */ void tpc_free(tsol_tpc_t *tpc) { DTRACE_PROBE1(tx__tndb__l3__tpcfree, tsol_tpc_t *, tpc); ASSERT(tpc->tpc_invalid); mutex_exit(&tpc->tpc_lock); mutex_destroy(&tpc->tpc_lock); kmem_free(tpc, sizeof (*tpc)); } /* * Find and hold a reference to a template entry by name. Ignores entries that * are being deleted. */ static tsol_tpc_t * tnrhtp_find(const char *name, mod_hash_t *hash) { mod_hash_val_t hv; tsol_tpc_t *tpc = NULL; mutex_enter(&tpc_lock); if (mod_hash_find(hash, (mod_hash_key_t)name, &hv) == 0) { tpc = (tsol_tpc_t *)hv; if (tpc->tpc_invalid) tpc = NULL; else TPC_HOLD(tpc); } mutex_exit(&tpc_lock); return (tpc); } static int tnrh_delete(const tsol_rhent_t *rhent) { tsol_tnrhc_t *current; tsol_tnrhc_t **prevp; ipaddr_t tmpmask; in6_addr_t tmpmask_v6; tnrhc_hash_t *tnrhc_hash; if (rhent->rh_address.ta_family == AF_INET) { if (rhent->rh_prefix < 0 || rhent->rh_prefix > IP_ABITS) return (EINVAL); if (tnrhc_table[rhent->rh_prefix] == NULL) return (ENOENT); tmpmask = tsol_plen_to_mask(rhent->rh_prefix); tnrhc_hash = &tnrhc_table[rhent->rh_prefix][ TSOL_ADDR_HASH(rhent->rh_address.ta_addr_v4.s_addr & tmpmask, TNRHC_SIZE)]; } else if (rhent->rh_address.ta_family == AF_INET6) { if (rhent->rh_prefix < 0 || rhent->rh_prefix > IPV6_ABITS) return (EINVAL); if (tnrhc_table_v6[rhent->rh_prefix] == NULL) return (ENOENT); tsol_plen_to_mask_v6(rhent->rh_prefix, &tmpmask_v6); tnrhc_hash = &tnrhc_table_v6[rhent->rh_prefix][ TSOL_ADDR_MASK_HASH_V6(rhent->rh_address.ta_addr_v6, tmpmask_v6, TNRHC_SIZE)]; } else { return (EAFNOSUPPORT); } /* search for existing entry */ mutex_enter(&tnrhc_hash->tnrh_lock); prevp = &tnrhc_hash->tnrh_list; while ((current = *prevp) != NULL) { if (TNADDR_EQ(&rhent->rh_address, ¤t->rhc_host)) break; prevp = ¤t->rhc_next; } if (current != NULL) { DTRACE_PROBE(tx__tndb__l2__tnrhdelete_existingrhentry); *prevp = current->rhc_next; mutex_enter(¤t->rhc_lock); current->rhc_next = NULL; current->rhc_invalid = 1; mutex_exit(¤t->rhc_lock); TNRHC_RELE(current); } mutex_exit(&tnrhc_hash->tnrh_lock); return (current == NULL ? ENOENT : 0); } /* * Flush all remote host entries from the database. * * Note that the htable arrays themselves do not have reference counters, so, * unlike the remote host entries, they cannot be freed. */ static void flush_rh_table(tnrhc_hash_t **htable, int nbits) { tnrhc_hash_t *hent, *hend; tsol_tnrhc_t *rhc, *rhnext; while (--nbits >= 0) { if ((hent = htable[nbits]) == NULL) continue; hend = hent + TNRHC_SIZE; while (hent < hend) { /* * List walkers hold this lock during the walk. It * protects tnrh_list and rhc_next. */ mutex_enter(&hent->tnrh_lock); rhnext = hent->tnrh_list; hent->tnrh_list = NULL; mutex_exit(&hent->tnrh_lock); /* * There may still be users of the rhcs at this point, * but not of the list or its next pointer. Thus, the * only thing that would need to be done under a lock * is setting the invalid bit, but that's atomic * anyway, so no locks needed here. */ while ((rhc = rhnext) != NULL) { rhnext = rhc->rhc_next; rhc->rhc_next = NULL; rhc->rhc_invalid = 1; TNRHC_RELE(rhc); } hent++; } } } /* * Load a remote host entry into kernel cache. Create a new one if a matching * entry isn't found, otherwise replace the contents of the previous one by * deleting it and recreating it. (Delete and recreate is used to avoid * allowing other threads to see an unstable data structure.) * * A "matching" entry is the one whose address matches that of the one * being loaded. * * Return 0 for success, error code for failure. */ static int tnrh_hash_add(tsol_tnrhc_t *new, short prefix) { tsol_tnrhc_t **rhp; tsol_tnrhc_t *rh; ipaddr_t tmpmask; in6_addr_t tmpmask_v6; tnrhc_hash_t *tnrhc_hash; /* Find the existing entry, if any, leaving the hash locked */ if (new->rhc_host.ta_family == AF_INET) { if (prefix < 0 || prefix > IP_ABITS) return (EINVAL); if (tnrhc_table[prefix] == NULL && !tnrhc_init_table(tnrhc_table, prefix, KM_NOSLEEP)) return (ENOMEM); tmpmask = tsol_plen_to_mask(prefix); tnrhc_hash = &tnrhc_table[prefix][ TSOL_ADDR_HASH(new->rhc_host.ta_addr_v4.s_addr & tmpmask, TNRHC_SIZE)]; mutex_enter(&tnrhc_hash->tnrh_lock); for (rhp = &tnrhc_hash->tnrh_list; (rh = *rhp) != NULL; rhp = &rh->rhc_next) { ASSERT(rh->rhc_host.ta_family == AF_INET); if (((rh->rhc_host.ta_addr_v4.s_addr ^ new->rhc_host.ta_addr_v4.s_addr) & tmpmask) == 0) break; } } else if (new->rhc_host.ta_family == AF_INET6) { if (prefix < 0 || prefix > IPV6_ABITS) return (EINVAL); if (tnrhc_table_v6[prefix] == NULL && !tnrhc_init_table(tnrhc_table_v6, prefix, KM_NOSLEEP)) return (ENOMEM); tsol_plen_to_mask_v6(prefix, &tmpmask_v6); tnrhc_hash = &tnrhc_table_v6[prefix][ TSOL_ADDR_MASK_HASH_V6(new->rhc_host.ta_addr_v6, tmpmask_v6, TNRHC_SIZE)]; mutex_enter(&tnrhc_hash->tnrh_lock); for (rhp = &tnrhc_hash->tnrh_list; (rh = *rhp) != NULL; rhp = &rh->rhc_next) { ASSERT(rh->rhc_host.ta_family == AF_INET6); if (V6_MASK_EQ_2(rh->rhc_host.ta_addr_v6, tmpmask_v6, new->rhc_host.ta_addr_v6)) break; } } else { return (EAFNOSUPPORT); } /* Clobber the old remote host entry. */ if (rh != NULL) { ASSERT(!rh->rhc_invalid); rh->rhc_invalid = 1; *rhp = rh->rhc_next; rh->rhc_next = NULL; DTRACE_PROBE1(tx__tndb__l2__tnrhhashadd__invalidaterh, tsol_tnrhc_t *, rh); TNRHC_RELE(rh); } TNRHC_HOLD(new); new->rhc_next = tnrhc_hash->tnrh_list; tnrhc_hash->tnrh_list = new; DTRACE_PROBE1(tx__tndb__l2__tnrhhashadd__addedrh, tsol_tnrhc_t *, new); mutex_exit(&tnrhc_hash->tnrh_lock); return (0); } /* * Load a remote host entry into kernel cache. * * Return 0 for success, error code for failure. */ int tnrh_load(const tsol_rhent_t *rhent) { tsol_tnrhc_t *new; tsol_tpc_t *tpc; int status; /* Find and bump the reference count on the named template */ if ((tpc = tnrhtp_find(rhent->rh_template, tpc_name_hash)) == NULL) { return (EINVAL); } ASSERT(tpc->tpc_tp.host_type == UNLABELED || tpc->tpc_tp.host_type == SUN_CIPSO); if ((new = kmem_zalloc(sizeof (*new), KM_NOSLEEP)) == NULL) { TPC_RELE(tpc); return (ENOMEM); } /* Initialize the new entry. */ mutex_init(&new->rhc_lock, NULL, MUTEX_DEFAULT, NULL); new->rhc_host = rhent->rh_address; /* The rhc now owns this tpc reference, so no TPC_RELE past here */ new->rhc_tpc = tpc; /* * tnrh_hash_add handles the tnrh entry ref count for hash * table inclusion. The ref count is incremented and decremented * here to trigger deletion of the new hash table entry in the * event that tnrh_hash_add fails. */ TNRHC_HOLD(new); status = tnrh_hash_add(new, rhent->rh_prefix); TNRHC_RELE(new); return (status); } static int tnrh_get(tsol_rhent_t *rhent) { tsol_tpc_t *tpc; switch (rhent->rh_address.ta_family) { case AF_INET: tpc = find_tpc(&rhent->rh_address.ta_addr_v4, IPV4_VERSION, B_TRUE); break; case AF_INET6: tpc = find_tpc(&rhent->rh_address.ta_addr_v6, IPV6_VERSION, B_TRUE); break; default: return (EINVAL); } if (tpc == NULL) return (ENOENT); DTRACE_PROBE2(tx__tndb__l4__tnrhget__foundtpc, tsol_rhent_t *, rhent, tsol_tpc_t *, tpc); bcopy(tpc->tpc_tp.name, rhent->rh_template, sizeof (rhent->rh_template)); TPC_RELE(tpc); return (0); } static boolean_t template_name_ok(const char *name) { const char *name_end = name + TNTNAMSIZ; while (name < name_end) { if (*name == '\0') break; name++; } return (name < name_end); } static int tnrh(int cmd, void *buf) { int retv; tsol_rhent_t rhent; /* Make sure user has sufficient privilege */ if (cmd != TNDB_GET && (retv = secpolicy_net_config(CRED(), B_FALSE)) != 0) return (set_errno(retv)); /* * Get arguments */ if (cmd != TNDB_FLUSH && copyin(buf, &rhent, sizeof (rhent)) != 0) { DTRACE_PROBE(tx__tndb__l0__tnrhdelete__copyin); return (set_errno(EFAULT)); } switch (cmd) { case TNDB_LOAD: DTRACE_PROBE(tx__tndb__l2__tnrhdelete__tndbload); if (!template_name_ok(rhent.rh_template)) { retv = EINVAL; } else { retv = tnrh_load(&rhent); } break; case TNDB_DELETE: DTRACE_PROBE(tx__tndb__l2__tnrhdelete__tndbdelete); retv = tnrh_delete(&rhent); break; case TNDB_GET: DTRACE_PROBE(tx__tndb__l4__tnrhdelete__tndbget); if (!template_name_ok(rhent.rh_template)) { retv = EINVAL; break; } retv = tnrh_get(&rhent); if (retv != 0) break; /* * Copy out result */ if (copyout(&rhent, buf, sizeof (rhent)) != 0) { DTRACE_PROBE(tx__tndb__l0__tnrhdelete__copyout); retv = EFAULT; } break; case TNDB_FLUSH: DTRACE_PROBE(tx__tndb__l2__tnrhdelete__flush); flush_rh_table(tnrhc_table, TSOL_MASK_TABLE_SIZE); flush_rh_table(tnrhc_table_v6, TSOL_MASK_TABLE_SIZE_V6); break; default: DTRACE_PROBE1(tx__tndb__l0__tnrhdelete__unknowncmd, int, cmd); retv = EOPNOTSUPP; break; } if (retv != 0) return (set_errno(retv)); else return (retv); } static tsol_tpc_t * tnrhtp_create(const tsol_tpent_t *tpent, int kmflags) { tsol_tpc_t *tpc; mod_hash_val_t hv; /* * We intentionally allocate a new entry before taking the lock on the * entire database. */ if ((tpc = kmem_zalloc(sizeof (*tpc), kmflags)) == NULL) return (NULL); mutex_enter(&tpc_lock); if (mod_hash_find(tpc_name_hash, (mod_hash_key_t)tpent->name, &hv) == 0) { tsol_tpc_t *found_tpc = (tsol_tpc_t *)hv; found_tpc->tpc_invalid = 1; (void) mod_hash_destroy(tpc_name_hash, (mod_hash_key_t)tpent->name); TPC_RELE(found_tpc); } mutex_init(&tpc->tpc_lock, NULL, MUTEX_DEFAULT, NULL); /* tsol_tpent_t is the same on LP64 and ILP32 */ bcopy(tpent, &tpc->tpc_tp, sizeof (tpc->tpc_tp)); (void) mod_hash_insert(tpc_name_hash, (mod_hash_key_t)tpc->tpc_tp.name, (mod_hash_val_t)tpc); TPC_HOLD(tpc); mutex_exit(&tpc_lock); return (tpc); } static int tnrhtp_delete(const char *tname) { tsol_tpc_t *tpc; mod_hash_val_t hv; int retv = ENOENT; mutex_enter(&tpc_lock); if (mod_hash_find(tpc_name_hash, (mod_hash_key_t)tname, &hv) == 0) { tpc = (tsol_tpc_t *)hv; ASSERT(!tpc->tpc_invalid); tpc->tpc_invalid = 1; (void) mod_hash_destroy(tpc_name_hash, (mod_hash_key_t)tpc->tpc_tp.name); TPC_RELE(tpc); retv = 0; } mutex_exit(&tpc_lock); return (retv); } /* ARGSUSED */ static uint_t tpc_delete(mod_hash_key_t key, mod_hash_val_t *val, void *arg) { tsol_tpc_t *tpc = (tsol_tpc_t *)val; ASSERT(!tpc->tpc_invalid); tpc->tpc_invalid = 1; TPC_RELE(tpc); return (MH_WALK_CONTINUE); } static void tnrhtp_flush(void) { mutex_enter(&tpc_lock); mod_hash_walk(tpc_name_hash, tpc_delete, NULL); mod_hash_clear(tpc_name_hash); mutex_exit(&tpc_lock); } static int tnrhtp(int cmd, void *buf) { int retv; int type; tsol_tpent_t rhtpent; tsol_tpc_t *tpc; /* Make sure user has sufficient privilege */ if (cmd != TNDB_GET && (retv = secpolicy_net_config(CRED(), B_FALSE)) != 0) return (set_errno(retv)); /* * Get argument. Note that tsol_tpent_t is the same on LP64 and ILP32, * so no special handling is required. */ if (cmd != TNDB_FLUSH) { if (copyin(buf, &rhtpent, sizeof (rhtpent)) != 0) { DTRACE_PROBE(tx__tndb__l0__tnrhtp__copyin); return (set_errno(EFAULT)); } /* * Don't let the user give us a bogus (unterminated) template * name. */ if (!template_name_ok(rhtpent.name)) return (set_errno(EINVAL)); } switch (cmd) { case TNDB_LOAD: DTRACE_PROBE1(tx__tndb__l2__tnrhtp__tndbload, char *, rhtpent.name); type = rhtpent.host_type; if (type != UNLABELED && type != SUN_CIPSO) { retv = EINVAL; break; } if (tnrhtp_create(&rhtpent, KM_NOSLEEP) == NULL) retv = ENOMEM; else retv = 0; break; case TNDB_GET: DTRACE_PROBE1(tx__tndb__l4__tnrhtp__tndbget, char *, rhtpent.name); tpc = tnrhtp_find(rhtpent.name, tpc_name_hash); if (tpc == NULL) { retv = ENOENT; break; } /* Copy out result */ if (copyout(&tpc->tpc_tp, buf, sizeof (tpc->tpc_tp)) != 0) { DTRACE_PROBE(tx__tndb__l0__tnrhtp__copyout); retv = EFAULT; } else { retv = 0; } TPC_RELE(tpc); break; case TNDB_DELETE: DTRACE_PROBE1(tx__tndb__l4__tnrhtp__tndbdelete, char *, rhtpent.name); retv = tnrhtp_delete(rhtpent.name); break; case TNDB_FLUSH: DTRACE_PROBE(tx__tndb__l4__tnrhtp__flush); tnrhtp_flush(); retv = 0; break; default: DTRACE_PROBE1(tx__tndb__l0__tnrhtp__unknowncmd, int, cmd); retv = EOPNOTSUPP; break; } if (retv != 0) return (set_errno(retv)); else return (retv); } /* * MLP entry ordering logic * * There are two loops in this routine. The first loop finds the entry that * either logically follows the new entry to be inserted, or is the entry that * precedes and overlaps the new entry, or is NULL to mean end-of-list. This * is 'tme.' The second loop scans ahead from that point to find any overlap * on the front or back of this new entry. * * For the first loop, we can have the following cases in the list (note that * the port-portmax range is inclusive): * * port portmax * +--------+ * 1: +------+ ................... precedes; skip to next * 2: +------+ ............. overlaps; stop here if same protocol * 3: +------+ ......... overlaps; stop if same or higher protocol * 4: +-------+ .... overlaps or succeeds; stop here * * For the second loop, we can have the following cases (note that we need not * care about other protocol entries at this point, because we're only looking * for overlap, not an insertion point): * * port portmax * +--------+ * 5: +------+ ............. overlaps; stop if same protocol * 6: +------+ ......... overlaps; stop if same protocol * 7: +-------+ .... overlaps; stop if same protocol * 8: +---+ . follows; search is done * * In other words, this second search needs to consider only whether the entry * has a starting port number that's greater than the end point of the new * entry. All others are overlaps. */ static int mlp_add_del(tsol_mlp_list_t *mlpl, zoneid_t zoneid, uint8_t proto, uint16_t port, uint16_t portmax, boolean_t addflag) { int retv; tsol_mlp_entry_t *tme, *tme2, *newent; if (addflag) { if ((newent = kmem_zalloc(sizeof (*newent), KM_NOSLEEP)) == NULL) return (ENOMEM); } else { newent = NULL; } rw_enter(&mlpl->mlpl_rwlock, RW_WRITER); /* * First loop: find logical insertion point or overlap. Table is kept * in order of port number first, and then, within that, by protocol * number. */ for (tme = mlpl->mlpl_first; tme != NULL; tme = tme->mlpe_next) { /* logically next (case 4) */ if (tme->mlpe_mlp.mlp_port > port) break; /* if this is logically next or overlap, then stop (case 3) */ if (tme->mlpe_mlp.mlp_port == port && tme->mlpe_mlp.mlp_ipp >= proto) break; /* earlier or same port sequence; check for overlap (case 2) */ if (tme->mlpe_mlp.mlp_ipp == proto && tme->mlpe_mlp.mlp_port_upper >= port) break; /* otherwise, loop again (case 1) */ } /* Second loop: scan ahead for overlap */ for (tme2 = tme; tme2 != NULL; tme2 = tme2->mlpe_next) { /* check if entry follows; no overlap (case 8) */ if (tme2->mlpe_mlp.mlp_port > portmax) { tme2 = NULL; break; } /* only exact protocol matches at this point (cases 5-7) */ if (tme2->mlpe_mlp.mlp_ipp == proto) break; } retv = 0; if (addflag) { if (tme2 != NULL) { retv = EEXIST; } else { newent->mlpe_zoneid = zoneid; newent->mlpe_mlp.mlp_ipp = proto; newent->mlpe_mlp.mlp_port = port; newent->mlpe_mlp.mlp_port_upper = portmax; newent->mlpe_next = tme; if (tme == NULL) { tme2 = mlpl->mlpl_last; mlpl->mlpl_last = newent; } else { tme2 = tme->mlpe_prev; tme->mlpe_prev = newent; } newent->mlpe_prev = tme2; if (tme2 == NULL) mlpl->mlpl_first = newent; else tme2->mlpe_next = newent; newent = NULL; } } else { if (tme2 == NULL || tme2->mlpe_mlp.mlp_port != port || tme2->mlpe_mlp.mlp_port_upper != portmax) { retv = ENOENT; } else { if ((tme2 = tme->mlpe_prev) == NULL) mlpl->mlpl_first = tme->mlpe_next; else tme2->mlpe_next = tme->mlpe_next; if ((tme2 = tme->mlpe_next) == NULL) mlpl->mlpl_last = tme->mlpe_prev; else tme2->mlpe_prev = tme->mlpe_prev; newent = tme; } } rw_exit(&mlpl->mlpl_rwlock); if (newent != NULL) kmem_free(newent, sizeof (*newent)); return (retv); } /* * Add or remove an MLP entry from the database so that the classifier can find * it. * * Note: port number is in host byte order. */ int tsol_mlp_anon(zone_t *zone, mlp_type_t mlptype, uchar_t proto, uint16_t port, boolean_t addflag) { int retv = 0; if (mlptype == mlptBoth || mlptype == mlptPrivate) retv = mlp_add_del(&zone->zone_mlps, zone->zone_id, proto, port, port, addflag); if ((retv == 0 || !addflag) && (mlptype == mlptBoth || mlptype == mlptShared)) { retv = mlp_add_del(&shared_mlps, zone->zone_id, proto, port, port, addflag); if (retv != 0 && addflag) (void) mlp_add_del(&zone->zone_mlps, zone->zone_id, proto, port, port, B_FALSE); } return (retv); } static void mlp_flush(tsol_mlp_list_t *mlpl, zoneid_t zoneid) { tsol_mlp_entry_t *tme, *tme2, *tmnext; rw_enter(&mlpl->mlpl_rwlock, RW_WRITER); for (tme = mlpl->mlpl_first; tme != NULL; tme = tmnext) { tmnext = tme->mlpe_next; if (zoneid == ALL_ZONES || tme->mlpe_zoneid == zoneid) { if ((tme2 = tme->mlpe_prev) == NULL) mlpl->mlpl_first = tmnext; else tme2->mlpe_next = tmnext; if (tmnext == NULL) mlpl->mlpl_last = tme2; else tmnext->mlpe_prev = tme2; kmem_free(tme, sizeof (*tme)); } } rw_exit(&mlpl->mlpl_rwlock); } /* * Note: user supplies port numbers in host byte order. */ static int tnmlp(int cmd, void *buf) { int retv; tsol_mlpent_t tsme; zone_t *zone; tsol_mlp_list_t *mlpl; tsol_mlp_entry_t *tme; /* Make sure user has sufficient privilege */ if (cmd != TNDB_GET && (retv = secpolicy_net_config(CRED(), B_FALSE)) != 0) return (set_errno(retv)); /* * Get argument. Note that tsol_mlpent_t is the same on LP64 and * ILP32, so no special handling is required. */ if (copyin(buf, &tsme, sizeof (tsme)) != 0) { DTRACE_PROBE(tx__tndb__l0__tnmlp__copyin); return (set_errno(EFAULT)); } /* MLPs on shared IP addresses */ if (tsme.tsme_flags & TSOL_MEF_SHARED) { zone = NULL; mlpl = &shared_mlps; } else { zone = zone_find_by_id(tsme.tsme_zoneid); if (zone == NULL) return (set_errno(EINVAL)); mlpl = &zone->zone_mlps; } if (tsme.tsme_mlp.mlp_port_upper == 0) tsme.tsme_mlp.mlp_port_upper = tsme.tsme_mlp.mlp_port; switch (cmd) { case TNDB_LOAD: DTRACE_PROBE1(tx__tndb__l2__tnmlp__tndbload, tsol_mlpent_t *, &tsme); if (tsme.tsme_mlp.mlp_ipp == 0 || tsme.tsme_mlp.mlp_port == 0 || tsme.tsme_mlp.mlp_port > tsme.tsme_mlp.mlp_port_upper) { retv = EINVAL; break; } retv = mlp_add_del(mlpl, tsme.tsme_zoneid, tsme.tsme_mlp.mlp_ipp, tsme.tsme_mlp.mlp_port, tsme.tsme_mlp.mlp_port_upper, B_TRUE); break; case TNDB_GET: DTRACE_PROBE1(tx__tndb__l2__tnmlp__tndbget, tsol_mlpent_t *, &tsme); /* * Search for the requested element or, failing that, the one * that's logically next in the sequence. */ rw_enter(&mlpl->mlpl_rwlock, RW_READER); for (tme = mlpl->mlpl_first; tme != NULL; tme = tme->mlpe_next) { if (tsme.tsme_zoneid != ALL_ZONES && tme->mlpe_zoneid != tsme.tsme_zoneid) continue; if (tme->mlpe_mlp.mlp_ipp >= tsme.tsme_mlp.mlp_ipp && tme->mlpe_mlp.mlp_port == tsme.tsme_mlp.mlp_port) break; if (tme->mlpe_mlp.mlp_port > tsme.tsme_mlp.mlp_port) break; } if (tme == NULL) { retv = ENOENT; } else { tsme.tsme_zoneid = tme->mlpe_zoneid; tsme.tsme_mlp = tme->mlpe_mlp; retv = 0; } rw_exit(&mlpl->mlpl_rwlock); break; case TNDB_DELETE: DTRACE_PROBE1(tx__tndb__l4__tnmlp__tndbdelete, tsol_mlpent_t *, &tsme); retv = mlp_add_del(mlpl, tsme.tsme_zoneid, tsme.tsme_mlp.mlp_ipp, tsme.tsme_mlp.mlp_port, tsme.tsme_mlp.mlp_port_upper, B_FALSE); break; case TNDB_FLUSH: DTRACE_PROBE1(tx__tndb__l4__tnmlp__tndbflush, tsol_mlpent_t *, &tsme); mlp_flush(mlpl, ALL_ZONES); mlp_flush(&shared_mlps, tsme.tsme_zoneid); retv = 0; break; default: DTRACE_PROBE1(tx__tndb__l0__tnmlp__unknowncmd, int, cmd); retv = EOPNOTSUPP; break; } if (zone != NULL) zone_rele(zone); if (cmd == TNDB_GET && retv == 0) { /* Copy out result */ if (copyout(&tsme, buf, sizeof (tsme)) != 0) { DTRACE_PROBE(tx__tndb__l0__tnmlp__copyout); retv = EFAULT; } } if (retv != 0) return (set_errno(retv)); else return (retv); } /* * Returns a tnrhc matching the addr address. * The returned rhc's refcnt is incremented. */ tsol_tnrhc_t * find_rhc(const void *addr, uchar_t version, boolean_t staleok) { tsol_tnrhc_t *rh = NULL; tsol_tnrhc_t *new; tsol_tpc_t *tpc; tnrhc_hash_t *tnrhc_hash; ipaddr_t tmpmask; in_addr_t *in4 = (in_addr_t *)addr; in6_addr_t *in6 = (in6_addr_t *)addr; in_addr_t tmpin4; in6_addr_t tmpmask6; int i; int prefix; /* * An IPv4-mapped IPv6 address is really an IPv4 address * in IPv6 format. */ if (version == IPV6_VERSION && IN6_IS_ADDR_V4MAPPED(in6)) { IN6_V4MAPPED_TO_IPADDR(in6, tmpin4); version = IPV4_VERSION; in4 = &tmpin4; } /* * Search the tnrh hash table for each prefix length, * starting at longest prefix length, until a matching * rhc entry is found. */ if (version == IPV4_VERSION) { for (i = (TSOL_MASK_TABLE_SIZE - 1); i >= 0; i--) { if ((tnrhc_table[i]) == NULL) continue; tmpmask = tsol_plen_to_mask(i); tnrhc_hash = &tnrhc_table[i][ TSOL_ADDR_HASH(*in4 & tmpmask, TNRHC_SIZE)]; mutex_enter(&tnrhc_hash->tnrh_lock); for (rh = tnrhc_hash->tnrh_list; rh != NULL; rh = rh->rhc_next) { if ((rh->rhc_host.ta_family == AF_INET) && ((rh->rhc_host.ta_addr_v4.s_addr & tmpmask) == (*in4 & tmpmask))) { prefix = i; TNRHC_HOLD(rh); break; } } mutex_exit(&tnrhc_hash->tnrh_lock); if (rh != NULL) break; } if (rh == NULL) DTRACE_PROBE1(tx__tndb__l1__findrhc__norhv4ent, in_addr_t *, in4); } else { for (i = (TSOL_MASK_TABLE_SIZE_V6 - 1); i >= 0; i--) { if ((tnrhc_table_v6[i]) == NULL) continue; tsol_plen_to_mask_v6(i, &tmpmask6); tnrhc_hash = &tnrhc_table_v6[i][ TSOL_ADDR_MASK_HASH_V6(*in6, tmpmask6, TNRHC_SIZE)]; mutex_enter(&tnrhc_hash->tnrh_lock); for (rh = tnrhc_hash->tnrh_list; rh != NULL; rh = rh->rhc_next) { if ((rh->rhc_host.ta_family == AF_INET6) && V6_MASK_EQ_2(rh->rhc_host.ta_addr_v6, tmpmask6, *in6)) { prefix = i; TNRHC_HOLD(rh); break; } } mutex_exit(&tnrhc_hash->tnrh_lock); if (rh != NULL) break; } if (rh == NULL) DTRACE_PROBE1(tx__tndb__l1__findrhc__norhv6ent, in6_addr_t *, in6); } /* * Does the tnrh entry point to a stale template? * This can happen any time the user deletes or modifies * a template that has existing tnrh entries pointing * to it. Try to find a new version of the template. * If there is no template, then just give up. * If the template exists, reload the tnrh entry. */ if (rh != NULL && rh->rhc_tpc->tpc_invalid) { tpc = tnrhtp_find(rh->rhc_tpc->tpc_tp.name, tpc_name_hash); if (tpc == NULL) { if (!staleok) { DTRACE_PROBE2(tx__tndb__l1__findrhc__staletpc, tsol_tnrhc_t *, rh, tsol_tpc_t *, rh->rhc_tpc); TNRHC_RELE(rh); rh = NULL; } } else { ASSERT(tpc->tpc_tp.host_type == UNLABELED || tpc->tpc_tp.host_type == SUN_CIPSO); if ((new = kmem_zalloc(sizeof (*new), KM_NOSLEEP)) == NULL) { DTRACE_PROBE(tx__tndb__l1__findrhc__nomem); TNRHC_RELE(rh); TPC_RELE(tpc); return (NULL); } mutex_init(&new->rhc_lock, NULL, MUTEX_DEFAULT, NULL); new->rhc_host = rh->rhc_host; new->rhc_tpc = tpc; new->rhc_isbcast = rh->rhc_isbcast; new->rhc_local = rh->rhc_local; TNRHC_RELE(rh); rh = new; /* * This function increments the tnrh entry ref count * for the pointer returned to the caller. * tnrh_hash_add increments the tnrh entry ref count * for the pointer in the hash table. */ TNRHC_HOLD(rh); if (tnrh_hash_add(new, prefix) != 0) { TNRHC_RELE(rh); rh = NULL; } } } return (rh); } tsol_tpc_t * find_tpc(const void *addr, uchar_t version, boolean_t staleok) { tsol_tpc_t *tpc; tsol_tnrhc_t *rhc; if ((rhc = find_rhc(addr, version, staleok)) == NULL) return (NULL); tpc = rhc->rhc_tpc; TPC_HOLD(tpc); TNRHC_RELE(rhc); return (tpc); } /* * create an internal template called "_unlab": * * _unlab;\ * host_type = unlabeled;\ * def_label = ADMIN_LOW[ADMIN_LOW];\ * min_sl = ADMIN_LOW;\ * max_sl = ADMIN_HIGH; */ static void tsol_create_i_tmpls(void) { tsol_tpent_t rhtpent; bzero(&rhtpent, sizeof (rhtpent)); /* create _unlab */ (void) strcpy(rhtpent.name, "_unlab"); rhtpent.host_type = UNLABELED; rhtpent.tp_mask_unl = TSOL_MSK_DEF_LABEL | TSOL_MSK_DEF_CL | TSOL_MSK_SL_RANGE_TSOL; rhtpent.tp_gw_sl_range.lower_bound = *label2bslabel(l_admin_low); rhtpent.tp_def_label = rhtpent.tp_gw_sl_range.lower_bound; rhtpent.tp_gw_sl_range.upper_bound = *label2bslabel(l_admin_high); rhtpent.tp_cipso_doi_unl = default_doi; tpc_unlab = tnrhtp_create(&rhtpent, KM_SLEEP); } /* * set up internal host template, called from kernel only. */ static void tsol_create_i_tnrh(const tnaddr_t *sa) { tsol_tnrhc_t *rh, *new; tnrhc_hash_t *tnrhc_hash; /* Allocate a new entry before taking the lock */ new = kmem_zalloc(sizeof (*new), KM_SLEEP); tnrhc_hash = (sa->ta_family == AF_INET) ? &tnrhc_table[0][0] : &tnrhc_table_v6[0][0]; mutex_enter(&tnrhc_hash->tnrh_lock); rh = tnrhc_hash->tnrh_list; if (rh == NULL) { /* We're keeping the new entry. */ rh = new; new = NULL; rh->rhc_host = *sa; mutex_init(&rh->rhc_lock, NULL, MUTEX_DEFAULT, NULL); TNRHC_HOLD(rh); tnrhc_hash->tnrh_list = rh; } /* * Link the entry to internal_unlab */ if (rh->rhc_tpc != tpc_unlab) { if (rh->rhc_tpc != NULL) TPC_RELE(rh->rhc_tpc); rh->rhc_tpc = tpc_unlab; TPC_HOLD(tpc_unlab); } mutex_exit(&tnrhc_hash->tnrh_lock); if (new != NULL) kmem_free(new, sizeof (*new)); } /* * Returns 0 if the port is known to be SLP. Returns next possible port number * (wrapping through 1) if port is MLP on shared or global. Administrator * should not make all ports MLP. If that's done, then we'll just pretend * everything is SLP to avoid looping forever. * * Note: port is in host byte order. */ in_port_t tsol_next_port(zone_t *zone, in_port_t port, int proto, boolean_t upward) { boolean_t loop; tsol_mlp_entry_t *tme; int newport = port; loop = B_FALSE; for (;;) { if (zone != NULL && zone->zone_mlps.mlpl_first != NULL) { rw_enter(&zone->zone_mlps.mlpl_rwlock, RW_READER); for (tme = zone->zone_mlps.mlpl_first; tme != NULL; tme = tme->mlpe_next) { if (proto == tme->mlpe_mlp.mlp_ipp && newport >= tme->mlpe_mlp.mlp_port && newport <= tme->mlpe_mlp.mlp_port_upper) newport = upward ? tme->mlpe_mlp.mlp_port_upper + 1 : tme->mlpe_mlp.mlp_port - 1; } rw_exit(&zone->zone_mlps.mlpl_rwlock); } if (shared_mlps.mlpl_first != NULL) { rw_enter(&shared_mlps.mlpl_rwlock, RW_READER); for (tme = shared_mlps.mlpl_first; tme != NULL; tme = tme->mlpe_next) { if (proto == tme->mlpe_mlp.mlp_ipp && newport >= tme->mlpe_mlp.mlp_port && newport <= tme->mlpe_mlp.mlp_port_upper) newport = upward ? tme->mlpe_mlp.mlp_port_upper + 1 : tme->mlpe_mlp.mlp_port - 1; } rw_exit(&shared_mlps.mlpl_rwlock); } if (newport <= 65535 && newport > 0) break; if (loop) return (0); loop = B_TRUE; newport = upward ? 1 : 65535; } return (newport == port ? 0 : newport); } /* * tsol_mlp_port_type will check if the given (zone, proto, port) is a * multilevel port. If it is, return the type (shared, private, or both), or * indicate that it's single-level. * * Note: port is given in host byte order, not network byte order. */ mlp_type_t tsol_mlp_port_type(zone_t *zone, uchar_t proto, uint16_t port, mlp_type_t mlptype) { tsol_mlp_entry_t *tme; if (mlptype == mlptBoth || mlptype == mlptPrivate) { tme = NULL; if (zone->zone_mlps.mlpl_first != NULL) { rw_enter(&zone->zone_mlps.mlpl_rwlock, RW_READER); for (tme = zone->zone_mlps.mlpl_first; tme != NULL; tme = tme->mlpe_next) { if (proto == tme->mlpe_mlp.mlp_ipp && port >= tme->mlpe_mlp.mlp_port && port <= tme->mlpe_mlp.mlp_port_upper) break; } rw_exit(&zone->zone_mlps.mlpl_rwlock); } if (tme == NULL) { if (mlptype == mlptBoth) mlptype = mlptShared; else if (mlptype == mlptPrivate) mlptype = mlptSingle; } } if (mlptype == mlptBoth || mlptype == mlptShared) { tme = NULL; if (shared_mlps.mlpl_first != NULL) { rw_enter(&shared_mlps.mlpl_rwlock, RW_READER); for (tme = shared_mlps.mlpl_first; tme != NULL; tme = tme->mlpe_next) { if (proto == tme->mlpe_mlp.mlp_ipp && port >= tme->mlpe_mlp.mlp_port && port <= tme->mlpe_mlp.mlp_port_upper) break; } rw_exit(&shared_mlps.mlpl_rwlock); } if (tme == NULL) { if (mlptype == mlptBoth) mlptype = mlptPrivate; else if (mlptype == mlptShared) mlptype = mlptSingle; } } return (mlptype); } /* * tsol_mlp_findzone will check if the given (proto, port) is a multilevel port * on a shared address. If it is, return the owning zone. * * Note: lport is in network byte order, unlike the other MLP functions, * because the callers of this function are all dealing with packets off the * wire. */ zoneid_t tsol_mlp_findzone(uchar_t proto, uint16_t lport) { tsol_mlp_entry_t *tme; zoneid_t zoneid; uint16_t port; if (shared_mlps.mlpl_first == NULL) return (ALL_ZONES); port = ntohs(lport); rw_enter(&shared_mlps.mlpl_rwlock, RW_READER); for (tme = shared_mlps.mlpl_first; tme != NULL; tme = tme->mlpe_next) { if (proto == tme->mlpe_mlp.mlp_ipp && port >= tme->mlpe_mlp.mlp_port && port <= tme->mlpe_mlp.mlp_port_upper) break; } zoneid = tme == NULL ? ALL_ZONES : tme->mlpe_zoneid; rw_exit(&shared_mlps.mlpl_rwlock); return (zoneid); } /* Debug routine */ void tsol_print_label(const blevel_t *blev, const char *name) { const _blevel_impl_t *bli = (const _blevel_impl_t *)blev; /* We really support only sensitivity labels */ cmn_err(CE_NOTE, "%s %x:%x:%08x%08x%08x%08x%08x%08x%08x%08x", name, bli->id, LCLASS(bli), ntohl(bli->_comps.c1), ntohl(bli->_comps.c2), ntohl(bli->_comps.c3), ntohl(bli->_comps.c4), ntohl(bli->_comps.c5), ntohl(bli->_comps.c6), ntohl(bli->_comps.c7), ntohl(bli->_comps.c8)); } /* * Name: labelsys() * * Normal: Routes TSOL syscalls. * * Output: As defined for each TSOL syscall. * Returns ENOSYS for unrecognized calls. */ /* ARGSUSED */ int labelsys(int op, void *a1, void *a2, void *a3, void *a4, void *a5) { switch (op) { case TSOL_SYSLABELING: return (sys_labeling); case TSOL_TNRH: return (tnrh((int)(uintptr_t)a1, a2)); case TSOL_TNRHTP: return (tnrhtp((int)(uintptr_t)a1, a2)); case TSOL_TNMLP: return (tnmlp((int)(uintptr_t)a1, a2)); case TSOL_GETLABEL: return (getlabel((char *)a1, (bslabel_t *)a2)); case TSOL_FGETLABEL: return (fgetlabel((int)(uintptr_t)a1, (bslabel_t *)a2)); default: return (set_errno(ENOSYS)); } /* NOTREACHED */ }