| /* |
| * 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 2009 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| */ |
| |
| /* |
| * IPsec Security Policy Database. |
| * |
| * This module maintains the SPD and provides routines used by ip and ip6 |
| * to apply IPsec policy to inbound and outbound datagrams. |
| */ |
| |
| #include <sys/types.h> |
| #include <sys/stream.h> |
| #include <sys/stropts.h> |
| #include <sys/sysmacros.h> |
| #include <sys/strsubr.h> |
| #include <sys/strsun.h> |
| #include <sys/strlog.h> |
| #include <sys/strsun.h> |
| #include <sys/cmn_err.h> |
| #include <sys/zone.h> |
| |
| #include <sys/systm.h> |
| #include <sys/param.h> |
| #include <sys/kmem.h> |
| #include <sys/ddi.h> |
| |
| #include <sys/crypto/api.h> |
| |
| #include <inet/common.h> |
| #include <inet/mi.h> |
| |
| #include <netinet/ip6.h> |
| #include <netinet/icmp6.h> |
| #include <netinet/udp.h> |
| |
| #include <inet/ip.h> |
| #include <inet/ip6.h> |
| |
| #include <net/pfkeyv2.h> |
| #include <net/pfpolicy.h> |
| #include <inet/sadb.h> |
| #include <inet/ipsec_impl.h> |
| |
| #include <inet/ip_impl.h> /* For IP_MOD_ID */ |
| |
| #include <inet/ipsecah.h> |
| #include <inet/ipsecesp.h> |
| #include <inet/ipdrop.h> |
| #include <inet/ipclassifier.h> |
| #include <inet/iptun.h> |
| #include <inet/iptun/iptun_impl.h> |
| |
| static void ipsec_update_present_flags(ipsec_stack_t *); |
| static ipsec_act_t *ipsec_act_wildcard_expand(ipsec_act_t *, uint_t *, |
| netstack_t *); |
| static mblk_t *ipsec_check_ipsecin_policy(mblk_t *, ipsec_policy_t *, |
| ipha_t *, ip6_t *, uint64_t, ip_recv_attr_t *, netstack_t *); |
| static void ipsec_action_free_table(ipsec_action_t *); |
| static void ipsec_action_reclaim(void *); |
| static void ipsec_action_reclaim_stack(netstack_t *); |
| static void ipsid_init(netstack_t *); |
| static void ipsid_fini(netstack_t *); |
| |
| /* sel_flags values for ipsec_init_inbound_sel(). */ |
| #define SEL_NONE 0x0000 |
| #define SEL_PORT_POLICY 0x0001 |
| #define SEL_IS_ICMP 0x0002 |
| #define SEL_TUNNEL_MODE 0x0004 |
| #define SEL_POST_FRAG 0x0008 |
| |
| /* Return values for ipsec_init_inbound_sel(). */ |
| typedef enum { SELRET_NOMEM, SELRET_BADPKT, SELRET_SUCCESS, SELRET_TUNFRAG} |
| selret_t; |
| |
| static selret_t ipsec_init_inbound_sel(ipsec_selector_t *, mblk_t *, |
| ipha_t *, ip6_t *, uint8_t); |
| |
| static boolean_t ipsec_check_ipsecin_action(ip_recv_attr_t *, mblk_t *, |
| struct ipsec_action_s *, ipha_t *ipha, ip6_t *ip6h, const char **, |
| kstat_named_t **, netstack_t *); |
| static void ipsec_unregister_prov_update(void); |
| static void ipsec_prov_update_callback_stack(uint32_t, void *, netstack_t *); |
| static boolean_t ipsec_compare_action(ipsec_policy_t *, ipsec_policy_t *); |
| static uint32_t selector_hash(ipsec_selector_t *, ipsec_policy_root_t *); |
| static boolean_t ipsec_kstat_init(ipsec_stack_t *); |
| static void ipsec_kstat_destroy(ipsec_stack_t *); |
| static int ipsec_free_tables(ipsec_stack_t *); |
| static int tunnel_compare(const void *, const void *); |
| static void ipsec_freemsg_chain(mblk_t *); |
| static void ip_drop_packet_chain(mblk_t *, boolean_t, ill_t *, |
| struct kstat_named *, ipdropper_t *); |
| static boolean_t ipsec_kstat_init(ipsec_stack_t *); |
| static void ipsec_kstat_destroy(ipsec_stack_t *); |
| static int ipsec_free_tables(ipsec_stack_t *); |
| static int tunnel_compare(const void *, const void *); |
| static void ipsec_freemsg_chain(mblk_t *); |
| |
| /* |
| * Selector hash table is statically sized at module load time. |
| * we default to 251 buckets, which is the largest prime number under 255 |
| */ |
| |
| #define IPSEC_SPDHASH_DEFAULT 251 |
| |
| /* SPD hash-size tunable per tunnel. */ |
| #define TUN_SPDHASH_DEFAULT 5 |
| |
| uint32_t ipsec_spd_hashsize; |
| uint32_t tun_spd_hashsize; |
| |
| #define IPSEC_SEL_NOHASH ((uint32_t)(~0)) |
| |
| /* |
| * Handle global across all stack instances |
| */ |
| static crypto_notify_handle_t prov_update_handle = NULL; |
| |
| static kmem_cache_t *ipsec_action_cache; |
| static kmem_cache_t *ipsec_sel_cache; |
| static kmem_cache_t *ipsec_pol_cache; |
| |
| /* Frag cache prototypes */ |
| static void ipsec_fragcache_clean(ipsec_fragcache_t *, ipsec_stack_t *); |
| static ipsec_fragcache_entry_t *fragcache_delentry(int, |
| ipsec_fragcache_entry_t *, ipsec_fragcache_t *, ipsec_stack_t *); |
| boolean_t ipsec_fragcache_init(ipsec_fragcache_t *); |
| void ipsec_fragcache_uninit(ipsec_fragcache_t *, ipsec_stack_t *ipss); |
| mblk_t *ipsec_fragcache_add(ipsec_fragcache_t *, mblk_t *, mblk_t *, |
| int, ipsec_stack_t *); |
| |
| int ipsec_hdr_pullup_needed = 0; |
| int ipsec_weird_null_inbound_policy = 0; |
| |
| #define ALGBITS_ROUND_DOWN(x, align) (((x)/(align))*(align)) |
| #define ALGBITS_ROUND_UP(x, align) ALGBITS_ROUND_DOWN((x)+(align)-1, align) |
| |
| /* |
| * Inbound traffic should have matching identities for both SA's. |
| */ |
| |
| #define SA_IDS_MATCH(sa1, sa2) \ |
| (((sa1) == NULL) || ((sa2) == NULL) || \ |
| (((sa1)->ipsa_src_cid == (sa2)->ipsa_src_cid) && \ |
| (((sa1)->ipsa_dst_cid == (sa2)->ipsa_dst_cid)))) |
| |
| /* |
| * IPv6 Fragments |
| */ |
| #define IS_V6_FRAGMENT(ipp) (ipp.ipp_fields & IPPF_FRAGHDR) |
| |
| /* |
| * Policy failure messages. |
| */ |
| static char *ipsec_policy_failure_msgs[] = { |
| |
| /* IPSEC_POLICY_NOT_NEEDED */ |
| "%s: Dropping the datagram because the incoming packet " |
| "is %s, but the recipient expects clear; Source %s, " |
| "Destination %s.\n", |
| |
| /* IPSEC_POLICY_MISMATCH */ |
| "%s: Policy Failure for the incoming packet (%s); Source %s, " |
| "Destination %s.\n", |
| |
| /* IPSEC_POLICY_AUTH_NOT_NEEDED */ |
| "%s: Authentication present while not expected in the " |
| "incoming %s packet; Source %s, Destination %s.\n", |
| |
| /* IPSEC_POLICY_ENCR_NOT_NEEDED */ |
| "%s: Encryption present while not expected in the " |
| "incoming %s packet; Source %s, Destination %s.\n", |
| |
| /* IPSEC_POLICY_SE_NOT_NEEDED */ |
| "%s: Self-Encapsulation present while not expected in the " |
| "incoming %s packet; Source %s, Destination %s.\n", |
| }; |
| |
| /* |
| * General overviews: |
| * |
| * Locking: |
| * |
| * All of the system policy structures are protected by a single |
| * rwlock. These structures are threaded in a |
| * fairly complex fashion and are not expected to change on a |
| * regular basis, so this should not cause scaling/contention |
| * problems. As a result, policy checks should (hopefully) be MT-hot. |
| * |
| * Allocation policy: |
| * |
| * We use custom kmem cache types for the various |
| * bits & pieces of the policy data structures. All allocations |
| * use KM_NOSLEEP instead of KM_SLEEP for policy allocation. The |
| * policy table is of potentially unbounded size, so we don't |
| * want to provide a way to hog all system memory with policy |
| * entries.. |
| */ |
| |
| /* Convenient functions for freeing or dropping a b_next linked mblk chain */ |
| |
| /* Free all messages in an mblk chain */ |
| static void |
| ipsec_freemsg_chain(mblk_t *mp) |
| { |
| mblk_t *mpnext; |
| while (mp != NULL) { |
| ASSERT(mp->b_prev == NULL); |
| mpnext = mp->b_next; |
| mp->b_next = NULL; |
| freemsg(mp); |
| mp = mpnext; |
| } |
| } |
| |
| /* |
| * ip_drop all messages in an mblk chain |
| * Can handle a b_next chain of ip_recv_attr_t mblks, or just a b_next chain |
| * of data. |
| */ |
| static void |
| ip_drop_packet_chain(mblk_t *mp, boolean_t inbound, ill_t *ill, |
| struct kstat_named *counter, ipdropper_t *who_called) |
| { |
| mblk_t *mpnext; |
| while (mp != NULL) { |
| ASSERT(mp->b_prev == NULL); |
| mpnext = mp->b_next; |
| mp->b_next = NULL; |
| if (ip_recv_attr_is_mblk(mp)) |
| mp = ip_recv_attr_free_mblk(mp); |
| ip_drop_packet(mp, inbound, ill, counter, who_called); |
| mp = mpnext; |
| } |
| } |
| |
| /* |
| * AVL tree comparison function. |
| * the in-kernel avl assumes unique keys for all objects. |
| * Since sometimes policy will duplicate rules, we may insert |
| * multiple rules with the same rule id, so we need a tie-breaker. |
| */ |
| static int |
| ipsec_policy_cmpbyid(const void *a, const void *b) |
| { |
| const ipsec_policy_t *ipa, *ipb; |
| uint64_t idxa, idxb; |
| |
| ipa = (const ipsec_policy_t *)a; |
| ipb = (const ipsec_policy_t *)b; |
| idxa = ipa->ipsp_index; |
| idxb = ipb->ipsp_index; |
| |
| if (idxa < idxb) |
| return (-1); |
| if (idxa > idxb) |
| return (1); |
| /* |
| * Tie-breaker #1: All installed policy rules have a non-NULL |
| * ipsl_sel (selector set), so an entry with a NULL ipsp_sel is not |
| * actually in-tree but rather a template node being used in |
| * an avl_find query; see ipsec_policy_delete(). This gives us |
| * a placeholder in the ordering just before the first entry with |
| * a key >= the one we're looking for, so we can walk forward from |
| * that point to get the remaining entries with the same id. |
| */ |
| if ((ipa->ipsp_sel == NULL) && (ipb->ipsp_sel != NULL)) |
| return (-1); |
| if ((ipb->ipsp_sel == NULL) && (ipa->ipsp_sel != NULL)) |
| return (1); |
| /* |
| * At most one of the arguments to the comparison should have a |
| * NULL selector pointer; if not, the tree is broken. |
| */ |
| ASSERT(ipa->ipsp_sel != NULL); |
| ASSERT(ipb->ipsp_sel != NULL); |
| /* |
| * Tie-breaker #2: use the virtual address of the policy node |
| * to arbitrarily break ties. Since we use the new tree node in |
| * the avl_find() in ipsec_insert_always, the new node will be |
| * inserted into the tree in the right place in the sequence. |
| */ |
| if (ipa < ipb) |
| return (-1); |
| if (ipa > ipb) |
| return (1); |
| return (0); |
| } |
| |
| /* |
| * Free what ipsec_alloc_table allocated. |
| */ |
| void |
| ipsec_polhead_free_table(ipsec_policy_head_t *iph) |
| { |
| int dir; |
| int i; |
| |
| for (dir = 0; dir < IPSEC_NTYPES; dir++) { |
| ipsec_policy_root_t *ipr = &iph->iph_root[dir]; |
| |
| if (ipr->ipr_hash == NULL) |
| continue; |
| |
| for (i = 0; i < ipr->ipr_nchains; i++) { |
| ASSERT(ipr->ipr_hash[i].hash_head == NULL); |
| } |
| kmem_free(ipr->ipr_hash, ipr->ipr_nchains * |
| sizeof (ipsec_policy_hash_t)); |
| ipr->ipr_hash = NULL; |
| } |
| } |
| |
| void |
| ipsec_polhead_destroy(ipsec_policy_head_t *iph) |
| { |
| int dir; |
| |
| avl_destroy(&iph->iph_rulebyid); |
| rw_destroy(&iph->iph_lock); |
| |
| for (dir = 0; dir < IPSEC_NTYPES; dir++) { |
| ipsec_policy_root_t *ipr = &iph->iph_root[dir]; |
| int chain; |
| |
| for (chain = 0; chain < ipr->ipr_nchains; chain++) |
| mutex_destroy(&(ipr->ipr_hash[chain].hash_lock)); |
| |
| } |
| ipsec_polhead_free_table(iph); |
| } |
| |
| /* |
| * Free the IPsec stack instance. |
| */ |
| /* ARGSUSED */ |
| static void |
| ipsec_stack_fini(netstackid_t stackid, void *arg) |
| { |
| ipsec_stack_t *ipss = (ipsec_stack_t *)arg; |
| void *cookie; |
| ipsec_tun_pol_t *node; |
| netstack_t *ns = ipss->ipsec_netstack; |
| int i; |
| ipsec_algtype_t algtype; |
| |
| ipsec_loader_destroy(ipss); |
| |
| rw_enter(&ipss->ipsec_tunnel_policy_lock, RW_WRITER); |
| /* |
| * It's possible we can just ASSERT() the tree is empty. After all, |
| * we aren't called until IP is ready to unload (and presumably all |
| * tunnels have been unplumbed). But we'll play it safe for now, the |
| * loop will just exit immediately if it's empty. |
| */ |
| cookie = NULL; |
| while ((node = (ipsec_tun_pol_t *) |
| avl_destroy_nodes(&ipss->ipsec_tunnel_policies, |
| &cookie)) != NULL) { |
| ITP_REFRELE(node, ns); |
| } |
| avl_destroy(&ipss->ipsec_tunnel_policies); |
| rw_exit(&ipss->ipsec_tunnel_policy_lock); |
| rw_destroy(&ipss->ipsec_tunnel_policy_lock); |
| |
| ipsec_config_flush(ns); |
| |
| ipsec_kstat_destroy(ipss); |
| |
| ip_drop_unregister(&ipss->ipsec_dropper); |
| |
| ip_drop_unregister(&ipss->ipsec_spd_dropper); |
| ip_drop_destroy(ipss); |
| /* |
| * Globals start with ref == 1 to prevent IPPH_REFRELE() from |
| * attempting to free them, hence they should have 1 now. |
| */ |
| ipsec_polhead_destroy(&ipss->ipsec_system_policy); |
| ASSERT(ipss->ipsec_system_policy.iph_refs == 1); |
| ipsec_polhead_destroy(&ipss->ipsec_inactive_policy); |
| ASSERT(ipss->ipsec_inactive_policy.iph_refs == 1); |
| |
| for (i = 0; i < IPSEC_ACTION_HASH_SIZE; i++) { |
| ipsec_action_free_table(ipss->ipsec_action_hash[i].hash_head); |
| ipss->ipsec_action_hash[i].hash_head = NULL; |
| mutex_destroy(&(ipss->ipsec_action_hash[i].hash_lock)); |
| } |
| |
| for (i = 0; i < ipss->ipsec_spd_hashsize; i++) { |
| ASSERT(ipss->ipsec_sel_hash[i].hash_head == NULL); |
| mutex_destroy(&(ipss->ipsec_sel_hash[i].hash_lock)); |
| } |
| |
| mutex_enter(&ipss->ipsec_alg_lock); |
| for (algtype = 0; algtype < IPSEC_NALGTYPES; algtype ++) { |
| int nalgs = ipss->ipsec_nalgs[algtype]; |
| |
| for (i = 0; i < nalgs; i++) { |
| if (ipss->ipsec_alglists[algtype][i] != NULL) |
| ipsec_alg_unreg(algtype, i, ns); |
| } |
| } |
| mutex_exit(&ipss->ipsec_alg_lock); |
| mutex_destroy(&ipss->ipsec_alg_lock); |
| |
| ipsid_gc(ns); |
| ipsid_fini(ns); |
| |
| (void) ipsec_free_tables(ipss); |
| kmem_free(ipss, sizeof (*ipss)); |
| } |
| |
| void |
| ipsec_policy_g_destroy(void) |
| { |
| kmem_cache_destroy(ipsec_action_cache); |
| kmem_cache_destroy(ipsec_sel_cache); |
| kmem_cache_destroy(ipsec_pol_cache); |
| |
| ipsec_unregister_prov_update(); |
| |
| netstack_unregister(NS_IPSEC); |
| } |
| |
| |
| /* |
| * Free what ipsec_alloc_tables allocated. |
| * Called when table allocation fails to free the table. |
| */ |
| static int |
| ipsec_free_tables(ipsec_stack_t *ipss) |
| { |
| int i; |
| |
| if (ipss->ipsec_sel_hash != NULL) { |
| for (i = 0; i < ipss->ipsec_spd_hashsize; i++) { |
| ASSERT(ipss->ipsec_sel_hash[i].hash_head == NULL); |
| } |
| kmem_free(ipss->ipsec_sel_hash, ipss->ipsec_spd_hashsize * |
| sizeof (*ipss->ipsec_sel_hash)); |
| ipss->ipsec_sel_hash = NULL; |
| ipss->ipsec_spd_hashsize = 0; |
| } |
| ipsec_polhead_free_table(&ipss->ipsec_system_policy); |
| ipsec_polhead_free_table(&ipss->ipsec_inactive_policy); |
| |
| return (ENOMEM); |
| } |
| |
| /* |
| * Attempt to allocate the tables in a single policy head. |
| * Return nonzero on failure after cleaning up any work in progress. |
| */ |
| int |
| ipsec_alloc_table(ipsec_policy_head_t *iph, int nchains, int kmflag, |
| boolean_t global_cleanup, netstack_t *ns) |
| { |
| int dir; |
| |
| for (dir = 0; dir < IPSEC_NTYPES; dir++) { |
| ipsec_policy_root_t *ipr = &iph->iph_root[dir]; |
| |
| ipr->ipr_nchains = nchains; |
| ipr->ipr_hash = kmem_zalloc(nchains * |
| sizeof (ipsec_policy_hash_t), kmflag); |
| if (ipr->ipr_hash == NULL) |
| return (global_cleanup ? |
| ipsec_free_tables(ns->netstack_ipsec) : |
| ENOMEM); |
| } |
| return (0); |
| } |
| |
| /* |
| * Attempt to allocate the various tables. Return nonzero on failure |
| * after cleaning up any work in progress. |
| */ |
| static int |
| ipsec_alloc_tables(int kmflag, netstack_t *ns) |
| { |
| int error; |
| ipsec_stack_t *ipss = ns->netstack_ipsec; |
| |
| error = ipsec_alloc_table(&ipss->ipsec_system_policy, |
| ipss->ipsec_spd_hashsize, kmflag, B_TRUE, ns); |
| if (error != 0) |
| return (error); |
| |
| error = ipsec_alloc_table(&ipss->ipsec_inactive_policy, |
| ipss->ipsec_spd_hashsize, kmflag, B_TRUE, ns); |
| if (error != 0) |
| return (error); |
| |
| ipss->ipsec_sel_hash = kmem_zalloc(ipss->ipsec_spd_hashsize * |
| sizeof (*ipss->ipsec_sel_hash), kmflag); |
| |
| if (ipss->ipsec_sel_hash == NULL) |
| return (ipsec_free_tables(ipss)); |
| |
| return (0); |
| } |
| |
| /* |
| * After table allocation, initialize a policy head. |
| */ |
| void |
| ipsec_polhead_init(ipsec_policy_head_t *iph, int nchains) |
| { |
| int dir, chain; |
| |
| rw_init(&iph->iph_lock, NULL, RW_DEFAULT, NULL); |
| avl_create(&iph->iph_rulebyid, ipsec_policy_cmpbyid, |
| sizeof (ipsec_policy_t), offsetof(ipsec_policy_t, ipsp_byid)); |
| |
| for (dir = 0; dir < IPSEC_NTYPES; dir++) { |
| ipsec_policy_root_t *ipr = &iph->iph_root[dir]; |
| ipr->ipr_nchains = nchains; |
| |
| for (chain = 0; chain < nchains; chain++) { |
| mutex_init(&(ipr->ipr_hash[chain].hash_lock), |
| NULL, MUTEX_DEFAULT, NULL); |
| } |
| } |
| } |
| |
| static boolean_t |
| ipsec_kstat_init(ipsec_stack_t *ipss) |
| { |
| ipss->ipsec_ksp = kstat_create_netstack("ip", 0, "ipsec_stat", "net", |
| KSTAT_TYPE_NAMED, sizeof (ipsec_kstats_t) / sizeof (kstat_named_t), |
| KSTAT_FLAG_PERSISTENT, ipss->ipsec_netstack->netstack_stackid); |
| |
| if (ipss->ipsec_ksp == NULL || ipss->ipsec_ksp->ks_data == NULL) |
| return (B_FALSE); |
| |
| ipss->ipsec_kstats = ipss->ipsec_ksp->ks_data; |
| |
| #define KI(x) kstat_named_init(&ipss->ipsec_kstats->x, #x, KSTAT_DATA_UINT64) |
| KI(esp_stat_in_requests); |
| KI(esp_stat_in_discards); |
| KI(esp_stat_lookup_failure); |
| KI(ah_stat_in_requests); |
| KI(ah_stat_in_discards); |
| KI(ah_stat_lookup_failure); |
| KI(sadb_acquire_maxpackets); |
| KI(sadb_acquire_qhiwater); |
| #undef KI |
| |
| kstat_install(ipss->ipsec_ksp); |
| return (B_TRUE); |
| } |
| |
| static void |
| ipsec_kstat_destroy(ipsec_stack_t *ipss) |
| { |
| kstat_delete_netstack(ipss->ipsec_ksp, |
| ipss->ipsec_netstack->netstack_stackid); |
| ipss->ipsec_kstats = NULL; |
| |
| } |
| |
| /* |
| * Initialize the IPsec stack instance. |
| */ |
| /* ARGSUSED */ |
| static void * |
| ipsec_stack_init(netstackid_t stackid, netstack_t *ns) |
| { |
| ipsec_stack_t *ipss; |
| int i; |
| |
| ipss = (ipsec_stack_t *)kmem_zalloc(sizeof (*ipss), KM_SLEEP); |
| ipss->ipsec_netstack = ns; |
| |
| /* |
| * FIXME: netstack_ipsec is used by some of the routines we call |
| * below, but it isn't set until this routine returns. |
| * Either we introduce optional xxx_stack_alloc() functions |
| * that will be called by the netstack framework before xxx_stack_init, |
| * or we switch spd.c and sadb.c to operate on ipsec_stack_t |
| * (latter has some include file order issues for sadb.h, but makes |
| * sense if we merge some of the ipsec related stack_t's together. |
| */ |
| ns->netstack_ipsec = ipss; |
| |
| /* |
| * Make two attempts to allocate policy hash tables; try it at |
| * the "preferred" size (may be set in /etc/system) first, |
| * then fall back to the default size. |
| */ |
| ipss->ipsec_spd_hashsize = (ipsec_spd_hashsize == 0) ? |
| IPSEC_SPDHASH_DEFAULT : ipsec_spd_hashsize; |
| |
| if (ipsec_alloc_tables(KM_NOSLEEP, ns) != 0) { |
| cmn_err(CE_WARN, |
| "Unable to allocate %d entry IPsec policy hash table", |
| ipss->ipsec_spd_hashsize); |
| ipss->ipsec_spd_hashsize = IPSEC_SPDHASH_DEFAULT; |
| cmn_err(CE_WARN, "Falling back to %d entries", |
| ipss->ipsec_spd_hashsize); |
| (void) ipsec_alloc_tables(KM_SLEEP, ns); |
| } |
| |
| /* Just set a default for tunnels. */ |
| ipss->ipsec_tun_spd_hashsize = (tun_spd_hashsize == 0) ? |
| TUN_SPDHASH_DEFAULT : tun_spd_hashsize; |
| |
| ipsid_init(ns); |
| /* |
| * Globals need ref == 1 to prevent IPPH_REFRELE() from attempting |
| * to free them. |
| */ |
| ipss->ipsec_system_policy.iph_refs = 1; |
| ipss->ipsec_inactive_policy.iph_refs = 1; |
| ipsec_polhead_init(&ipss->ipsec_system_policy, |
| ipss->ipsec_spd_hashsize); |
| ipsec_polhead_init(&ipss->ipsec_inactive_policy, |
| ipss->ipsec_spd_hashsize); |
| rw_init(&ipss->ipsec_tunnel_policy_lock, NULL, RW_DEFAULT, NULL); |
| avl_create(&ipss->ipsec_tunnel_policies, tunnel_compare, |
| sizeof (ipsec_tun_pol_t), 0); |
| |
| ipss->ipsec_next_policy_index = 1; |
| |
| rw_init(&ipss->ipsec_system_policy.iph_lock, NULL, RW_DEFAULT, NULL); |
| rw_init(&ipss->ipsec_inactive_policy.iph_lock, NULL, RW_DEFAULT, NULL); |
| |
| for (i = 0; i < IPSEC_ACTION_HASH_SIZE; i++) |
| mutex_init(&(ipss->ipsec_action_hash[i].hash_lock), |
| NULL, MUTEX_DEFAULT, NULL); |
| |
| for (i = 0; i < ipss->ipsec_spd_hashsize; i++) |
| mutex_init(&(ipss->ipsec_sel_hash[i].hash_lock), |
| NULL, MUTEX_DEFAULT, NULL); |
| |
| mutex_init(&ipss->ipsec_alg_lock, NULL, MUTEX_DEFAULT, NULL); |
| for (i = 0; i < IPSEC_NALGTYPES; i++) { |
| ipss->ipsec_nalgs[i] = 0; |
| } |
| |
| ip_drop_init(ipss); |
| ip_drop_register(&ipss->ipsec_spd_dropper, "IPsec SPD"); |
| |
| /* IP's IPsec code calls the packet dropper */ |
| ip_drop_register(&ipss->ipsec_dropper, "IP IPsec processing"); |
| |
| (void) ipsec_kstat_init(ipss); |
| |
| ipsec_loader_init(ipss); |
| ipsec_loader_start(ipss); |
| |
| return (ipss); |
| } |
| |
| /* Global across all stack instances */ |
| void |
| ipsec_policy_g_init(void) |
| { |
| ipsec_action_cache = kmem_cache_create("ipsec_actions", |
| sizeof (ipsec_action_t), _POINTER_ALIGNMENT, NULL, NULL, |
| ipsec_action_reclaim, NULL, NULL, 0); |
| ipsec_sel_cache = kmem_cache_create("ipsec_selectors", |
| sizeof (ipsec_sel_t), _POINTER_ALIGNMENT, NULL, NULL, |
| NULL, NULL, NULL, 0); |
| ipsec_pol_cache = kmem_cache_create("ipsec_policy", |
| sizeof (ipsec_policy_t), _POINTER_ALIGNMENT, NULL, NULL, |
| NULL, NULL, NULL, 0); |
| |
| /* |
| * We want to be informed each time a stack is created or |
| * destroyed in the kernel, so we can maintain the |
| * set of ipsec_stack_t's. |
| */ |
| netstack_register(NS_IPSEC, ipsec_stack_init, NULL, ipsec_stack_fini); |
| } |
| |
| /* |
| * Sort algorithm lists. |
| * |
| * I may need to split this based on |
| * authentication/encryption, and I may wish to have an administrator |
| * configure this list. Hold on to some NDD variables... |
| * |
| * XXX For now, sort on minimum key size (GAG!). While minimum key size is |
| * not the ideal metric, it's the only quantifiable measure available. |
| * We need a better metric for sorting algorithms by preference. |
| */ |
| static void |
| alg_insert_sortlist(enum ipsec_algtype at, uint8_t algid, netstack_t *ns) |
| { |
| ipsec_stack_t *ipss = ns->netstack_ipsec; |
| ipsec_alginfo_t *ai = ipss->ipsec_alglists[at][algid]; |
| uint8_t holder, swap; |
| uint_t i; |
| uint_t count = ipss->ipsec_nalgs[at]; |
| ASSERT(ai != NULL); |
| ASSERT(algid == ai->alg_id); |
| |
| ASSERT(MUTEX_HELD(&ipss->ipsec_alg_lock)); |
| |
| holder = algid; |
| |
| for (i = 0; i < count - 1; i++) { |
| ipsec_alginfo_t *alt; |
| |
| alt = ipss->ipsec_alglists[at][ipss->ipsec_sortlist[at][i]]; |
| /* |
| * If you want to give precedence to newly added algs, |
| * add the = in the > comparison. |
| */ |
| if ((holder != algid) || (ai->alg_minbits > alt->alg_minbits)) { |
| /* Swap sortlist[i] and holder. */ |
| swap = ipss->ipsec_sortlist[at][i]; |
| ipss->ipsec_sortlist[at][i] = holder; |
| holder = swap; |
| ai = alt; |
| } /* Else just continue. */ |
| } |
| |
| /* Store holder in last slot. */ |
| ipss->ipsec_sortlist[at][i] = holder; |
| } |
| |
| /* |
| * Remove an algorithm from a sorted algorithm list. |
| * This should be considerably easier, even with complex sorting. |
| */ |
| static void |
| alg_remove_sortlist(enum ipsec_algtype at, uint8_t algid, netstack_t *ns) |
| { |
| boolean_t copyback = B_FALSE; |
| int i; |
| ipsec_stack_t *ipss = ns->netstack_ipsec; |
| int newcount = ipss->ipsec_nalgs[at]; |
| |
| ASSERT(MUTEX_HELD(&ipss->ipsec_alg_lock)); |
| |
| for (i = 0; i <= newcount; i++) { |
| if (copyback) { |
| ipss->ipsec_sortlist[at][i-1] = |
| ipss->ipsec_sortlist[at][i]; |
| } else if (ipss->ipsec_sortlist[at][i] == algid) { |
| copyback = B_TRUE; |
| } |
| } |
| } |
| |
| /* |
| * Add the specified algorithm to the algorithm tables. |
| * Must be called while holding the algorithm table writer lock. |
| */ |
| void |
| ipsec_alg_reg(ipsec_algtype_t algtype, ipsec_alginfo_t *alg, netstack_t *ns) |
| { |
| ipsec_stack_t *ipss = ns->netstack_ipsec; |
| |
| ASSERT(MUTEX_HELD(&ipss->ipsec_alg_lock)); |
| |
| ASSERT(ipss->ipsec_alglists[algtype][alg->alg_id] == NULL); |
| ipsec_alg_fix_min_max(alg, algtype, ns); |
| ipss->ipsec_alglists[algtype][alg->alg_id] = alg; |
| |
| ipss->ipsec_nalgs[algtype]++; |
| alg_insert_sortlist(algtype, alg->alg_id, ns); |
| } |
| |
| /* |
| * Remove the specified algorithm from the algorithm tables. |
| * Must be called while holding the algorithm table writer lock. |
| */ |
| void |
| ipsec_alg_unreg(ipsec_algtype_t algtype, uint8_t algid, netstack_t *ns) |
| { |
| ipsec_stack_t *ipss = ns->netstack_ipsec; |
| |
| ASSERT(MUTEX_HELD(&ipss->ipsec_alg_lock)); |
| |
| ASSERT(ipss->ipsec_alglists[algtype][algid] != NULL); |
| ipsec_alg_free(ipss->ipsec_alglists[algtype][algid]); |
| ipss->ipsec_alglists[algtype][algid] = NULL; |
| |
| ipss->ipsec_nalgs[algtype]--; |
| alg_remove_sortlist(algtype, algid, ns); |
| } |
| |
| /* |
| * Hooks for spdsock to get a grip on system policy. |
| */ |
| |
| ipsec_policy_head_t * |
| ipsec_system_policy(netstack_t *ns) |
| { |
| ipsec_stack_t *ipss = ns->netstack_ipsec; |
| ipsec_policy_head_t *h = &ipss->ipsec_system_policy; |
| |
| IPPH_REFHOLD(h); |
| return (h); |
| } |
| |
| ipsec_policy_head_t * |
| ipsec_inactive_policy(netstack_t *ns) |
| { |
| ipsec_stack_t *ipss = ns->netstack_ipsec; |
| ipsec_policy_head_t *h = &ipss->ipsec_inactive_policy; |
| |
| IPPH_REFHOLD(h); |
| return (h); |
| } |
| |
| /* |
| * Lock inactive policy, then active policy, then exchange policy root |
| * pointers. |
| */ |
| void |
| ipsec_swap_policy(ipsec_policy_head_t *active, ipsec_policy_head_t *inactive, |
| netstack_t *ns) |
| { |
| int af, dir; |
| avl_tree_t r1, r2; |
| |
| rw_enter(&inactive->iph_lock, RW_WRITER); |
| rw_enter(&active->iph_lock, RW_WRITER); |
| |
| r1 = active->iph_rulebyid; |
| r2 = inactive->iph_rulebyid; |
| active->iph_rulebyid = r2; |
| inactive->iph_rulebyid = r1; |
| |
| for (dir = 0; dir < IPSEC_NTYPES; dir++) { |
| ipsec_policy_hash_t *h1, *h2; |
| |
| h1 = active->iph_root[dir].ipr_hash; |
| h2 = inactive->iph_root[dir].ipr_hash; |
| active->iph_root[dir].ipr_hash = h2; |
| inactive->iph_root[dir].ipr_hash = h1; |
| |
| for (af = 0; af < IPSEC_NAF; af++) { |
| ipsec_policy_t *t1, *t2; |
| |
| t1 = active->iph_root[dir].ipr_nonhash[af]; |
| t2 = inactive->iph_root[dir].ipr_nonhash[af]; |
| active->iph_root[dir].ipr_nonhash[af] = t2; |
| inactive->iph_root[dir].ipr_nonhash[af] = t1; |
| if (t1 != NULL) { |
| t1->ipsp_hash.hash_pp = |
| &(inactive->iph_root[dir].ipr_nonhash[af]); |
| } |
| if (t2 != NULL) { |
| t2->ipsp_hash.hash_pp = |
| &(active->iph_root[dir].ipr_nonhash[af]); |
| } |
| |
| } |
| } |
| active->iph_gen++; |
| inactive->iph_gen++; |
| ipsec_update_present_flags(ns->netstack_ipsec); |
| rw_exit(&active->iph_lock); |
| rw_exit(&inactive->iph_lock); |
| } |
| |
| /* |
| * Swap global policy primary/secondary. |
| */ |
| void |
| ipsec_swap_global_policy(netstack_t *ns) |
| { |
| ipsec_stack_t *ipss = ns->netstack_ipsec; |
| |
| ipsec_swap_policy(&ipss->ipsec_system_policy, |
| &ipss->ipsec_inactive_policy, ns); |
| } |
| |
| /* |
| * Clone one policy rule.. |
| */ |
| static ipsec_policy_t * |
| ipsec_copy_policy(const ipsec_policy_t *src) |
| { |
| ipsec_policy_t *dst = kmem_cache_alloc(ipsec_pol_cache, KM_NOSLEEP); |
| |
| if (dst == NULL) |
| return (NULL); |
| |
| /* |
| * Adjust refcounts of cloned state. |
| */ |
| IPACT_REFHOLD(src->ipsp_act); |
| src->ipsp_sel->ipsl_refs++; |
| |
| HASH_NULL(dst, ipsp_hash); |
| dst->ipsp_netstack = src->ipsp_netstack; |
| dst->ipsp_refs = 1; |
| dst->ipsp_sel = src->ipsp_sel; |
| dst->ipsp_act = src->ipsp_act; |
| dst->ipsp_prio = src->ipsp_prio; |
| dst->ipsp_index = src->ipsp_index; |
| |
| return (dst); |
| } |
| |
| void |
| ipsec_insert_always(avl_tree_t *tree, void *new_node) |
| { |
| void *node; |
| avl_index_t where; |
| |
| node = avl_find(tree, new_node, &where); |
| ASSERT(node == NULL); |
| avl_insert(tree, new_node, where); |
| } |
| |
| |
| static int |
| ipsec_copy_chain(ipsec_policy_head_t *dph, ipsec_policy_t *src, |
| ipsec_policy_t **dstp) |
| { |
| for (; src != NULL; src = src->ipsp_hash.hash_next) { |
| ipsec_policy_t *dst = ipsec_copy_policy(src); |
| if (dst == NULL) |
| return (ENOMEM); |
| |
| HASHLIST_INSERT(dst, ipsp_hash, *dstp); |
| ipsec_insert_always(&dph->iph_rulebyid, dst); |
| } |
| return (0); |
| } |
| |
| |
| |
| /* |
| * Make one policy head look exactly like another. |
| * |
| * As with ipsec_swap_policy, we lock the destination policy head first, then |
| * the source policy head. Note that we only need to read-lock the source |
| * policy head as we are not changing it. |
| */ |
| int |
| ipsec_copy_polhead(ipsec_policy_head_t *sph, ipsec_policy_head_t *dph, |
| netstack_t *ns) |
| { |
| int af, dir, chain, nchains; |
| |
| rw_enter(&dph->iph_lock, RW_WRITER); |
| |
| ipsec_polhead_flush(dph, ns); |
| |
| rw_enter(&sph->iph_lock, RW_READER); |
| |
| for (dir = 0; dir < IPSEC_NTYPES; dir++) { |
| ipsec_policy_root_t *dpr = &dph->iph_root[dir]; |
| ipsec_policy_root_t *spr = &sph->iph_root[dir]; |
| nchains = dpr->ipr_nchains; |
| |
| ASSERT(dpr->ipr_nchains == spr->ipr_nchains); |
| |
| for (af = 0; af < IPSEC_NAF; af++) { |
| if (ipsec_copy_chain(dph, spr->ipr_nonhash[af], |
| &dpr->ipr_nonhash[af])) |
| goto abort_copy; |
| } |
| |
| for (chain = 0; chain < nchains; chain++) { |
| if (ipsec_copy_chain(dph, |
| spr->ipr_hash[chain].hash_head, |
| &dpr->ipr_hash[chain].hash_head)) |
| goto abort_copy; |
| } |
| } |
| |
| dph->iph_gen++; |
| |
| rw_exit(&sph->iph_lock); |
| rw_exit(&dph->iph_lock); |
| return (0); |
| |
| abort_copy: |
| ipsec_polhead_flush(dph, ns); |
| rw_exit(&sph->iph_lock); |
| rw_exit(&dph->iph_lock); |
| return (ENOMEM); |
| } |
| |
| /* |
| * Clone currently active policy to the inactive policy list. |
| */ |
| int |
| ipsec_clone_system_policy(netstack_t *ns) |
| { |
| ipsec_stack_t *ipss = ns->netstack_ipsec; |
| |
| return (ipsec_copy_polhead(&ipss->ipsec_system_policy, |
| &ipss->ipsec_inactive_policy, ns)); |
| } |
| |
| /* |
| * Extract the string from ipsec_policy_failure_msgs[type] and |
| * log it. |
| * |
| */ |
| void |
| ipsec_log_policy_failure(int type, char *func_name, ipha_t *ipha, ip6_t *ip6h, |
| boolean_t secure, netstack_t *ns) |
| { |
| char sbuf[INET6_ADDRSTRLEN]; |
| char dbuf[INET6_ADDRSTRLEN]; |
| char *s; |
| char *d; |
| ipsec_stack_t *ipss = ns->netstack_ipsec; |
| |
| ASSERT((ipha == NULL && ip6h != NULL) || |
| (ip6h == NULL && ipha != NULL)); |
| |
| if (ipha != NULL) { |
| s = inet_ntop(AF_INET, &ipha->ipha_src, sbuf, sizeof (sbuf)); |
| d = inet_ntop(AF_INET, &ipha->ipha_dst, dbuf, sizeof (dbuf)); |
| } else { |
| s = inet_ntop(AF_INET6, &ip6h->ip6_src, sbuf, sizeof (sbuf)); |
| d = inet_ntop(AF_INET6, &ip6h->ip6_dst, dbuf, sizeof (dbuf)); |
| |
| } |
| |
| /* Always bump the policy failure counter. */ |
| ipss->ipsec_policy_failure_count[type]++; |
| |
| ipsec_rl_strlog(ns, IP_MOD_ID, 0, 0, SL_ERROR|SL_WARN|SL_CONSOLE, |
| ipsec_policy_failure_msgs[type], func_name, |
| (secure ? "secure" : "not secure"), s, d); |
| } |
| |
| /* |
| * Rate-limiting front-end to strlog() for AH and ESP. Uses the ndd variables |
| * in /dev/ip and the same rate-limiting clock so that there's a single |
| * knob to turn to throttle the rate of messages. |
| */ |
| void |
| ipsec_rl_strlog(netstack_t *ns, short mid, short sid, char level, ushort_t sl, |
| char *fmt, ...) |
| { |
| va_list adx; |
| hrtime_t current = gethrtime(); |
| ip_stack_t *ipst = ns->netstack_ip; |
| ipsec_stack_t *ipss = ns->netstack_ipsec; |
| |
| sl |= SL_CONSOLE; |
| /* |
| * Throttle logging to stop syslog from being swamped. If variable |
| * 'ipsec_policy_log_interval' is zero, don't log any messages at |
| * all, otherwise log only one message every 'ipsec_policy_log_interval' |
| * msec. Convert interval (in msec) to hrtime (in nsec). |
| */ |
| |
| if (ipst->ips_ipsec_policy_log_interval) { |
| if (ipss->ipsec_policy_failure_last + |
| ((hrtime_t)ipst->ips_ipsec_policy_log_interval * |
| (hrtime_t)1000000) <= current) { |
| va_start(adx, fmt); |
| (void) vstrlog(mid, sid, level, sl, fmt, adx); |
| va_end(adx); |
| ipss->ipsec_policy_failure_last = current; |
| } |
| } |
| } |
| |
| void |
| ipsec_config_flush(netstack_t *ns) |
| { |
| ipsec_stack_t *ipss = ns->netstack_ipsec; |
| |
| rw_enter(&ipss->ipsec_system_policy.iph_lock, RW_WRITER); |
| ipsec_polhead_flush(&ipss->ipsec_system_policy, ns); |
| ipss->ipsec_next_policy_index = 1; |
| rw_exit(&ipss->ipsec_system_policy.iph_lock); |
| ipsec_action_reclaim_stack(ns); |
| } |
| |
| /* |
| * Clip a policy's min/max keybits vs. the capabilities of the |
| * algorithm. |
| */ |
| static void |
| act_alg_adjust(uint_t algtype, uint_t algid, |
| uint16_t *minbits, uint16_t *maxbits, netstack_t *ns) |
| { |
| ipsec_stack_t *ipss = ns->netstack_ipsec; |
| ipsec_alginfo_t *algp = ipss->ipsec_alglists[algtype][algid]; |
| |
| if (algp != NULL) { |
| /* |
| * If passed-in minbits is zero, we assume the caller trusts |
| * us with setting the minimum key size. We pick the |
| * algorithms DEFAULT key size for the minimum in this case. |
| */ |
| if (*minbits == 0) { |
| *minbits = algp->alg_default_bits; |
| ASSERT(*minbits >= algp->alg_minbits); |
| } else { |
| *minbits = MAX(MIN(*minbits, algp->alg_maxbits), |
| algp->alg_minbits); |
| } |
| if (*maxbits == 0) |
| *maxbits = algp->alg_maxbits; |
| else |
| *maxbits = MIN(MAX(*maxbits, algp->alg_minbits), |
| algp->alg_maxbits); |
| ASSERT(*minbits <= *maxbits); |
| } else { |
| *minbits = 0; |
| *maxbits = 0; |
| } |
| } |
| |
| /* |
| * Check an action's requested algorithms against the algorithms currently |
| * loaded in the system. |
| */ |
| boolean_t |
| ipsec_check_action(ipsec_act_t *act, int *diag, netstack_t *ns) |
| { |
| ipsec_prot_t *ipp; |
| ipsec_stack_t *ipss = ns->netstack_ipsec; |
| |
| ipp = &act->ipa_apply; |
| |
| if (ipp->ipp_use_ah && |
| ipss->ipsec_alglists[IPSEC_ALG_AUTH][ipp->ipp_auth_alg] == NULL) { |
| *diag = SPD_DIAGNOSTIC_UNSUPP_AH_ALG; |
| return (B_FALSE); |
| } |
| if (ipp->ipp_use_espa && |
| ipss->ipsec_alglists[IPSEC_ALG_AUTH][ipp->ipp_esp_auth_alg] == |
| NULL) { |
| *diag = SPD_DIAGNOSTIC_UNSUPP_ESP_AUTH_ALG; |
| return (B_FALSE); |
| } |
| if (ipp->ipp_use_esp && |
| ipss->ipsec_alglists[IPSEC_ALG_ENCR][ipp->ipp_encr_alg] == NULL) { |
| *diag = SPD_DIAGNOSTIC_UNSUPP_ESP_ENCR_ALG; |
| return (B_FALSE); |
| } |
| |
| act_alg_adjust(IPSEC_ALG_AUTH, ipp->ipp_auth_alg, |
| &ipp->ipp_ah_minbits, &ipp->ipp_ah_maxbits, ns); |
| act_alg_adjust(IPSEC_ALG_AUTH, ipp->ipp_esp_auth_alg, |
| &ipp->ipp_espa_minbits, &ipp->ipp_espa_maxbits, ns); |
| act_alg_adjust(IPSEC_ALG_ENCR, ipp->ipp_encr_alg, |
| &ipp->ipp_espe_minbits, &ipp->ipp_espe_maxbits, ns); |
| |
| if (ipp->ipp_ah_minbits > ipp->ipp_ah_maxbits) { |
| *diag = SPD_DIAGNOSTIC_UNSUPP_AH_KEYSIZE; |
| return (B_FALSE); |
| } |
| if (ipp->ipp_espa_minbits > ipp->ipp_espa_maxbits) { |
| *diag = SPD_DIAGNOSTIC_UNSUPP_ESP_AUTH_KEYSIZE; |
| return (B_FALSE); |
| } |
| if (ipp->ipp_espe_minbits > ipp->ipp_espe_maxbits) { |
| *diag = SPD_DIAGNOSTIC_UNSUPP_ESP_ENCR_KEYSIZE; |
| return (B_FALSE); |
| } |
| /* TODO: sanity check lifetimes */ |
| return (B_TRUE); |
| } |
| |
| /* |
| * Set up a single action during wildcard expansion.. |
| */ |
| static void |
| ipsec_setup_act(ipsec_act_t *outact, ipsec_act_t *act, |
| uint_t auth_alg, uint_t encr_alg, uint_t eauth_alg, netstack_t *ns) |
| { |
| ipsec_prot_t *ipp; |
| |
| *outact = *act; |
| ipp = &outact->ipa_apply; |
| ipp->ipp_auth_alg = (uint8_t)auth_alg; |
| ipp->ipp_encr_alg = (uint8_t)encr_alg; |
| ipp->ipp_esp_auth_alg = (uint8_t)eauth_alg; |
| |
| act_alg_adjust(IPSEC_ALG_AUTH, auth_alg, |
| &ipp->ipp_ah_minbits, &ipp->ipp_ah_maxbits, ns); |
| act_alg_adjust(IPSEC_ALG_AUTH, eauth_alg, |
| &ipp->ipp_espa_minbits, &ipp->ipp_espa_maxbits, ns); |
| act_alg_adjust(IPSEC_ALG_ENCR, encr_alg, |
| &ipp->ipp_espe_minbits, &ipp->ipp_espe_maxbits, ns); |
| } |
| |
| /* |
| * combinatoric expansion time: expand a wildcarded action into an |
| * array of wildcarded actions; we return the exploded action list, |
| * and return a count in *nact (output only). |
| */ |
| static ipsec_act_t * |
| ipsec_act_wildcard_expand(ipsec_act_t *act, uint_t *nact, netstack_t *ns) |
| { |
| boolean_t use_ah, use_esp, use_espa; |
| boolean_t wild_auth, wild_encr, wild_eauth; |
| uint_t auth_alg, auth_idx, auth_min, auth_max; |
| uint_t eauth_alg, eauth_idx, eauth_min, eauth_max; |
| uint_t encr_alg, encr_idx, encr_min, encr_max; |
| uint_t action_count, ai; |
| ipsec_act_t *outact; |
| ipsec_stack_t *ipss = ns->netstack_ipsec; |
| |
| if (act->ipa_type != IPSEC_ACT_APPLY) { |
| outact = kmem_alloc(sizeof (*act), KM_NOSLEEP); |
| *nact = 1; |
| if (outact != NULL) |
| bcopy(act, outact, sizeof (*act)); |
| return (outact); |
| } |
| /* |
| * compute the combinatoric explosion.. |
| * |
| * we assume a request for encr if esp_req is PREF_REQUIRED |
| * we assume a request for ah auth if ah_req is PREF_REQUIRED. |
| * we assume a request for esp auth if !ah and esp_req is PREF_REQUIRED |
| */ |
| |
| use_ah = act->ipa_apply.ipp_use_ah; |
| use_esp = act->ipa_apply.ipp_use_esp; |
| use_espa = act->ipa_apply.ipp_use_espa; |
| auth_alg = act->ipa_apply.ipp_auth_alg; |
| eauth_alg = act->ipa_apply.ipp_esp_auth_alg; |
| encr_alg = act->ipa_apply.ipp_encr_alg; |
| |
| wild_auth = use_ah && (auth_alg == 0); |
| wild_eauth = use_espa && (eauth_alg == 0); |
| wild_encr = use_esp && (encr_alg == 0); |
| |
| action_count = 1; |
| auth_min = auth_max = auth_alg; |
| eauth_min = eauth_max = eauth_alg; |
| encr_min = encr_max = encr_alg; |
| |
| /* |
| * set up for explosion.. for each dimension, expand output |
| * size by the explosion factor. |
| * |
| * Don't include the "any" algorithms, if defined, as no |
| * kernel policies should be set for these algorithms. |
| */ |
| |
| #define SET_EXP_MINMAX(type, wild, alg, min, max, ipss) \ |
| if (wild) { \ |
| int nalgs = ipss->ipsec_nalgs[type]; \ |
| if (ipss->ipsec_alglists[type][alg] != NULL) \ |
| nalgs--; \ |
| action_count *= nalgs; \ |
| min = 0; \ |
| max = ipss->ipsec_nalgs[type] - 1; \ |
| } |
| |
| SET_EXP_MINMAX(IPSEC_ALG_AUTH, wild_auth, SADB_AALG_NONE, |
| auth_min, auth_max, ipss); |
| SET_EXP_MINMAX(IPSEC_ALG_AUTH, wild_eauth, SADB_AALG_NONE, |
| eauth_min, eauth_max, ipss); |
| SET_EXP_MINMAX(IPSEC_ALG_ENCR, wild_encr, SADB_EALG_NONE, |
| encr_min, encr_max, ipss); |
| |
| #undef SET_EXP_MINMAX |
| |
| /* |
| * ok, allocate the whole mess.. |
| */ |
| |
| outact = kmem_alloc(sizeof (*outact) * action_count, KM_NOSLEEP); |
| if (outact == NULL) |
| return (NULL); |
| |
| /* |
| * Now compute all combinations. Note that non-wildcarded |
| * dimensions just get a single value from auth_min, while |
| * wildcarded dimensions indirect through the sortlist. |
| * |
| * We do encryption outermost since, at this time, there's |
| * greater difference in security and performance between |
| * encryption algorithms vs. authentication algorithms. |
| */ |
| |
| ai = 0; |
| |
| #define WHICH_ALG(type, wild, idx, ipss) \ |
| ((wild)?(ipss->ipsec_sortlist[type][idx]):(idx)) |
| |
| for (encr_idx = encr_min; encr_idx <= encr_max; encr_idx++) { |
| encr_alg = WHICH_ALG(IPSEC_ALG_ENCR, wild_encr, encr_idx, ipss); |
| if (wild_encr && encr_alg == SADB_EALG_NONE) |
| continue; |
| for (auth_idx = auth_min; auth_idx <= auth_max; auth_idx++) { |
| auth_alg = WHICH_ALG(IPSEC_ALG_AUTH, wild_auth, |
| auth_idx, ipss); |
| if (wild_auth && auth_alg == SADB_AALG_NONE) |
| continue; |
| for (eauth_idx = eauth_min; eauth_idx <= eauth_max; |
| eauth_idx++) { |
| eauth_alg = WHICH_ALG(IPSEC_ALG_AUTH, |
| wild_eauth, eauth_idx, ipss); |
| if (wild_eauth && eauth_alg == SADB_AALG_NONE) |
| continue; |
| |
| ipsec_setup_act(&outact[ai], act, |
| auth_alg, encr_alg, eauth_alg, ns); |
| ai++; |
| } |
| } |
| } |
| |
| #undef WHICH_ALG |
| |
| ASSERT(ai == action_count); |
| *nact = action_count; |
| return (outact); |
| } |
| |
| /* |
| * Extract the parts of an ipsec_prot_t from an old-style ipsec_req_t. |
| */ |
| static void |
| ipsec_prot_from_req(const ipsec_req_t *req, ipsec_prot_t *ipp) |
| { |
| bzero(ipp, sizeof (*ipp)); |
| /* |
| * ipp_use_* are bitfields. Look at "!!" in the following as a |
| * "boolean canonicalization" operator. |
| */ |
| ipp->ipp_use_ah = !!(req->ipsr_ah_req & IPSEC_PREF_REQUIRED); |
| ipp->ipp_use_esp = !!(req->ipsr_esp_req & IPSEC_PREF_REQUIRED); |
| ipp->ipp_use_espa = !!(req->ipsr_esp_auth_alg); |
| ipp->ipp_use_se = !!(req->ipsr_self_encap_req & IPSEC_PREF_REQUIRED); |
| ipp->ipp_use_unique = !!((req->ipsr_ah_req|req->ipsr_esp_req) & |
| IPSEC_PREF_UNIQUE); |
| ipp->ipp_encr_alg = req->ipsr_esp_alg; |
| /* |
| * SADB_AALG_ANY is a placeholder to distinguish "any" from |
| * "none" above. If auth is required, as determined above, |
| * SADB_AALG_ANY becomes 0, which is the representation |
| * of "any" and "none" in PF_KEY v2. |
| */ |
| ipp->ipp_auth_alg = (req->ipsr_auth_alg != SADB_AALG_ANY) ? |
| req->ipsr_auth_alg : 0; |
| ipp->ipp_esp_auth_alg = (req->ipsr_esp_auth_alg != SADB_AALG_ANY) ? |
| req->ipsr_esp_auth_alg : 0; |
| } |
| |
| /* |
| * Extract a new-style action from a request. |
| */ |
| void |
| ipsec_actvec_from_req(const ipsec_req_t *req, ipsec_act_t **actp, uint_t *nactp, |
| netstack_t *ns) |
| { |
| struct ipsec_act act; |
| |
| bzero(&act, sizeof (act)); |
| if ((req->ipsr_ah_req & IPSEC_PREF_NEVER) && |
| (req->ipsr_esp_req & IPSEC_PREF_NEVER)) { |
| act.ipa_type = IPSEC_ACT_BYPASS; |
| } else { |
| act.ipa_type = IPSEC_ACT_APPLY; |
| ipsec_prot_from_req(req, &act.ipa_apply); |
| } |
| *actp = ipsec_act_wildcard_expand(&act, nactp, ns); |
| } |
| |
| /* |
| * Convert a new-style "prot" back to an ipsec_req_t (more backwards compat). |
| * We assume caller has already zero'ed *req for us. |
| */ |
| static int |
| ipsec_req_from_prot(ipsec_prot_t *ipp, ipsec_req_t *req) |
| { |
| req->ipsr_esp_alg = ipp->ipp_encr_alg; |
| req->ipsr_auth_alg = ipp->ipp_auth_alg; |
| req->ipsr_esp_auth_alg = ipp->ipp_esp_auth_alg; |
| |
| if (ipp->ipp_use_unique) { |
| req->ipsr_ah_req |= IPSEC_PREF_UNIQUE; |
| req->ipsr_esp_req |= IPSEC_PREF_UNIQUE; |
| } |
| if (ipp->ipp_use_se) |
| req->ipsr_self_encap_req |= IPSEC_PREF_REQUIRED; |
| if (ipp->ipp_use_ah) |
| req->ipsr_ah_req |= IPSEC_PREF_REQUIRED; |
| if (ipp->ipp_use_esp) |
| req->ipsr_esp_req |= IPSEC_PREF_REQUIRED; |
| return (sizeof (*req)); |
| } |
| |
| /* |
| * Convert a new-style action back to an ipsec_req_t (more backwards compat). |
| * We assume caller has already zero'ed *req for us. |
| */ |
| static int |
| ipsec_req_from_act(ipsec_action_t *ap, ipsec_req_t *req) |
| { |
| switch (ap->ipa_act.ipa_type) { |
| case IPSEC_ACT_BYPASS: |
| req->ipsr_ah_req = IPSEC_PREF_NEVER; |
| req->ipsr_esp_req = IPSEC_PREF_NEVER; |
| return (sizeof (*req)); |
| case IPSEC_ACT_APPLY: |
| return (ipsec_req_from_prot(&ap->ipa_act.ipa_apply, req)); |
| } |
| return (sizeof (*req)); |
| } |
| |
| /* |
| * Convert a new-style action back to an ipsec_req_t (more backwards compat). |
| * We assume caller has already zero'ed *req for us. |
| */ |
| int |
| ipsec_req_from_head(ipsec_policy_head_t *ph, ipsec_req_t *req, int af) |
| { |
| ipsec_policy_t *p; |
| |
| /* |
| * FULL-PERSOCK: consult hash table, too? |
| */ |
| for (p = ph->iph_root[IPSEC_INBOUND].ipr_nonhash[af]; |
| p != NULL; |
| p = p->ipsp_hash.hash_next) { |
| if ((p->ipsp_sel->ipsl_key.ipsl_valid & IPSL_WILDCARD) == 0) |
| return (ipsec_req_from_act(p->ipsp_act, req)); |
| } |
| return (sizeof (*req)); |
| } |
| |
| /* |
| * Based on per-socket or latched policy, convert to an appropriate |
| * IP_SEC_OPT ipsec_req_t for the socket option; return size so we can |
| * be tail-called from ip. |
| */ |
| int |
| ipsec_req_from_conn(conn_t *connp, ipsec_req_t *req, int af) |
| { |
| ipsec_latch_t *ipl; |
| int rv = sizeof (ipsec_req_t); |
| |
| bzero(req, sizeof (*req)); |
| |
| ASSERT(MUTEX_HELD(&connp->conn_lock)); |
| ipl = connp->conn_latch; |
| |
| /* |
| * Find appropriate policy. First choice is latched action; |
| * failing that, see latched policy; failing that, |
| * look at configured policy. |
| */ |
| if (ipl != NULL) { |
| if (connp->conn_latch_in_action != NULL) { |
| rv = ipsec_req_from_act(connp->conn_latch_in_action, |
| req); |
| goto done; |
| } |
| if (connp->conn_latch_in_policy != NULL) { |
| rv = ipsec_req_from_act( |
| connp->conn_latch_in_policy->ipsp_act, req); |
| goto done; |
| } |
| } |
| if (connp->conn_policy != NULL) |
| rv = ipsec_req_from_head(connp->conn_policy, req, af); |
| done: |
| return (rv); |
| } |
| |
| void |
| ipsec_actvec_free(ipsec_act_t *act, uint_t nact) |
| { |
| kmem_free(act, nact * sizeof (*act)); |
| } |
| |
| /* |
| * Consumes a reference to ipsp. |
| */ |
| static mblk_t * |
| ipsec_check_loopback_policy(mblk_t *data_mp, ip_recv_attr_t *ira, |
| ipsec_policy_t *ipsp) |
| { |
| if (!(ira->ira_flags & IRAF_IPSEC_SECURE)) |
| return (data_mp); |
| |
| ASSERT(ira->ira_flags & IRAF_LOOPBACK); |
| |
| IPPOL_REFRELE(ipsp); |
| |
| /* |
| * We should do an actual policy check here. Revisit this |
| * when we revisit the IPsec API. (And pass a conn_t in when we |
| * get there.) |
| */ |
| |
| return (data_mp); |
| } |
| |
| /* |
| * Check that packet's inbound ports & proto match the selectors |
| * expected by the SAs it traversed on the way in. |
| */ |
| static boolean_t |
| ipsec_check_ipsecin_unique(ip_recv_attr_t *ira, const char **reason, |
| kstat_named_t **counter, uint64_t pkt_unique, netstack_t *ns) |
| { |
| uint64_t ah_mask, esp_mask; |
| ipsa_t *ah_assoc; |
| ipsa_t *esp_assoc; |
| ipsec_stack_t *ipss = ns->netstack_ipsec; |
| |
| ASSERT(ira->ira_flags & IRAF_IPSEC_SECURE); |
| ASSERT(!(ira->ira_flags & IRAF_LOOPBACK)); |
| |
| ah_assoc = ira->ira_ipsec_ah_sa; |
| esp_assoc = ira->ira_ipsec_esp_sa; |
| ASSERT((ah_assoc != NULL) || (esp_assoc != NULL)); |
| |
| ah_mask = (ah_assoc != NULL) ? ah_assoc->ipsa_unique_mask : 0; |
| esp_mask = (esp_assoc != NULL) ? esp_assoc->ipsa_unique_mask : 0; |
| |
| if ((ah_mask == 0) && (esp_mask == 0)) |
| return (B_TRUE); |
| |
| /* |
| * The pkt_unique check will also check for tunnel mode on the SA |
| * vs. the tunneled_packet boolean. "Be liberal in what you receive" |
| * should not apply in this case. ;) |
| */ |
| |
| if (ah_mask != 0 && |
| ah_assoc->ipsa_unique_id != (pkt_unique & ah_mask)) { |
| *reason = "AH inner header mismatch"; |
| *counter = DROPPER(ipss, ipds_spd_ah_innermismatch); |
| return (B_FALSE); |
| } |
| if (esp_mask != 0 && |
| esp_assoc->ipsa_unique_id != (pkt_unique & esp_mask)) { |
| *reason = "ESP inner header mismatch"; |
| *counter = DROPPER(ipss, ipds_spd_esp_innermismatch); |
| return (B_FALSE); |
| } |
| return (B_TRUE); |
| } |
| |
| static boolean_t |
| ipsec_check_ipsecin_action(ip_recv_attr_t *ira, mblk_t *mp, ipsec_action_t *ap, |
| ipha_t *ipha, ip6_t *ip6h, const char **reason, kstat_named_t **counter, |
| netstack_t *ns) |
| { |
| boolean_t ret = B_TRUE; |
| ipsec_prot_t *ipp; |
| ipsa_t *ah_assoc; |
| ipsa_t *esp_assoc; |
| boolean_t decaps; |
| ipsec_stack_t *ipss = ns->netstack_ipsec; |
| |
| ASSERT((ipha == NULL && ip6h != NULL) || |
| (ip6h == NULL && ipha != NULL)); |
| |
| if (ira->ira_flags & IRAF_LOOPBACK) { |
| /* |
| * Besides accepting pointer-equivalent actions, we also |
| * accept any ICMP errors we generated for ourselves, |
| * regardless of policy. If we do not wish to make this |
| * assumption in the future, check here, and where |
| * IXAF_TRUSTED_ICMP is initialized in ip.c and ip6.c. |
| */ |
| if (ap == ira->ira_ipsec_action || |
| (ira->ira_flags & IRAF_TRUSTED_ICMP)) |
| return (B_TRUE); |
| |
| /* Deep compare necessary here?? */ |
| *counter = DROPPER(ipss, ipds_spd_loopback_mismatch); |
| *reason = "loopback policy mismatch"; |
| return (B_FALSE); |
| } |
| ASSERT(!(ira->ira_flags & IRAF_TRUSTED_ICMP)); |
| ASSERT(ira->ira_flags & IRAF_IPSEC_SECURE); |
| |
| ah_assoc = ira->ira_ipsec_ah_sa; |
| esp_assoc = ira->ira_ipsec_esp_sa; |
| |
| decaps = (ira->ira_flags & IRAF_IPSEC_DECAPS); |
| |
| switch (ap->ipa_act.ipa_type) { |
| case IPSEC_ACT_DISCARD: |
| case IPSEC_ACT_REJECT: |
| /* Should "fail hard" */ |
| *counter = DROPPER(ipss, ipds_spd_explicit); |
| *reason = "blocked by policy"; |
| return (B_FALSE); |
| |
| case IPSEC_ACT_BYPASS: |
| case IPSEC_ACT_CLEAR: |
| *counter = DROPPER(ipss, ipds_spd_got_secure); |
| *reason = "expected clear, got protected"; |
| return (B_FALSE); |
| |
| case IPSEC_ACT_APPLY: |
| ipp = &ap->ipa_act.ipa_apply; |
| /* |
| * As of now we do the simple checks of whether |
| * the datagram has gone through the required IPSEC |
| * protocol constraints or not. We might have more |
| * in the future like sensitive levels, key bits, etc. |
| * If it fails the constraints, check whether we would |
| * have accepted this if it had come in clear. |
| */ |
| if (ipp->ipp_use_ah) { |
| if (ah_assoc == NULL) { |
| ret = ipsec_inbound_accept_clear(mp, ipha, |
| ip6h); |
| *counter = DROPPER(ipss, ipds_spd_got_clear); |
| *reason = "unprotected not accepted"; |
| break; |
| } |
| ASSERT(ah_assoc != NULL); |
| ASSERT(ipp->ipp_auth_alg != 0); |
| |
| if (ah_assoc->ipsa_auth_alg != |
| ipp->ipp_auth_alg) { |
| *counter = DROPPER(ipss, ipds_spd_bad_ahalg); |
| *reason = "unacceptable ah alg"; |
| ret = B_FALSE; |
| break; |
| } |
| } else if (ah_assoc != NULL) { |
| /* |
| * Don't allow this. Check IPSEC NOTE above |
| * ip_fanout_proto(). |
| */ |
| *counter = DROPPER(ipss, ipds_spd_got_ah); |
| *reason = "unexpected AH"; |
| ret = B_FALSE; |
| break; |
| } |
| if (ipp->ipp_use_esp) { |
| if (esp_assoc == NULL) { |
| ret = ipsec_inbound_accept_clear(mp, ipha, |
| ip6h); |
| *counter = DROPPER(ipss, ipds_spd_got_clear); |
| *reason = "unprotected not accepted"; |
| break; |
| } |
| ASSERT(esp_assoc != NULL); |
| ASSERT(ipp->ipp_encr_alg != 0); |
| |
| if (esp_assoc->ipsa_encr_alg != |
| ipp->ipp_encr_alg) { |
| *counter = DROPPER(ipss, ipds_spd_bad_espealg); |
| *reason = "unacceptable esp alg"; |
| ret = B_FALSE; |
| break; |
| } |
| /* |
| * If the client does not need authentication, |
| * we don't verify the alogrithm. |
| */ |
| if (ipp->ipp_use_espa) { |
| if (esp_assoc->ipsa_auth_alg != |
| ipp->ipp_esp_auth_alg) { |
| *counter = DROPPER(ipss, |
| ipds_spd_bad_espaalg); |
| *reason = "unacceptable esp auth alg"; |
| ret = B_FALSE; |
| break; |
| } |
| } |
| } else if (esp_assoc != NULL) { |
| /* |
| * Don't allow this. Check IPSEC NOTE above |
| * ip_fanout_proto(). |
| */ |
| *counter = DROPPER(ipss, ipds_spd_got_esp); |
| *reason = "unexpected ESP"; |
| ret = B_FALSE; |
| break; |
| } |
| if (ipp->ipp_use_se) { |
| if (!decaps) { |
| ret = ipsec_inbound_accept_clear(mp, ipha, |
| ip6h); |
| if (!ret) { |
| /* XXX mutant? */ |
| *counter = DROPPER(ipss, |
| ipds_spd_bad_selfencap); |
| *reason = "self encap not found"; |
| break; |
| } |
| } |
| } else if (decaps) { |
| /* |
| * XXX If the packet comes in tunneled and the |
| * recipient does not expect it to be tunneled, it |
| * is okay. But we drop to be consistent with the |
| * other cases. |
| */ |
| *counter = DROPPER(ipss, ipds_spd_got_selfencap); |
| *reason = "unexpected self encap"; |
| ret = B_FALSE; |
| break; |
| } |
| if (ira->ira_ipsec_action != NULL) { |
| /* |
| * This can happen if we do a double policy-check on |
| * a packet |
| * XXX XXX should fix this case! |
| */ |
| IPACT_REFRELE(ira->ira_ipsec_action); |
| } |
| ASSERT(ira->ira_flags & IRAF_IPSEC_SECURE); |
| ASSERT(ira->ira_ipsec_action == NULL); |
| IPACT_REFHOLD(ap); |
| ira->ira_ipsec_action = ap; |
| break; /* from switch */ |
| } |
| return (ret); |
| } |
| |
| static boolean_t |
| spd_match_inbound_ids(ipsec_latch_t *ipl, ipsa_t *sa) |
| { |
| ASSERT(ipl->ipl_ids_latched == B_TRUE); |
| return ipsid_equal(ipl->ipl_remote_cid, sa->ipsa_src_cid) && |
| ipsid_equal(ipl->ipl_local_cid, sa->ipsa_dst_cid); |
| } |
| |
| /* |
| * Takes a latched conn and an inbound packet and returns a unique_id suitable |
| * for SA comparisons. Most of the time we will copy from the conn_t, but |
| * there are cases when the conn_t is latched but it has wildcard selectors, |
| * and then we need to fallback to scooping them out of the packet. |
| * |
| * Assume we'll never have 0 with a conn_t present, so use 0 as a failure. We |
| * can get away with this because we only have non-zero ports/proto for |
| * latched conn_ts. |
| * |
| * Ideal candidate for an "inline" keyword, as we're JUST convoluted enough |
| * to not be a nice macro. |
| */ |
| static uint64_t |
| conn_to_unique(conn_t *connp, mblk_t *data_mp, ipha_t *ipha, ip6_t *ip6h) |
| { |
| ipsec_selector_t sel; |
| uint8_t ulp = connp->conn_proto; |
| |
| ASSERT(connp->conn_latch_in_policy != NULL); |
| |
| if ((ulp == IPPROTO_TCP || ulp == IPPROTO_UDP || ulp == IPPROTO_SCTP) && |
| (connp->conn_fport == 0 || connp->conn_lport == 0)) { |
| /* Slow path - we gotta grab from the packet. */ |
| if (ipsec_init_inbound_sel(&sel, data_mp, ipha, ip6h, |
| SEL_NONE) != SELRET_SUCCESS) { |
| /* Failure -> have caller free packet with ENOMEM. */ |
| return (0); |
| } |
| return (SA_UNIQUE_ID(sel.ips_remote_port, sel.ips_local_port, |
| sel.ips_protocol, 0)); |
| } |
| |
| #ifdef DEBUG_NOT_UNTIL_6478464 |
| if (ipsec_init_inbound_sel(&sel, data_mp, ipha, ip6h, SEL_NONE) == |
| SELRET_SUCCESS) { |
| ASSERT(sel.ips_local_port == connp->conn_lport); |
| ASSERT(sel.ips_remote_port == connp->conn_fport); |
| ASSERT(sel.ips_protocol == connp->conn_proto); |
| } |
| ASSERT(connp->conn_proto != 0); |
| #endif |
| |
| return (SA_UNIQUE_ID(connp->conn_fport, connp->conn_lport, ulp, 0)); |
| } |
| |
| /* |
| * Called to check policy on a latched connection. |
| * Note that we don't dereference conn_latch or conn_ihere since the conn might |
| * be closing. The caller passes a held ipsec_latch_t instead. |
| */ |
| static boolean_t |
| ipsec_check_ipsecin_latch(ip_recv_attr_t *ira, mblk_t *mp, ipsec_latch_t *ipl, |
| ipsec_action_t *ap, ipha_t *ipha, ip6_t *ip6h, const char **reason, |
| kstat_named_t **counter, conn_t *connp, netstack_t *ns) |
| { |
| ipsec_stack_t *ipss = ns->netstack_ipsec; |
| |
| ASSERT(ipl->ipl_ids_latched == B_TRUE); |
| ASSERT(ira->ira_flags & IRAF_IPSEC_SECURE); |
| |
| if (!(ira->ira_flags & IRAF_LOOPBACK)) { |
| /* |
| * Over loopback, there aren't real security associations, |
| * so there are neither identities nor "unique" values |
| * for us to check the packet against. |
| */ |
| if (ira->ira_ipsec_ah_sa != NULL) { |
| if (!spd_match_inbound_ids(ipl, |
| ira->ira_ipsec_ah_sa)) { |
| *counter = DROPPER(ipss, ipds_spd_ah_badid); |
| *reason = "AH identity mismatch"; |
| return (B_FALSE); |
| } |
| } |
| |
| if (ira->ira_ipsec_esp_sa != NULL) { |
| if (!spd_match_inbound_ids(ipl, |
| ira->ira_ipsec_esp_sa)) { |
| *counter = DROPPER(ipss, ipds_spd_esp_badid); |
| *reason = "ESP identity mismatch"; |
| return (B_FALSE); |
| } |
| } |
| |
| /* |
| * Can fudge pkt_unique from connp because we're latched. |
| * In DEBUG kernels (see conn_to_unique()'s implementation), |
| * verify this even if it REALLY slows things down. |
| */ |
| if (!ipsec_check_ipsecin_unique(ira, reason, counter, |
| conn_to_unique(connp, mp, ipha, ip6h), ns)) { |
| return (B_FALSE); |
| } |
| } |
| return (ipsec_check_ipsecin_action(ira, mp, ap, ipha, ip6h, reason, |
| counter, ns)); |
| } |
| |
| /* |
| * Check to see whether this secured datagram meets the policy |
| * constraints specified in ipsp. |
| * |
| * Called from ipsec_check_global_policy, and ipsec_check_inbound_policy. |
| * |
| * Consumes a reference to ipsp. |
| * Returns the mblk if ok. |
| */ |
| static mblk_t * |
| ipsec_check_ipsecin_policy(mblk_t *data_mp, ipsec_policy_t *ipsp, |
| ipha_t *ipha, ip6_t *ip6h, uint64_t pkt_unique, ip_recv_attr_t *ira, |
| netstack_t *ns) |
| { |
| ipsec_action_t *ap; |
| const char *reason = "no policy actions found"; |
| ip_stack_t *ipst = ns->netstack_ip; |
| ipsec_stack_t *ipss = ns->netstack_ipsec; |
| kstat_named_t *counter; |
| |
| counter = DROPPER(ipss, ipds_spd_got_secure); |
| |
| ASSERT(ipsp != NULL); |
| |
| ASSERT((ipha == NULL && ip6h != NULL) || |
| (ip6h == NULL && ipha != NULL)); |
| |
| if (ira->ira_flags & IRAF_LOOPBACK) |
| return (ipsec_check_loopback_policy(data_mp, ira, ipsp)); |
| |
| ASSERT(ira->ira_flags & IRAF_IPSEC_SECURE); |
| |
| if (ira->ira_ipsec_action != NULL) { |
| /* |
| * this can happen if we do a double policy-check on a packet |
| * Would be nice to be able to delete this test.. |
| */ |
| IPACT_REFRELE(ira->ira_ipsec_action); |
| } |
| ASSERT(ira->ira_ipsec_action == NULL); |
| |
| if (!SA_IDS_MATCH(ira->ira_ipsec_ah_sa, ira->ira_ipsec_esp_sa)) { |
| reason = "inbound AH and ESP identities differ"; |
| counter = DROPPER(ipss, ipds_spd_ahesp_diffid); |
| goto drop; |
| } |
| |
| if (!ipsec_check_ipsecin_unique(ira, &reason, &counter, pkt_unique, |
| ns)) |
| goto drop; |
| |
| /* |
| * Ok, now loop through the possible actions and see if any |
| * of them work for us. |
| */ |
| |
| for (ap = ipsp->ipsp_act; ap != NULL; ap = ap->ipa_next) { |
| if (ipsec_check_ipsecin_action(ira, data_mp, ap, |
| ipha, ip6h, &reason, &counter, ns)) { |
| BUMP_MIB(&ipst->ips_ip_mib, ipsecInSucceeded); |
| IPPOL_REFRELE(ipsp); |
| return (data_mp); |
| } |
| } |
| drop: |
| ipsec_rl_strlog(ns, IP_MOD_ID, 0, 0, SL_ERROR|SL_WARN|SL_CONSOLE, |
| "ipsec inbound policy mismatch: %s, packet dropped\n", |
| reason); |
| IPPOL_REFRELE(ipsp); |
| ASSERT(ira->ira_ipsec_action == NULL); |
| BUMP_MIB(&ipst->ips_ip_mib, ipsecInFailed); |
| ip_drop_packet(data_mp, B_TRUE, NULL, counter, |
| &ipss->ipsec_spd_dropper); |
| return (NULL); |
| } |
| |
| /* |
| * sleazy prefix-length-based compare. |
| * another inlining candidate.. |
| */ |
| boolean_t |
| ip_addr_match(uint8_t *addr1, int pfxlen, in6_addr_t *addr2p) |
| { |
| int offset = pfxlen>>3; |
| int bitsleft = pfxlen & 7; |
| uint8_t *addr2 = (uint8_t *)addr2p; |
| |
| /* |
| * and there was much evil.. |
| * XXX should inline-expand the bcmp here and do this 32 bits |
| * or 64 bits at a time.. |
| */ |
| return ((bcmp(addr1, addr2, offset) == 0) && |
| ((bitsleft == 0) || |
| (((addr1[offset] ^ addr2[offset]) & (0xff<<(8-bitsleft))) == 0))); |
| } |
| |
| static ipsec_policy_t * |
| ipsec_find_policy_chain(ipsec_policy_t *best, ipsec_policy_t *chain, |
| ipsec_selector_t *sel, boolean_t is_icmp_inv_acq) |
| { |
| ipsec_selkey_t *isel; |
| ipsec_policy_t *p; |
| int bpri = best ? best->ipsp_prio : 0; |
| |
| for (p = chain; p != NULL; p = p->ipsp_hash.hash_next) { |
| uint32_t valid; |
| |
| if (p->ipsp_prio <= bpri) |
| continue; |
| isel = &p->ipsp_sel->ipsl_key; |
| valid = isel->ipsl_valid; |
| |
| if ((valid & IPSL_PROTOCOL) && |
| (isel->ipsl_proto != sel->ips_protocol)) |
| continue; |
| |
| if ((valid & IPSL_REMOTE_ADDR) && |
| !ip_addr_match((uint8_t *)&isel->ipsl_remote, |
| isel->ipsl_remote_pfxlen, &sel->ips_remote_addr_v6)) |
| continue; |
| |
| if ((valid & IPSL_LOCAL_ADDR) && |
| !ip_addr_match((uint8_t *)&isel->ipsl_local, |
| isel->ipsl_local_pfxlen, &sel->ips_local_addr_v6)) |
| continue; |
| |
| if ((valid & IPSL_REMOTE_PORT) && |
| isel->ipsl_rport != sel->ips_remote_port) |
| continue; |
| |
| if ((valid & IPSL_LOCAL_PORT) && |
| isel->ipsl_lport != sel->ips_local_port) |
| continue; |
| |
| if (!is_icmp_inv_acq) { |
| if ((valid & IPSL_ICMP_TYPE) && |
| (isel->ipsl_icmp_type > sel->ips_icmp_type || |
| isel->ipsl_icmp_type_end < sel->ips_icmp_type)) { |
| continue; |
| } |
| |
| if ((valid & IPSL_ICMP_CODE) && |
| (isel->ipsl_icmp_code > sel->ips_icmp_code || |
| isel->ipsl_icmp_code_end < |
| sel->ips_icmp_code)) { |
| continue; |
| } |
| } else { |
| /* |
| * special case for icmp inverse acquire |
| * we only want policies that aren't drop/pass |
| */ |
| if (p->ipsp_act->ipa_act.ipa_type != IPSEC_ACT_APPLY) |
| continue; |
| } |
| |
| /* we matched all the packet-port-field selectors! */ |
| best = p; |
| bpri = p->ipsp_prio; |
| } |
| |
| return (best); |
| } |
| |
| /* |
| * Try to find and return the best policy entry under a given policy |
| * root for a given set of selectors; the first parameter "best" is |
| * the current best policy so far. If "best" is non-null, we have a |
| * reference to it. We return a reference to a policy; if that policy |
| * is not the original "best", we need to release that reference |
| * before returning. |
| */ |
| ipsec_policy_t * |
| ipsec_find_policy_head(ipsec_policy_t *best, ipsec_policy_head_t *head, |
| int direction, ipsec_selector_t *sel) |
| { |
| ipsec_policy_t *curbest; |
| ipsec_policy_root_t *root; |
| uint8_t is_icmp_inv_acq = sel->ips_is_icmp_inv_acq; |
| int af = sel->ips_isv4 ? IPSEC_AF_V4 : IPSEC_AF_V6; |
| |
| curbest = best; |
| root = &head->iph_root[direction]; |
| |
| #ifdef DEBUG |
| if (is_icmp_inv_acq) { |
| if (sel->ips_isv4) { |
| if (sel->ips_protocol != IPPROTO_ICMP) { |
| cmn_err(CE_WARN, "ipsec_find_policy_head:" |
| " expecting icmp, got %d", |
| sel->ips_protocol); |
| } |
| } else { |
| if (sel->ips_protocol != IPPROTO_ICMPV6) { |
| cmn_err(CE_WARN, "ipsec_find_policy_head:" |
| " expecting icmpv6, got %d", |
| sel->ips_protocol); |
| } |
| } |
| } |
| #endif |
| |
| rw_enter(&head->iph_lock, RW_READER); |
| |
| if (root->ipr_nchains > 0) { |
| curbest = ipsec_find_policy_chain(curbest, |
| root->ipr_hash[selector_hash(sel, root)].hash_head, sel, |
| is_icmp_inv_acq); |
| } |
| curbest = ipsec_find_policy_chain(curbest, root->ipr_nonhash[af], sel, |
| is_icmp_inv_acq); |
| |
| /* |
| * Adjust reference counts if we found anything new. |
| */ |
| if (curbest != best) { |
| ASSERT(curbest != NULL); |
| IPPOL_REFHOLD(curbest); |
| |
| if (best != NULL) { |
| IPPOL_REFRELE(best); |
| } |
| } |
| |
| rw_exit(&head->iph_lock); |
| |
| return (curbest); |
| } |
| |
| /* |
| * Find the best system policy (either global or per-interface) which |
| * applies to the given selector; look in all the relevant policy roots |
| * to figure out which policy wins. |
| * |
| * Returns a reference to a policy; caller must release this |
| * reference when done. |
| */ |
| ipsec_policy_t * |
| ipsec_find_policy(int direction, const conn_t *connp, ipsec_selector_t *sel, |
| netstack_t *ns) |
| { |
| ipsec_policy_t *p; |
| ipsec_stack_t *ipss = ns->netstack_ipsec; |
| |
| p = ipsec_find_policy_head(NULL, &ipss->ipsec_system_policy, |
| direction, sel); |
| if ((connp != NULL) && (connp->conn_policy != NULL)) { |
| p = ipsec_find_policy_head(p, connp->conn_policy, |
| direction, sel); |
| } |
| |
| return (p); |
| } |
| |
| /* |
| * Check with global policy and see whether this inbound |
| * packet meets the policy constraints. |
| * |
| * Locate appropriate policy from global policy, supplemented by the |
| * conn's configured and/or cached policy if the conn is supplied. |
| * |
| * Dispatch to ipsec_check_ipsecin_policy if we have policy and an |
| * encrypted packet to see if they match. |
| * |
| * Otherwise, see if the policy allows cleartext; if not, drop it on the |
| * floor. |
| */ |
| mblk_t * |
| ipsec_check_global_policy(mblk_t *data_mp, conn_t *connp, |
| ipha_t *ipha, ip6_t *ip6h, ip_recv_attr_t *ira, netstack_t *ns) |
| { |
| ipsec_policy_t *p; |
| ipsec_selector_t sel; |
| boolean_t policy_present; |
| kstat_named_t *counter; |
| uint64_t pkt_unique; |
| ip_stack_t *ipst = ns->netstack_ip; |
| ipsec_stack_t *ipss = ns->netstack_ipsec; |
| |
| sel.ips_is_icmp_inv_acq = 0; |
| |
| ASSERT((ipha == NULL && ip6h != NULL) || |
| (ip6h == NULL && ipha != NULL)); |
| |
| if (ipha != NULL) |
| policy_present = ipss->ipsec_inbound_v4_policy_present; |
| else |
| policy_present = ipss->ipsec_inbound_v6_policy_present; |
| |
| if (!policy_present && connp == NULL) { |
| /* |
| * No global policy and no per-socket policy; |
| * just pass it back (but we shouldn't get here in that case) |
| */ |
| return (data_mp); |
| } |
| |
| /* |
| * If we have cached policy, use it. |
| * Otherwise consult system policy. |
| */ |
| if ((connp != NULL) && (connp->conn_latch != NULL)) { |
| p = connp->conn_latch_in_policy; |
| if (p != NULL) { |
| IPPOL_REFHOLD(p); |
| } |
| /* |
| * Fudge sel for UNIQUE_ID setting below. |
| */ |
| pkt_unique = conn_to_unique(connp, data_mp, ipha, ip6h); |
| } else { |
| /* Initialize the ports in the selector */ |
| if (ipsec_init_inbound_sel(&sel, data_mp, ipha, ip6h, |
| SEL_NONE) == SELRET_NOMEM) { |
| /* |
| * Technically not a policy mismatch, but it is |
| * an internal failure. |
| */ |
| ipsec_log_policy_failure(IPSEC_POLICY_MISMATCH, |
| "ipsec_init_inbound_sel", ipha, ip6h, B_TRUE, ns); |
| counter = DROPPER(ipss, ipds_spd_nomem); |
| goto fail; |
| } |
| |
| /* |
| * Find the policy which best applies. |
| * |
| * If we find global policy, we should look at both |
| * local policy and global policy and see which is |
| * stronger and match accordingly. |
| * |
| * If we don't find a global policy, check with |
| * local policy alone. |
| */ |
| |
| p = ipsec_find_policy(IPSEC_TYPE_INBOUND, connp, &sel, ns); |
| pkt_unique = SA_UNIQUE_ID(sel.ips_remote_port, |
| sel.ips_local_port, sel.ips_protocol, 0); |
| } |
| |
| if (p == NULL) { |
| if (!(ira->ira_flags & IRAF_IPSEC_SECURE)) { |
| /* |
| * We have no policy; default to succeeding. |
| * XXX paranoid system design doesn't do this. |
| */ |
| BUMP_MIB(&ipst->ips_ip_mib, ipsecInSucceeded); |
| return (data_mp); |
| } else { |
| counter = DROPPER(ipss, ipds_spd_got_secure); |
| ipsec_log_policy_failure(IPSEC_POLICY_NOT_NEEDED, |
| "ipsec_check_global_policy", ipha, ip6h, B_TRUE, |
| ns); |
| goto fail; |
| } |
| } |
| if (ira->ira_flags & IRAF_IPSEC_SECURE) { |
| return (ipsec_check_ipsecin_policy(data_mp, p, ipha, ip6h, |
| pkt_unique, ira, ns)); |
| } |
| if (p->ipsp_act->ipa_allow_clear) { |
| BUMP_MIB(&ipst->ips_ip_mib, ipsecInSucceeded); |
| IPPOL_REFRELE(p); |
| return (data_mp); |
| } |
| IPPOL_REFRELE(p); |
| /* |
| * If we reach here, we will drop the packet because it failed the |
| * global policy check because the packet was cleartext, and it |
| * should not have been. |
| */ |
| ipsec_log_policy_failure(IPSEC_POLICY_MISMATCH, |
| "ipsec_check_global_policy", ipha, ip6h, B_FALSE, ns); |
| counter = DROPPER(ipss, ipds_spd_got_clear); |
| |
| fail: |
| ip_drop_packet(data_mp, B_TRUE, NULL, counter, |
| &ipss->ipsec_spd_dropper); |
| BUMP_MIB(&ipst->ips_ip_mib, ipsecInFailed); |
| return (NULL); |
| } |
| |
| /* |
| * We check whether an inbound datagram is a valid one |
| * to accept in clear. If it is secure, it is the job |
| * of IPSEC to log information appropriately if it |
| * suspects that it may not be the real one. |
| * |
| * It is called only while fanning out to the ULP |
| * where ULP accepts only secure data and the incoming |
| * is clear. Usually we never accept clear datagrams in |
| * such cases. ICMP is the only exception. |
| * |
| * NOTE : We don't call this function if the client (ULP) |
| * is willing to accept things in clear. |
| */ |
| boolean_t |
| ipsec_inbound_accept_clear(mblk_t *mp, ipha_t *ipha, ip6_t *ip6h) |
| { |
| ushort_t iph_hdr_length; |
| icmph_t *icmph; |
| icmp6_t *icmp6; |
| uint8_t *nexthdrp; |
| |
| ASSERT((ipha != NULL && ip6h == NULL) || |
| (ipha == NULL && ip6h != NULL)); |
| |
| if (ip6h != NULL) { |
| iph_hdr_length = ip_hdr_length_v6(mp, ip6h); |
| if (!ip_hdr_length_nexthdr_v6(mp, ip6h, &iph_hdr_length, |
| &nexthdrp)) { |
| return (B_FALSE); |
| } |
| if (*nexthdrp != IPPROTO_ICMPV6) |
| return (B_FALSE); |
| icmp6 = (icmp6_t *)(&mp->b_rptr[iph_hdr_length]); |
| /* Match IPv6 ICMP policy as closely as IPv4 as possible. */ |
| switch (icmp6->icmp6_type) { |
| case ICMP6_PARAM_PROB: |
| /* Corresponds to port/proto unreach in IPv4. */ |
| case ICMP6_ECHO_REQUEST: |
| /* Just like IPv4. */ |
| return (B_FALSE); |
| |
| case MLD_LISTENER_QUERY: |
| case MLD_LISTENER_REPORT: |
| case MLD_LISTENER_REDUCTION: |
| /* |
| * XXX Seperate NDD in IPv4 what about here? |
| * Plus, mcast is important to ND. |
| */ |
| case ICMP6_DST_UNREACH: |
| /* Corresponds to HOST/NET unreachable in IPv4. */ |
| case ICMP6_PACKET_TOO_BIG: |
| case ICMP6_ECHO_REPLY: |
| /* These are trusted in IPv4. */ |
| case ND_ROUTER_SOLICIT: |
| case ND_ROUTER_ADVERT: |
| case ND_NEIGHBOR_SOLICIT: |
| case ND_NEIGHBOR_ADVERT: |
| case ND_REDIRECT: |
| /* Trust ND messages for now. */ |
| case ICMP6_TIME_EXCEEDED: |
| default: |
| return (B_TRUE); |
| } |
| } else { |
| /* |
| * If it is not ICMP, fail this request. |
| */ |
| if (ipha->ipha_protocol != IPPROTO_ICMP) { |
| #ifdef FRAGCACHE_DEBUG |
| cmn_err(CE_WARN, "Dropping - ipha_proto = %d\n", |
| ipha->ipha_protocol); |
| #endif |
| return (B_FALSE); |
| } |
| iph_hdr_length = IPH_HDR_LENGTH(ipha); |
| icmph = (icmph_t *)&mp->b_rptr[iph_hdr_length]; |
| /* |
| * It is an insecure icmp message. Check to see whether we are |
| * willing to accept this one. |
| */ |
| |
| switch (icmph->icmph_type) { |
| case ICMP_ECHO_REPLY: |
| case ICMP_TIME_STAMP_REPLY: |
| case ICMP_INFO_REPLY: |
| case ICMP_ROUTER_ADVERTISEMENT: |
| /* |
| * We should not encourage clear replies if this |
| * client expects secure. If somebody is replying |
| * in clear some mailicious user watching both the |
| * request and reply, can do chosen-plain-text attacks. |
| * With global policy we might be just expecting secure |
| * but sending out clear. We don't know what the right |
| * thing is. We can't do much here as we can't control |
| * the sender here. Till we are sure of what to do, |
| * accept them. |
| */ |
| return (B_TRUE); |
| case ICMP_ECHO_REQUEST: |
| case ICMP_TIME_STAMP_REQUEST: |
| case ICMP_INFO_REQUEST: |
| case ICMP_ADDRESS_MASK_REQUEST: |
| case ICMP_ROUTER_SOLICITATION: |
| case ICMP_ADDRESS_MASK_REPLY: |
| /* |
| * Don't accept this as somebody could be sending |
| * us plain text to get encrypted data. If we reply, |
| * it will lead to chosen plain text attack. |
| */ |
| return (B_FALSE); |
| case ICMP_DEST_UNREACHABLE: |
| switch (icmph->icmph_code) { |
| case ICMP_FRAGMENTATION_NEEDED: |
| /* |
| * Be in sync with icmp_inbound, where we have |
| * already set dce_pmtu |
| */ |
| #ifdef FRAGCACHE_DEBUG |
| cmn_err(CE_WARN, "ICMP frag needed\n"); |
| #endif |
| return (B_TRUE); |
| case ICMP_HOST_UNREACHABLE: |
| case ICMP_NET_UNREACHABLE: |
| /* |
| * By accepting, we could reset a connection. |
| * How do we solve the problem of some |
| * intermediate router sending in-secure ICMP |
| * messages ? |
| */ |
| return (B_TRUE); |
| case ICMP_PORT_UNREACHABLE: |
| case ICMP_PROTOCOL_UNREACHABLE: |
| default : |
| return (B_FALSE); |
| } |
| case ICMP_SOURCE_QUENCH: |
| /* |
| * If this is an attack, TCP will slow start |
| * because of this. Is it very harmful ? |
| */ |
| return (B_TRUE); |
| case ICMP_PARAM_PROBLEM: |
| return (B_FALSE); |
| case ICMP_TIME_EXCEEDED: |
| return (B_TRUE); |
| case ICMP_REDIRECT: |
| return (B_FALSE); |
| default : |
| return (B_FALSE); |
| } |
| } |
| } |
| |
| void |
| ipsec_latch_ids(ipsec_latch_t *ipl, ipsid_t *local, ipsid_t *remote) |
| { |
| mutex_enter(&ipl->ipl_lock); |
| |
| if (ipl->ipl_ids_latched) { |
| /* I lost, someone else got here before me */ |
| mutex_exit(&ipl->ipl_lock); |
| return; |
| } |
| |
| if (local != NULL) |
| IPSID_REFHOLD(local); |
| if (remote != NULL) |
| IPSID_REFHOLD(remote); |
| |
| ipl->ipl_local_cid = local; |
| ipl->ipl_remote_cid = remote; |
| ipl->ipl_ids_latched = B_TRUE; |
| mutex_exit(&ipl->ipl_lock); |
| } |
| |
| void |
| ipsec_latch_inbound(conn_t *connp, ip_recv_attr_t *ira) |
| { |
| ipsa_t *sa; |
| ipsec_latch_t *ipl = connp->conn_latch; |
| |
| if (!ipl->ipl_ids_latched) { |
| ipsid_t *local = NULL; |
| ipsid_t *remote = NULL; |
| |
| if (!(ira->ira_flags & IRAF_LOOPBACK)) { |
| ASSERT(ira->ira_flags & IRAF_IPSEC_SECURE); |
| if (ira->ira_ipsec_esp_sa != NULL) |
| sa = ira->ira_ipsec_esp_sa; |
| else |
| sa = ira->ira_ipsec_ah_sa; |
| ASSERT(sa != NULL); |
| local = sa->ipsa_dst_cid; |
| remote = sa->ipsa_src_cid; |
| } |
| ipsec_latch_ids(ipl, local, remote); |
| } |
| if (ira->ira_flags & IRAF_IPSEC_SECURE) { |
| if (connp->conn_latch_in_action != NULL) { |
| /* |
| * Previously cached action. This is probably |
| * harmless, but in DEBUG kernels, check for |
| * action equality. |
| * |
| * Preserve the existing action to preserve latch |
| * invariance. |
| */ |
| ASSERT(connp->conn_latch_in_action == |
| ira->ira_ipsec_action); |
| return; |
| } |
| connp->conn_latch_in_action = ira->ira_ipsec_action; |
| IPACT_REFHOLD(connp->conn_latch_in_action); |
| } |
| } |
| |
| /* |
| * Check whether the policy constraints are met either for an |
| * inbound datagram; called from IP in numerous places. |
| * |
| * Note that this is not a chokepoint for inbound policy checks; |
| * see also ipsec_check_ipsecin_latch() and ipsec_check_global_policy() |
| */ |
| mblk_t * |
| ipsec_check_inbound_policy(mblk_t *mp, conn_t *connp, |
| ipha_t *ipha, ip6_t *ip6h, ip_recv_attr_t *ira) |
| { |
| boolean_t ret; |
| ipsec_latch_t *ipl; |
| ipsec_action_t *ap; |
| uint64_t unique_id; |
| ipsec_stack_t *ipss; |
| ip_stack_t *ipst; |
| netstack_t *ns; |
| ipsec_policy_head_t *policy_head; |
| ipsec_policy_t *p = NULL; |
| |
| ASSERT(connp != NULL); |
| ns = connp->conn_netstack; |
| ipss = ns->netstack_ipsec; |
| ipst = ns->netstack_ip; |
| |
| if (!(ira->ira_flags & IRAF_IPSEC_SECURE)) { |
| /* |
| * This is the case where the incoming datagram is |
| * cleartext and we need to see whether this client |
| * would like to receive such untrustworthy things from |
| * the wire. |
| */ |
| ASSERT(mp != NULL); |
| |
| mutex_enter(&connp->conn_lock); |
| if (connp->conn_state_flags & CONN_CONDEMNED) { |
| mutex_exit(&connp->conn_lock); |
| ip_drop_packet(mp, B_TRUE, NULL, |
| DROPPER(ipss, ipds_spd_got_clear), |
| &ipss->ipsec_spd_dropper); |
| BUMP_MIB(&ipst->ips_ip_mib, ipsecInFailed); |
| return (NULL); |
| } |
| if (connp->conn_latch != NULL) { |
| /* Hold a reference in case the conn is closing */ |
| p = connp->conn_latch_in_policy; |
| if (p != NULL) |
| IPPOL_REFHOLD(p); |
| mutex_exit(&connp->conn_lock); |
| /* |
| * Policy is cached in the conn. |
| */ |
| if (p != NULL && !p->ipsp_act->ipa_allow_clear) { |
| ret = ipsec_inbound_accept_clear(mp, |
| ipha, ip6h); |
| if (ret) { |
| BUMP_MIB(&ipst->ips_ip_mib, |
| ipsecInSucceeded); |
| IPPOL_REFRELE(p); |
| return (mp); |
| } else { |
| ipsec_log_policy_failure( |
| IPSEC_POLICY_MISMATCH, |
| "ipsec_check_inbound_policy", ipha, |
| ip6h, B_FALSE, ns); |
| ip_drop_packet(mp, B_TRUE, NULL, |
| DROPPER(ipss, ipds_spd_got_clear), |
| &ipss->ipsec_spd_dropper); |
| BUMP_MIB(&ipst->ips_ip_mib, |
| ipsecInFailed); |
| IPPOL_REFRELE(p); |
| return (NULL); |
| } |
| } else { |
| BUMP_MIB(&ipst->ips_ip_mib, ipsecInSucceeded); |
| if (p != NULL) |
| IPPOL_REFRELE(p); |
| return (mp); |
| } |
| } else { |
| policy_head = connp->conn_policy; |
| |
| /* Hold a reference in case the conn is closing */ |
| if (policy_head != NULL) |
| IPPH_REFHOLD(policy_head); |
| mutex_exit(&connp->conn_lock); |
| /* |
| * As this is a non-hardbound connection we need |
| * to look at both per-socket policy and global |
| * policy. |
| */ |
| mp = ipsec_check_global_policy(mp, connp, |
| ipha, ip6h, ira, ns); |
| if (policy_head != NULL) |
| IPPH_REFRELE(policy_head, ns); |
| return (mp); |
| } |
| } |
| |
| mutex_enter(&connp->conn_lock); |
| /* Connection is closing */ |
| if (connp->conn_state_flags & CONN_CONDEMNED) { |
| mutex_exit(&connp->conn_lock); |
| ip_drop_packet(mp, B_TRUE, NULL, |
| DROPPER(ipss, ipds_spd_got_clear), |
| &ipss->ipsec_spd_dropper); |
| BUMP_MIB(&ipst->ips_ip_mib, ipsecInFailed); |
| return (NULL); |
| } |
| |
| /* |
| * Once a connection is latched it remains so for life, the conn_latch |
| * pointer on the conn has not changed, simply initializing ipl here |
| * as the earlier initialization was done only in the cleartext case. |
| */ |
| if ((ipl = connp->conn_latch) == NULL) { |
| mblk_t *retmp; |
| policy_head = connp->conn_policy; |
| |
| /* Hold a reference in case the conn is closing */ |
| if (policy_head != NULL) |
| IPPH_REFHOLD(policy_head); |
| mutex_exit(&connp->conn_lock); |
| /* |
| * We don't have policies cached in the conn |
| * for this stream. So, look at the global |
| * policy. It will check against conn or global |
| * depending on whichever is stronger. |
| */ |
| retmp = ipsec_check_global_policy(mp, connp, |
| ipha, ip6h, ira, ns); |
| if (policy_head != NULL) |
| IPPH_REFRELE(policy_head, ns); |
| return (retmp); |
| } |
| |
| IPLATCH_REFHOLD(ipl); |
| /* Hold reference on conn_latch_in_action in case conn is closing */ |
| ap = connp->conn_latch_in_action; |
| if (ap != NULL) |
| IPACT_REFHOLD(ap); |
| mutex_exit(&connp->conn_lock); |
| |
| if (ap != NULL) { |
| /* Policy is cached & latched; fast(er) path */ |
| const char *reason; |
| kstat_named_t *counter; |
| |
| if (ipsec_check_ipsecin_latch(ira, mp, ipl, ap, |
| ipha, ip6h, &reason, &counter, connp, ns)) { |
| BUMP_MIB(&ipst->ips_ip_mib, ipsecInSucceeded); |
| IPLATCH_REFRELE(ipl); |
| IPACT_REFRELE(ap); |
| return (mp); |
| } |
| ipsec_rl_strlog(ns, IP_MOD_ID, 0, 0, |
| SL_ERROR|SL_WARN|SL_CONSOLE, |
| "ipsec inbound policy mismatch: %s, packet dropped\n", |
| reason); |
| ip_drop_packet(mp, B_TRUE, NULL, counter, |
| &ipss->ipsec_spd_dropper); |
| BUMP_MIB(&ipst->ips_ip_mib, ipsecInFailed); |
| IPLATCH_REFRELE(ipl); |
| IPACT_REFRELE(ap); |
| return (NULL); |
| } |
| if ((p = connp->conn_latch_in_policy) == NULL) { |
| ipsec_weird_null_inbound_policy++; |
| IPLATCH_REFRELE(ipl); |
| return (mp); |
| } |
| |
| unique_id = conn_to_unique(connp, mp, ipha, ip6h); |
| IPPOL_REFHOLD(p); |
| mp = ipsec_check_ipsecin_policy(mp, p, ipha, ip6h, unique_id, ira, ns); |
| /* |
| * NOTE: ipsecIn{Failed,Succeeeded} bumped by |
| * ipsec_check_ipsecin_policy(). |
| */ |
| if (mp != NULL) |
| ipsec_latch_inbound(connp, ira); |
| IPLATCH_REFRELE(ipl); |
| return (mp); |
| } |
| |
| /* |
| * Handle all sorts of cases like tunnel-mode and ICMP. |
| */ |
| static int |
| prepended_length(mblk_t *mp, uintptr_t hptr) |
| { |
| int rc = 0; |
| |
| while (mp != NULL) { |
| if (hptr >= (uintptr_t)mp->b_rptr && hptr < |
| (uintptr_t)mp->b_wptr) { |
| rc += (int)(hptr - (uintptr_t)mp->b_rptr); |
| break; /* out of while loop */ |
| } |
| rc += (int)MBLKL(mp); |
| mp = mp->b_cont; |
| } |
| |
| if (mp == NULL) { |
| /* |
| * IF (big IF) we make it here by naturally exiting the loop, |
| * then ip6h isn't in the mblk chain "mp" at all. |
| * |
| * The only case where this happens is with a reversed IP |
| * header that gets passed up by inbound ICMP processing. |
| * This unfortunately triggers longstanding bug 6478464. For |
| * now, just pass up 0 for the answer. |
| */ |
| #ifdef DEBUG_NOT_UNTIL_6478464 |
| ASSERT(mp != NULL); |
| #endif |
| rc = 0; |
| } |
| |
| return (rc); |
| } |
| |
| /* |
| * Returns: |
| * |
| * SELRET_NOMEM --> msgpullup() needed to gather things failed. |
| * SELRET_BADPKT --> If we're being called after tunnel-mode fragment |
| * gathering, the initial fragment is too short for |
| * useful data. Only returned if SEL_TUNNEL_FIRSTFRAG is |
| * set. |
| * SELRET_SUCCESS --> "sel" now has initialized IPsec selector data. |
| * SELRET_TUNFRAG --> This is a fragment in a tunnel-mode packet. Caller |
| * should put this packet in a fragment-gathering queue. |
| * Only returned if SEL_TUNNEL_MODE and SEL_PORT_POLICY |
| * is set. |
| * |
| * Note that ipha/ip6h can be in a different mblk (mp->b_cont) in the case |
| * of tunneled packets. |
| * Also, mp->b_rptr can be an ICMP error where ipha/ip6h is the packet in |
| * error past the ICMP error. |
| */ |
| static selret_t |
| ipsec_init_inbound_sel(ipsec_selector_t *sel, mblk_t *mp, ipha_t *ipha, |
| ip6_t *ip6h, uint8_t sel_flags) |
| { |
| uint16_t *ports; |
| int outer_hdr_len = 0; /* For ICMP or tunnel-mode cases... */ |
| ushort_t hdr_len; |
| mblk_t *spare_mp = NULL; |
| uint8_t *nexthdrp, *transportp; |
| uint8_t nexthdr; |
| uint8_t icmp_proto; |
| ip_pkt_t ipp; |
| boolean_t port_policy_present = (sel_flags & SEL_PORT_POLICY); |
| boolean_t is_icmp = (sel_flags & SEL_IS_ICMP); |
| boolean_t tunnel_mode = (sel_flags & SEL_TUNNEL_MODE); |
| boolean_t post_frag = (sel_flags & SEL_POST_FRAG); |
| |
| ASSERT((ipha == NULL && ip6h != NULL) || |
| (ipha != NULL && ip6h == NULL)); |
| |
| if (ip6h != NULL) { |
| outer_hdr_len = prepended_length(mp, (uintptr_t)ip6h); |
| nexthdr = ip6h->ip6_nxt; |
| icmp_proto = IPPROTO_ICMPV6; |
| sel->ips_isv4 = B_FALSE; |
| sel->ips_local_addr_v6 = ip6h->ip6_dst; |
| sel->ips_remote_addr_v6 = ip6h->ip6_src; |
| |
| bzero(&ipp, sizeof (ipp)); |
| (void) ip_find_hdr_v6(mp, ip6h, B_FALSE, &ipp, NULL); |
| |
| switch (nexthdr) { |
| case IPPROTO_HOPOPTS: |
| case IPPROTO_ROUTING: |
| case IPPROTO_DSTOPTS: |
| case IPPROTO_FRAGMENT: |
| /* |
| * Use ip_hdr_length_nexthdr_v6(). And have a spare |
| * mblk that's contiguous to feed it |
| */ |
| if ((spare_mp = msgpullup(mp, -1)) == NULL) |
| return (SELRET_NOMEM); |
| if (!ip_hdr_length_nexthdr_v6(spare_mp, |
| (ip6_t *)(spare_mp->b_rptr + outer_hdr_len), |
| &hdr_len, &nexthdrp)) { |
| /* Malformed packet - caller frees. */ |
| ipsec_freemsg_chain(spare_mp); |
| return (SELRET_BADPKT); |
| } |
| nexthdr = *nexthdrp; |
| /* We can just extract based on hdr_len now. */ |
| break; |
| default: |
| hdr_len = IPV6_HDR_LEN; |
| break; |
| } |
| |
| if (port_policy_present && IS_V6_FRAGMENT(ipp) && !is_icmp) { |
| /* IPv6 Fragment */ |
| ipsec_freemsg_chain(spare_mp); |
| return (SELRET_TUNFRAG); |
| } |
| transportp = (uint8_t *)ip6h + hdr_len; |
| } else { |
| outer_hdr_len = prepended_length(mp, (uintptr_t)ipha); |
| icmp_proto = IPPROTO_ICMP; |
| sel->ips_isv4 = B_TRUE; |
| sel->ips_local_addr_v4 = ipha->ipha_dst; |
| sel->ips_remote_addr_v4 = ipha->ipha_src; |
| nexthdr = ipha->ipha_protocol; |
| hdr_len = IPH_HDR_LENGTH(ipha); |
| |
| if (port_policy_present && |
| IS_V4_FRAGMENT(ipha->ipha_fragment_offset_and_flags) && |
| !is_icmp) { |
| /* IPv4 Fragment */ |
| ipsec_freemsg_chain(spare_mp); |
| return (SELRET_TUNFRAG); |
| } |
| transportp = (uint8_t *)ipha + hdr_len; |
| } |
| sel->ips_protocol = nexthdr; |
| |
| if ((nexthdr != IPPROTO_TCP && nexthdr != IPPROTO_UDP && |
| nexthdr != IPPROTO_SCTP && nexthdr != icmp_proto) || |
| (!port_policy_present && !post_frag && tunnel_mode)) { |
| sel->ips_remote_port = sel->ips_local_port = 0; |
| ipsec_freemsg_chain(spare_mp); |
| return (SELRET_SUCCESS); |
| } |
| |
| if (transportp + 4 > mp->b_wptr) { |
| /* If we didn't pullup a copy already, do so now. */ |
| /* |
| * XXX performance, will upper-layers frequently split TCP/UDP |
| * apart from IP or options? If so, perhaps we should revisit |
| * the spare_mp strategy. |
| */ |
| ipsec_hdr_pullup_needed++; |
| if (spare_mp == NULL && |
| (spare_mp = msgpullup(mp, -1)) == NULL) { |
| return (SELRET_NOMEM); |
| } |
| transportp = &spare_mp->b_rptr[hdr_len + outer_hdr_len]; |
|