lists.openwall.net | lists / announce owl-users owl-dev john-users john-dev passwdqc-users yescrypt popa3d-users / oss-security kernel-hardening musl sabotage tlsify passwords / crypt-dev xvendor / Bugtraq Full-Disclosure linux-kernel linux-netdev linux-ext4 linux-hardening linux-cve-announce PHC | |
Open Source and information security mailing list archives
| ||
|
Message-Id: <1548221483-3085-3-git-send-email-tom@quantonium.net> Date: Tue, 22 Jan 2019 21:31:20 -0800 From: Tom Herbert <tom@...bertland.com> To: davem@...emloft.net, netdev@...r.kernel.org Cc: Tom Herbert <tom@...ntonium.net> Subject: [PATCH net-next 2/5] exthdrs: Registration of TLV handlers and parameters Define a table that contains 256 entries, one for each TLV. Each entry points to a structure that contains parameters and handler functions for receiving and transmitting TLVs. The receive and transmit properties can be managed independently. TLV transmit properties include a description of limits, alignment, and preferred ordering. TLV receive properties provide the receiver handler. A class attribute is defined in both receive and transmit properties that indicate the type of extension header in which the TLV may be used (e.g. Hop-by-Hop options, Destination options, or Destination options before the routing header. Receive TLV lookup and processing is modified to be a lookup in the TLV table. tlv_{set,unset}_{rx,tx}_param function can be used to set attributes in the TLV table. A table containing parameters for TLVs supported by the kernel and is used to initialize the TLV table. --- include/net/ipv6.h | 58 ++++++++- include/uapi/linux/in6.h | 17 +++ net/ipv6/exthdrs.c | 47 ++++--- net/ipv6/exthdrs_options.c | 314 +++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 389 insertions(+), 47 deletions(-) diff --git a/include/net/ipv6.h b/include/net/ipv6.h index 8abdcdb..3d3b5a1 100644 --- a/include/net/ipv6.h +++ b/include/net/ipv6.h @@ -379,13 +379,61 @@ struct ipv6_txoptions *ipv6_renew_options(struct sock *sk, struct ipv6_txoptions *ipv6_fixup_options(struct ipv6_txoptions *opt_space, struct ipv6_txoptions *opt); -struct tlvtype_proc { - int type; - bool (*func)(struct sk_buff *skb, int offset); +struct tlv_tx_param { + unsigned char preferred_order; + unsigned char admin_perm : 2; + unsigned char user_perm : 2; + unsigned char class : 3; + unsigned char align_mult : 4; + unsigned char align_off : 4; + unsigned char data_len_mult : 4; + unsigned char data_len_off : 4; + unsigned char min_data_len; + unsigned char max_data_len; }; -extern const struct tlvtype_proc tlvprocdestopt_lst[]; -extern const struct tlvtype_proc tlvprochopopt_lst[]; +struct tlv_rx_param { + unsigned char class: 3; + bool (*func)(unsigned int class, struct sk_buff *skb, int offset); +}; + +struct tlv_param { + struct tlv_tx_param tx_params; + struct tlv_rx_param rx_params; + struct rcu_head rcu; +}; + +extern struct tlv_param __rcu *tlv_param_table[256]; + +/* Preferred TLV ordering (placed by increasing order) */ +#define TLV_PREF_ORDER_HAO 10 +#define TLV_PREF_ORDER_ROUTERALERT 20 +#define TLV_PREF_ORDER_JUMBO 30 +#define TLV_PREF_ORDER_CALIPSO 40 + +/* tlv_deref_rx_params assume rcu_read_lock is held */ +static inline struct tlv_rx_param *tlv_deref_rx_params(unsigned int type) +{ + struct tlv_param *tp = rcu_dereference(tlv_param_table[type]); + + return &tp->rx_params; +} + +/* tlv_deref_tx_params assume rcu_read_lock is held */ +static inline struct tlv_tx_param *tlv_deref_tx_params(unsigned int type) +{ + struct tlv_param *tp = rcu_dereference(tlv_param_table[type]); + + return &tp->tx_params; +} + +int tlv_set_param(unsigned char type, + const struct tlv_rx_param *rx_param_tmpl, + const struct tlv_tx_param *tx_param_tmpl); +int tlv_unset_rx_param(unsigned char type); +int tlv_set_rx_param(unsigned char type, struct tlv_rx_param *rx_param_tmpl); +int tlv_unset_tx_param(unsigned char type); +int tlv_set_tx_param(unsigned char type, struct tlv_tx_param *tx_param_tmpl); bool ipv6_opt_accepted(const struct sock *sk, const struct sk_buff *skb, const struct inet6_skb_parm *opt); diff --git a/include/uapi/linux/in6.h b/include/uapi/linux/in6.h index 71d82fe..38e8e63 100644 --- a/include/uapi/linux/in6.h +++ b/include/uapi/linux/in6.h @@ -296,4 +296,21 @@ struct in6_flowlabel_req { * ... * MRT6_MAX */ + +/* TLV permissions values */ +enum { + IPV6_TLV_PERM_NONE, + IPV6_TLV_PERM_WITH_CHECK, + IPV6_TLV_PERM_NO_CHECK, + IPV6_TLV_PERM_MAX = IPV6_TLV_PERM_NO_CHECK +}; + +/* Flags for EH type that can use a TLV option */ +#define IPV6_TLV_CLASS_FLAG_HOPOPT 0x1 +#define IPV6_TLV_CLASS_FLAG_RTRDSTOPT 0x2 +#define IPV6_TLV_CLASS_FLAG_DSTOPT 0x4 +#define IPV6_TLV_CLASS_MAX 0x7 + +#define IPV6_TLV_CLASS_ANY_DSTOPT (IPV6_TLV_CLASS_FLAG_RTRDSTOPT | \ + IPV6_TLV_CLASS_FLAG_DSTOPT) #endif /* _UAPI_LINUX_IN6_H */ diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c index 6dbacf1..af4152e 100644 --- a/net/ipv6/exthdrs.c +++ b/net/ipv6/exthdrs.c @@ -100,15 +100,14 @@ static bool ip6_tlvopt_unknown(struct sk_buff *skb, int optoff, /* Parse tlv encoded option header (hop-by-hop or destination) */ -static bool ip6_parse_tlv(const struct tlvtype_proc *procs, - struct sk_buff *skb, +static bool ip6_parse_tlv(unsigned int class, struct sk_buff *skb, int max_count) { int len = (skb_transport_header(skb)[1] + 1) << 3; const unsigned char *nh = skb_network_header(skb); int off = skb_network_header_len(skb); - const struct tlvtype_proc *curr; bool disallow_unknowns = false; + struct tlv_rx_param *tprx; int tlv_count = 0; int padlen = 0; @@ -117,12 +116,16 @@ static bool ip6_parse_tlv(const struct tlvtype_proc *procs, max_count = -max_count; } - if (skb_transport_offset(skb) + len > skb_headlen(skb)) - goto bad; + if (skb_transport_offset(skb) + len > skb_headlen(skb)) { + kfree_skb(skb); + return false; + } off += 2; len -= 2; + rcu_read_unlock(); + while (len > 0) { int optlen = nh[off + 1] + 2; int i; @@ -162,19 +165,19 @@ static bool ip6_parse_tlv(const struct tlvtype_proc *procs, if (tlv_count > max_count) goto bad; - for (curr = procs; curr->type >= 0; curr++) { - if (curr->type == nh[off]) { - /* type specific length/alignment - checks will be performed in the - func(). */ - if (curr->func(skb, off) == false) - return false; - break; - } + tprx = tlv_deref_rx_params(nh[off]); + + if ((tprx->class & class) && tprx->func) { + /* Handler will apply additional checks to + * the TLV + */ + if (!tprx->func(class, skb, off)) + goto bad_nofree; + } else if (!ip6_tlvopt_unknown(skb, off, + disallow_unknowns)) { + /* No appropriate handler, TLV is unknown */ + goto bad_nofree; } - if (curr->type < 0 && - !ip6_tlvopt_unknown(skb, off, disallow_unknowns)) - return false; padlen = 0; break; @@ -183,10 +186,14 @@ static bool ip6_parse_tlv(const struct tlvtype_proc *procs, len -= optlen; } - if (len == 0) + if (len == 0) { + rcu_read_unlock(); return true; + } bad: kfree_skb(skb); +bad_nofree: + rcu_read_unlock(); return false; } @@ -220,7 +227,7 @@ static int ipv6_destopt_rcv(struct sk_buff *skb) dstbuf = opt->dst1; #endif - if (ip6_parse_tlv(tlvprocdestopt_lst, skb, + if (ip6_parse_tlv(IPV6_TLV_CLASS_FLAG_DSTOPT, skb, init_net.ipv6.sysctl.max_dst_opts_cnt)) { skb->transport_header += extlen; opt = IP6CB(skb); @@ -643,7 +650,7 @@ int ipv6_parse_hopopts(struct sk_buff *skb) goto fail_and_free; opt->flags |= IP6SKB_HOPBYHOP; - if (ip6_parse_tlv(tlvprochopopt_lst, skb, + if (ip6_parse_tlv(IPV6_TLV_CLASS_FLAG_HOPOPT, skb, init_net.ipv6.sysctl.max_hbh_opts_cnt)) { skb->transport_header += extlen; opt = IP6CB(skb); diff --git a/net/ipv6/exthdrs_options.c b/net/ipv6/exthdrs_options.c index 70266a6..a1b7a2e 100644 --- a/net/ipv6/exthdrs_options.c +++ b/net/ipv6/exthdrs_options.c @@ -11,6 +11,7 @@ #if IS_ENABLED(CONFIG_IPV6_MIP6) #include <net/xfrm.h> #endif +#include <uapi/linux/in.h> /* Parsing tlv encoded headers. * @@ -19,6 +20,8 @@ * It MUST NOT touch skb->h. */ +struct tlv_param __rcu *tlv_param_table[256]; + struct ipv6_txoptions * ipv6_dup_options(struct sock *sk, struct ipv6_txoptions *opt) { @@ -160,7 +163,7 @@ EXPORT_SYMBOL_GPL(ipv6_fixup_options); /* Destination options header */ #if IS_ENABLED(CONFIG_IPV6_MIP6) -static bool ipv6_dest_hao(struct sk_buff *skb, int optoff) +static bool ipv6_dest_hao(unsigned int class, struct sk_buff *skb, int optoff) { struct ipv6_destopt_hao *hao; struct inet6_skb_parm *opt = IP6CB(skb); @@ -219,16 +222,6 @@ static bool ipv6_dest_hao(struct sk_buff *skb, int optoff) } #endif -const struct tlvtype_proc tlvprocdestopt_lst[] = { -#if IS_ENABLED(CONFIG_IPV6_MIP6) - { - .type = IPV6_TLV_HAO, - .func = ipv6_dest_hao, - }, -#endif - {-1, NULL} -}; - /* Hop-by-hop options */ /* Note: we cannot rely on skb_dst(skb) before we assign it in @@ -247,7 +240,7 @@ static inline struct net *ipv6_skb_net(struct sk_buff *skb) /* Router Alert as of RFC 2711 */ -static bool ipv6_hop_ra(struct sk_buff *skb, int optoff) +static bool ipv6_hop_ra(unsigned int class, struct sk_buff *skb, int optoff) { const unsigned char *nh = skb_network_header(skb); @@ -265,7 +258,7 @@ static bool ipv6_hop_ra(struct sk_buff *skb, int optoff) /* Jumbo payload */ -static bool ipv6_hop_jumbo(struct sk_buff *skb, int optoff) +static bool ipv6_hop_jumbo(unsigned int class, struct sk_buff *skb, int optoff) { const unsigned char *nh = skb_network_header(skb); struct inet6_dev *idev = __in6_dev_get_safely(skb->dev); @@ -309,7 +302,8 @@ static bool ipv6_hop_jumbo(struct sk_buff *skb, int optoff) /* CALIPSO RFC 5570 */ -static bool ipv6_hop_calipso(struct sk_buff *skb, int optoff) +static bool ipv6_hop_calipso(unsigned int class, struct sk_buff *skb, + int optoff) { const unsigned char *nh = skb_network_header(skb); @@ -329,18 +323,294 @@ static bool ipv6_hop_calipso(struct sk_buff *skb, int optoff) return false; } -const struct tlvtype_proc tlvprochopopt_lst[] = { +/* TLV parameter table functions and structures */ + +static void tlv_param_release(struct rcu_head *rcu) +{ + struct tlv_param *tp = container_of(rcu, struct tlv_param, rcu); + + vfree(tp); +} + +/* Default (unset) values for TX TLV parameters */ +static const struct tlv_param tlv_default_param = { + .tx_params.preferred_order = 0, + .tx_params.admin_perm = IPV6_TLV_PERM_NO_CHECK, + .tx_params.user_perm = IPV6_TLV_PERM_NONE, + .tx_params.class = 0, + .tx_params.align_mult = (4 - 1), /* Default alignment: 4n + 2 */ + .tx_params.align_off = 2, + .tx_params.min_data_len = 0, + .tx_params.max_data_len = 255, + .tx_params.data_len_mult = (1 - 1), /* No default length align */ + .tx_params.data_len_off = 0, +}; + +int __tlv_write_param(unsigned char type, const struct tlv_param *tp) +{ + static DEFINE_MUTEX(tlv_mutex); + struct tlv_param *old; + + mutex_lock(&tlv_mutex); + + old = rcu_dereference_protected(tlv_param_table[type], + lockdep_is_held(&tlv_mutex)); + + rcu_assign_pointer(tlv_param_table[type], tp); + + if (old != &tlv_default_param) { + /* Old table entry is not default. Assume that it was + * vmalloc'ed so schedule a vfree in rcu. + */ + call_rcu(&old->rcu, tlv_param_release); + } + + mutex_unlock(&tlv_mutex); + + return 0; +} + +int tlv_set_param(unsigned char type, + const struct tlv_rx_param *rx_param_tmpl, + const struct tlv_tx_param *tx_param_tmpl) +{ + struct tlv_param *tp; + int ret; + + if (type < 2) + return -EINVAL; + + /* Need to alloc and copy from templates */ + + tp = vmalloc(sizeof(*tp)); + if (!tp) + return -ENOMEM; + + memcpy(&tp->rx_params, rx_param_tmpl, sizeof(tp->rx_params)); + memcpy(&tp->tx_params, tx_param_tmpl, sizeof(tp->tx_params)); + + ret = __tlv_write_param(type, tp); + if (ret < 0) + vfree(tp); + + return ret; +} +EXPORT_SYMBOL(tlv_set_param); + +int tlv_unset_rx_param(unsigned char type) +{ + struct tlv_tx_param *tptx; + int ret; + + if (type < 2) + return -EINVAL; + + rcu_read_lock(); + + tptx = tlv_deref_tx_params(type); + + if (!tptx->preferred_order) + ret = __tlv_write_param(type, &tlv_default_param); + else + ret = tlv_set_param(type, &tlv_default_param.rx_params, tptx); + + rcu_read_unlock(); + + return ret; +} +EXPORT_SYMBOL(tlv_unset_rx_param); + +int tlv_set_rx_param(unsigned char type, struct tlv_rx_param *rx_param_tmpl) +{ + struct tlv_tx_param *tptx; + int ret; + + if (type < 2) + return -EINVAL; + + rcu_read_lock(); + + tptx = tlv_deref_tx_params(type); + + ret = tlv_set_param(type, rx_param_tmpl, tptx); + + rcu_read_unlock(); + + return ret; +} +EXPORT_SYMBOL(tlv_set_rx_param); + +int tlv_unset_tx_param(unsigned char type) +{ + struct tlv_rx_param *tprx; + int ret; + + if (type < 2) + return -EINVAL; + + rcu_read_lock(); + + tprx = tlv_deref_rx_params(type); + + if (!tprx->class) + ret = __tlv_write_param(type, &tlv_default_param); + else + ret = tlv_set_param(type, tprx, &tlv_default_param.tx_params); + + rcu_read_unlock(); + + return ret; +} +EXPORT_SYMBOL(tlv_unset_tx_param); + +int tlv_set_tx_param(unsigned char type, struct tlv_tx_param *tx_param_tmpl) +{ + struct tlv_rx_param *tprx; + int ret; + + if (type < 2) + return -EINVAL; + + rcu_read_lock(); + + tprx = tlv_deref_rx_params(type); + + ret = tlv_set_param(type, tprx, tx_param_tmpl); + + rcu_read_unlock(); + + return ret; +} +EXPORT_SYMBOL(tlv_set_tx_param); + +struct tlv_init_params { + int type; + struct tlv_tx_param t; + struct tlv_rx_param r; +}; + +static const struct tlv_init_params tlv_init_params[] __initconst = { { - .type = IPV6_TLV_ROUTERALERT, - .func = ipv6_hop_ra, + .type = IPV6_TLV_HAO, + + .t.preferred_order = TLV_PREF_ORDER_HAO, + .t.admin_perm = IPV6_TLV_PERM_NO_CHECK, + .t.user_perm = IPV6_TLV_PERM_NONE, + .t.class = IPV6_TLV_CLASS_FLAG_DSTOPT, + .t.align_mult = (8 - 1), /* Align to 8n + 6 */ + .t.align_off = 6, + .t.min_data_len = 16, + .t.max_data_len = 16, + .t.data_len_mult = (1 - 1), /* Fixed length */ + .t.data_len_off = 0, + + .r.func = ipv6_dest_hao, + .r.class = IPV6_TLV_CLASS_FLAG_DSTOPT, }, { - .type = IPV6_TLV_JUMBO, - .func = ipv6_hop_jumbo, + .type = IPV6_TLV_ROUTERALERT, + + .t.preferred_order = TLV_PREF_ORDER_ROUTERALERT, + .t.admin_perm = IPV6_TLV_PERM_NO_CHECK, + .t.user_perm = IPV6_TLV_PERM_NONE, + .t.class = IPV6_TLV_CLASS_FLAG_HOPOPT, + .t.align_mult = (2 - 1), /* Align to 2n */ + .t.align_off = 0, + .t.min_data_len = 2, + .t.max_data_len = 2, + .t.data_len_mult = (1 - 1), /* Fixed length */ + .t.data_len_off = 0, + + .r.func = ipv6_hop_ra, + .r.class = IPV6_TLV_CLASS_FLAG_HOPOPT, }, { - .type = IPV6_TLV_CALIPSO, - .func = ipv6_hop_calipso, + .type = IPV6_TLV_JUMBO, + + .t.preferred_order = TLV_PREF_ORDER_JUMBO, + .t.admin_perm = IPV6_TLV_PERM_NO_CHECK, + .t.user_perm = IPV6_TLV_PERM_NONE, + .t.class = IPV6_TLV_CLASS_FLAG_HOPOPT, + .t.align_mult = (4 - 1), /* Align to 4n + 2 */ + .t.align_off = 2, + .t.min_data_len = 4, + .t.max_data_len = 4, + .t.data_len_mult = (1 - 1), /* Fixed length */ + .t.data_len_off = 0, + + .r.func = ipv6_hop_jumbo, + .r.class = IPV6_TLV_CLASS_FLAG_HOPOPT, }, - { -1, } + { + .type = IPV6_TLV_CALIPSO, + + .t.preferred_order = TLV_PREF_ORDER_CALIPSO, + .t.admin_perm = IPV6_TLV_PERM_NO_CHECK, + .t.user_perm = IPV6_TLV_PERM_NONE, + .t.class = IPV6_TLV_CLASS_FLAG_HOPOPT, + .t.align_mult = (4 - 1), /* Align to 4n + 2 */ + .t.align_off = 2, + .t.min_data_len = 8, + .t.max_data_len = 252, + .t.data_len_mult = (4 - 1), /* Length is multiple of 4 */ + .t.data_len_off = 0, + + .r.func = ipv6_hop_calipso, + .r.class = IPV6_TLV_CLASS_FLAG_HOPOPT, + } }; + +static int __init exthdrs_init(void) +{ + unsigned long check_map[BITS_TO_LONGS(256)]; + const struct tlv_rx_param *rx_params; + const struct tlv_tx_param *tx_params; + int i, ret; + + memset(check_map, 0, sizeof(check_map)); + + for (i = 2; i < 256; i++) + RCU_INIT_POINTER(tlv_param_table[i], &tlv_default_param); + + for (i = 0; i < ARRAY_SIZE(tlv_init_params); i++) { + const struct tlv_init_params *tpi = &tlv_init_params[i]; + unsigned int order = tpi->t.preferred_order; + + WARN_ON(tpi->type < 2); /* Padding TLV initialized? */ + + if (order) { + WARN_ON(test_bit(order, check_map)); + set_bit(order, check_map); + tx_params = &tpi->t; + } else { + tx_params = &tlv_default_param.tx_params; + } + + if (tpi->r.class) + rx_params = &tpi->r; + else + rx_params = &tlv_default_param.rx_params; + + ret = tlv_set_param(tpi->type, rx_params, tx_params); + if (ret < 0) + goto fail; + } + + return 0; + +fail: + /* Undo anything that was set. */ + for (i = 0; i < ARRAY_SIZE(tlv_init_params); i++) + __tlv_write_param(tlv_init_params[i].type, &tlv_default_param); + + for (i = 2; i < 256; i++) + RCU_INIT_POINTER(tlv_param_table[i], NULL); + + return ret; +} +module_init(exthdrs_init); + +static void __exit exthdrs_fini(void) +{ +} +module_exit(exthdrs_fini); -- 2.7.4
Powered by blists - more mailing lists