__attribute__((packed)) is potentially unsafe on some systems. The symptom probably won't show up on an x86, which just makes the problem more insidious; testing on x86 systems won't reveal the problem. (On the x86, misaligned accesses are handled in hardware; if you dereference an int* pointer that points to an odd address, it will be a little slower than if it were properly aligned, but you'll get the correct result.)
On some other systems, such as SPARC, attempting to access a misaligned int object causes a bus error, crashing the program. On Mon, Oct 17, 2016 at 10:42 PM, David Lebrun <david.leb...@uclouvain.be> wrote: > This patch adds the necessary functions to compute and check the HMAC > signature > of an SR-enabled packet. Two HMAC algorithms are supported: hmac(sha1) and > hmac(sha256). > > In order to avoid dynamic memory allocation for each HMAC computation, > a per-cpu ring buffer is allocated for this purpose. > > Signed-off-by: David Lebrun <david.leb...@uclouvain.be> > --- > include/linux/seg6_hmac.h | 6 + > include/net/seg6_hmac.h | 61 ++++++ > include/uapi/linux/seg6_hmac.h | 20 ++ > net/ipv6/seg6_hmac.c | 432 > +++++++++++++++++++++++++++++++++++++++++ > 4 files changed, 519 insertions(+) > create mode 100644 include/linux/seg6_hmac.h > create mode 100644 include/net/seg6_hmac.h > create mode 100644 include/uapi/linux/seg6_hmac.h > create mode 100644 net/ipv6/seg6_hmac.c > > diff --git a/include/linux/seg6_hmac.h b/include/linux/seg6_hmac.h > new file mode 100644 > index 0000000..da437eb > --- /dev/null > +++ b/include/linux/seg6_hmac.h > @@ -0,0 +1,6 @@ > +#ifndef _LINUX_SEG6_HMAC_H > +#define _LINUX_SEG6_HMAC_H > + > +#include <uapi/linux/seg6_hmac.h> > + > +#endif > diff --git a/include/net/seg6_hmac.h b/include/net/seg6_hmac.h > new file mode 100644 > index 0000000..6e5ee6a > --- /dev/null > +++ b/include/net/seg6_hmac.h > @@ -0,0 +1,61 @@ > +/* > + * SR-IPv6 implementation > + * > + * Author: > + * David Lebrun <david.leb...@uclouvain.be> > + * > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * as published by the Free Software Foundation; either version > + * 2 of the License, or (at your option) any later version. > + */ > + > +#ifndef _NET_SEG6_HMAC_H > +#define _NET_SEG6_HMAC_H > + > +#include <net/flow.h> > +#include <net/ip6_fib.h> > +#include <net/sock.h> > +#include <linux/ip.h> > +#include <linux/ipv6.h> > +#include <linux/route.h> > +#include <net/seg6.h> > +#include <linux/seg6_hmac.h> > + > +#define SEG6_HMAC_MAX_DIGESTSIZE 160 > +#define SEG6_HMAC_RING_SIZE 256 > + > +struct seg6_hmac_info { > + struct list_head list; > + > + u32 hmackeyid; > + char secret[SEG6_HMAC_SECRET_LEN]; > + u8 slen; > + u8 alg_id; > +}; > + > +struct seg6_hmac_algo { > + u8 alg_id; > + char name[64]; > + struct crypto_shash * __percpu *tfms; > + struct shash_desc * __percpu *shashs; > +}; > + > +extern int seg6_hmac_compute(struct seg6_hmac_info *hinfo, > + struct ipv6_sr_hdr *hdr, struct in6_addr *saddr, > + u8 *output); > +extern struct seg6_hmac_info *seg6_hmac_info_lookup(struct net *net, u32 > key); > +extern int seg6_hmac_info_add(struct net *net, u32 key, > + struct seg6_hmac_info *hinfo); > +extern int seg6_hmac_info_del(struct net *net, u32 key, > + struct seg6_hmac_info *hinfo); > +extern int seg6_push_hmac(struct net *net, struct in6_addr *saddr, > + struct ipv6_sr_hdr *srh); > +extern bool seg6_hmac_validate_skb(struct sk_buff *skb); > +extern int seg6_hmac_init(void); > +extern void seg6_hmac_exit(void); > +extern int seg6_hmac_net_init(struct net *net); > +extern void seg6_hmac_net_exit(struct net *net); > + > +#endif > diff --git a/include/uapi/linux/seg6_hmac.h b/include/uapi/linux/seg6_hmac.h > new file mode 100644 > index 0000000..0b5eda7 > --- /dev/null > +++ b/include/uapi/linux/seg6_hmac.h > @@ -0,0 +1,20 @@ > +#ifndef _UAPI_LINUX_SEG6_HMAC_H > +#define _UAPI_LINUX_SEG6_HMAC_H > + > +#define SEG6_HMAC_SECRET_LEN 64 > +#define SEG6_HMAC_FIELD_LEN 32 > + > +struct sr6_tlv_hmac { > + __u8 type; > + __u8 len; > + __u16 reserved; > + __be32 hmackeyid; > + __u8 hmac[SEG6_HMAC_FIELD_LEN]; > +} __attribute__((packed)); > + > +enum { > + SEG6_HMAC_ALGO_SHA1 = 1, > + SEG6_HMAC_ALGO_SHA256 = 2, > +}; > + > +#endif > diff --git a/net/ipv6/seg6_hmac.c b/net/ipv6/seg6_hmac.c > new file mode 100644 > index 0000000..91bd59a > --- /dev/null > +++ b/net/ipv6/seg6_hmac.c > @@ -0,0 +1,432 @@ > +/* > + * SR-IPv6 implementation -- HMAC functions > + * > + * Author: > + * David Lebrun <david.leb...@uclouvain.be> > + * > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * as published by the Free Software Foundation; either version > + * 2 of the License, or (at your option) any later version. > + */ > + > +#include <linux/errno.h> > +#include <linux/types.h> > +#include <linux/socket.h> > +#include <linux/sockios.h> > +#include <linux/net.h> > +#include <linux/netdevice.h> > +#include <linux/in6.h> > +#include <linux/icmpv6.h> > +#include <linux/mroute6.h> > +#include <linux/slab.h> > + > +#include <linux/netfilter.h> > +#include <linux/netfilter_ipv6.h> > + > +#include <net/sock.h> > +#include <net/snmp.h> > + > +#include <net/ipv6.h> > +#include <net/protocol.h> > +#include <net/transp_v6.h> > +#include <net/rawv6.h> > +#include <net/ndisc.h> > +#include <net/ip6_route.h> > +#include <net/addrconf.h> > +#include <net/xfrm.h> > + > +#include <linux/cryptohash.h> > +#include <crypto/hash.h> > +#include <crypto/sha.h> > +#include <net/seg6.h> > +#include <net/genetlink.h> > +#include <net/seg6_hmac.h> > +#include <linux/random.h> > + > +static char * __percpu *hmac_ring; > + > +static struct seg6_hmac_algo hmac_algos[] = { > + { > + .alg_id = SEG6_HMAC_ALGO_SHA1, > + .name = "hmac(sha1)", > + }, > + { > + .alg_id = SEG6_HMAC_ALGO_SHA256, > + .name = "hmac(sha256)", > + }, > +}; > + > +static struct seg6_hmac_algo *__hmac_get_algo(u8 alg_id) > +{ > + int i, alg_count; > + struct seg6_hmac_algo *algo; > + > + alg_count = sizeof(hmac_algos) / sizeof(struct seg6_hmac_algo); > + for (i = 0; i < alg_count; i++) { > + algo = &hmac_algos[i]; > + if (algo->alg_id == alg_id) > + return algo; > + } > + > + return NULL; > +} > + > +static int __do_hmac(struct seg6_hmac_info *hinfo, const char *text, u8 > psize, > + u8 *output, int outlen) > +{ > + struct crypto_shash *tfm; > + struct shash_desc *shash; > + struct seg6_hmac_algo *algo; > + int ret, dgsize; > + > + algo = __hmac_get_algo(hinfo->alg_id); > + if (!algo) > + return -ENOENT; > + > + tfm = *this_cpu_ptr(algo->tfms); > + > + dgsize = crypto_shash_digestsize(tfm); > + if (dgsize > outlen) { > + pr_debug("sr-ipv6: __do_hmac: digest size too big (%d / > %d)\n", > + dgsize, outlen); > + return -ENOMEM; > + } > + > + ret = crypto_shash_setkey(tfm, hinfo->secret, hinfo->slen); > + if (ret < 0) { > + pr_debug("sr-ipv6: crypto_shash_setkey failed: err %d\n", > ret); > + goto failed; > + } > + > + shash = *this_cpu_ptr(algo->shashs); > + shash->tfm = tfm; > + > + ret = crypto_shash_digest(shash, text, psize, output); > + if (ret < 0) { > + pr_debug("sr-ipv6: crypto_shash_digest failed: err %d\n", > ret); > + goto failed; > + } > + > + return dgsize; > + > +failed: > + return ret; > +} > + > +int seg6_hmac_compute(struct seg6_hmac_info *hinfo, struct ipv6_sr_hdr *hdr, > + struct in6_addr *saddr, u8 *output) > +{ > + int plen, i, dgsize, wrsize; > + char *ring, *off; > + u8 tmp_out[SEG6_HMAC_MAX_DIGESTSIZE]; > + __be32 hmackeyid = cpu_to_be32(hinfo->hmackeyid); > + > + /* a 160-byte buffer for digest output allows to store highest known > + * hash function (RadioGatun) with up to 1216 bits > + */ > + > + /* saddr(16) + first_seg(1) + cleanup(1) + keyid(4) + seglist(16n) */ > + plen = 16 + 1 + 1 + 4 + (hdr->first_segment + 1) * 16; > + > + /* this limit allows for 14 segments */ > + if (plen >= SEG6_HMAC_RING_SIZE) > + return -EMSGSIZE; > + > + local_bh_disable(); > + ring = *this_cpu_ptr(hmac_ring); > + off = ring; > + memcpy(off, saddr, 16); > + off += 16; > + *off++ = hdr->first_segment; > + *off++ = !!(sr_get_flags(hdr) & SR6_FLAG_CLEANUP) << 7; > + memcpy(off, &hmackeyid, 4); > + off += 4; > + > + for (i = 0; i < hdr->first_segment + 1; i++) { > + memcpy(off, hdr->segments + i, 16); > + off += 16; > + } > + > + dgsize = __do_hmac(hinfo, ring, plen, tmp_out, > + SEG6_HMAC_MAX_DIGESTSIZE); > + local_bh_enable(); > + > + if (dgsize < 0) > + return dgsize; > + > + wrsize = SEG6_HMAC_FIELD_LEN; > + if (wrsize > dgsize) > + wrsize = dgsize; > + > + memset(output, 0, SEG6_HMAC_FIELD_LEN); > + memcpy(output, tmp_out, wrsize); > + > + return 0; > +} > +EXPORT_SYMBOL(seg6_hmac_compute); > + > +/* checks if an incoming SR-enabled packet's HMAC status matches > + * the incoming policy. > + * > + * called with rcu_read_lock() > + */ > +bool seg6_hmac_validate_skb(struct sk_buff *skb) > +{ > + struct inet6_dev *idev; > + struct ipv6_sr_hdr *srh; > + struct sr6_tlv_hmac *tlv; > + struct seg6_hmac_info *hinfo; > + struct net *net = dev_net(skb->dev); > + u8 hmac_output[SEG6_HMAC_FIELD_LEN]; > + > + idev = __in6_dev_get(skb->dev); > + > + srh = (struct ipv6_sr_hdr *)skb_transport_header(skb); > + > + tlv = seg6_get_tlv_hmac(srh); > + > + /* mandatory check but no tlv */ > + if (idev->cnf.seg6_require_hmac > 0 && !tlv) > + return false; > + > + /* no check */ > + if (idev->cnf.seg6_require_hmac < 0) > + return true; > + > + /* check only if present */ > + if (idev->cnf.seg6_require_hmac == 0 && !tlv) > + return true; > + > + /* now, seg6_require_hmac >= 0 && tlv */ > + > + hinfo = seg6_hmac_info_lookup(net, be32_to_cpu(tlv->hmackeyid)); > + if (!hinfo) > + return false; > + > + if (seg6_hmac_compute(hinfo, srh, &ipv6_hdr(skb)->saddr, hmac_output)) > + return false; > + > + if (memcmp(hmac_output, tlv->hmac, SEG6_HMAC_FIELD_LEN) != 0) > + return false; > + > + return true; > +} > + > +/* called with rcu_read_lock() */ > +struct seg6_hmac_info *seg6_hmac_info_lookup(struct net *net, u32 key) > +{ > + struct seg6_pernet_data *sdata = seg6_pernet(net); > + struct seg6_hmac_info *hinfo; > + > + list_for_each_entry_rcu(hinfo, &sdata->hmac_infos, list) { > + if (hinfo->hmackeyid == key) > + return hinfo; > + } > + > + return NULL; > +} > +EXPORT_SYMBOL(seg6_hmac_info_lookup); > + > +int seg6_hmac_info_add(struct net *net, u32 key, struct seg6_hmac_info > *hinfo) > +{ > + struct seg6_pernet_data *sdata = seg6_pernet(net); > + struct seg6_hmac_info *old_hinfo; > + > + old_hinfo = seg6_hmac_info_lookup(net, key); > + if (old_hinfo) > + return -EEXIST; > + > + list_add_rcu(&hinfo->list, &sdata->hmac_infos); > + > + return 0; > +} > +EXPORT_SYMBOL(seg6_hmac_info_add); > + > +int seg6_hmac_info_del(struct net *net, u32 key, struct seg6_hmac_info > *hinfo) > +{ > + struct seg6_hmac_info *tmp; > + > + tmp = seg6_hmac_info_lookup(net, key); > + if (!tmp) > + return -ENOENT; > + > + /* entry was replaced, ignore deletion */ > + if (tmp != hinfo) > + return -ENOENT; > + > + list_del_rcu(&hinfo->list); > + synchronize_net(); > + > + return 0; > +} > +EXPORT_SYMBOL(seg6_hmac_info_del); > + > +static void seg6_hmac_info_flush(struct net *net) > +{ > + struct seg6_pernet_data *sdata = seg6_pernet(net); > + struct seg6_hmac_info *hinfo; > + > + seg6_pernet_lock(net); > + while ((hinfo = list_first_or_null_rcu(&sdata->hmac_infos, > + struct seg6_hmac_info, > + list)) != NULL) { > + list_del_rcu(&hinfo->list); > + seg6_pernet_unlock(net); > + synchronize_net(); > + kfree(hinfo); > + seg6_pernet_lock(net); > + } > + > + seg6_pernet_unlock(net); > +} > + > +int seg6_push_hmac(struct net *net, struct in6_addr *saddr, > + struct ipv6_sr_hdr *srh) > +{ > + struct seg6_hmac_info *hinfo; > + int err = -ENOENT; > + struct sr6_tlv_hmac *tlv; > + > + tlv = seg6_get_tlv(srh, SR6_TLV_HMAC); > + if (!tlv) > + return -EINVAL; > + > + rcu_read_lock(); > + > + hinfo = seg6_hmac_info_lookup(net, be32_to_cpu(tlv->hmackeyid)); > + if (!hinfo) > + goto out; > + > + memset(tlv->hmac, 0, SEG6_HMAC_FIELD_LEN); > + err = seg6_hmac_compute(hinfo, srh, saddr, tlv->hmac); > + > +out: > + rcu_read_unlock(); > + return err; > +} > +EXPORT_SYMBOL(seg6_push_hmac); > + > +static int seg6_hmac_init_ring(void) > +{ > + int i; > + > + hmac_ring = alloc_percpu(char *); > + > + if (!hmac_ring) > + return -ENOMEM; > + > + for_each_possible_cpu(i) { > + char *ring = kzalloc(SEG6_HMAC_RING_SIZE, GFP_KERNEL); > + > + if (!ring) > + return -ENOMEM; > + > + *per_cpu_ptr(hmac_ring, i) = ring; > + } > + > + return 0; > +} > + > +static int seg6_hmac_init_algo(void) > +{ > + int i, alg_count, cpu; > + struct seg6_hmac_algo *algo; > + struct crypto_shash *tfm; > + struct shash_desc *shash; > + > + alg_count = sizeof(hmac_algos) / sizeof(struct seg6_hmac_algo); > + > + for (i = 0; i < alg_count; i++) { > + int shsize; > + struct crypto_shash **p_tfm; > + > + algo = &hmac_algos[i]; > + algo->tfms = alloc_percpu(struct crypto_shash *); > + if (!algo->tfms) > + return -ENOMEM; > + > + for_each_possible_cpu(cpu) { > + tfm = crypto_alloc_shash(algo->name, 0, GFP_KERNEL); > + if (IS_ERR(tfm)) > + return PTR_ERR(tfm); > + p_tfm = per_cpu_ptr(algo->tfms, cpu); > + *p_tfm = tfm; > + } > + > + p_tfm = this_cpu_ptr(algo->tfms); > + tfm = *p_tfm; > + > + shsize = sizeof(*shash) + crypto_shash_descsize(tfm); > + > + algo->shashs = alloc_percpu(struct shash_desc *); > + if (!algo->shashs) > + return -ENOMEM; > + > + for_each_possible_cpu(cpu) { > + shash = kzalloc(shsize, GFP_KERNEL); > + if (!shash) > + return -ENOMEM; > + *per_cpu_ptr(algo->shashs, cpu) = shash; > + } > + } > + > + return 0; > +} > + > +int __init seg6_hmac_init(void) > +{ > + int ret; > + > + ret = seg6_hmac_init_ring(); > + if (ret < 0) > + goto out; > + > + ret = seg6_hmac_init_algo(); > + > +out: > + return ret; > +} > + > +int __net_init seg6_hmac_net_init(struct net *net) > +{ > + struct seg6_pernet_data *sdata = seg6_pernet(net); > + > + INIT_LIST_HEAD(&sdata->hmac_infos); > + return 0; > +} > + > +void __exit seg6_hmac_exit(void) > +{ > + int i, alg_count, cpu; > + struct seg6_hmac_algo *algo = NULL; > + > + for_each_possible_cpu(i) { > + char *ring = *per_cpu_ptr(hmac_ring, i); > + > + kfree(ring); > + } > + free_percpu(hmac_ring); > + > + alg_count = sizeof(hmac_algos) / sizeof(struct seg6_hmac_algo); > + for (i = 0; i < alg_count; i++) { > + algo = &hmac_algos[i]; > + for_each_possible_cpu(cpu) { > + struct crypto_shash *tfm; > + struct shash_desc *shash; > + > + shash = *per_cpu_ptr(algo->shashs, cpu); > + kfree(shash); > + tfm = *per_cpu_ptr(algo->tfms, cpu); > + crypto_free_shash(tfm); > + } > + free_percpu(algo->tfms); > + free_percpu(algo->shashs); > + } > +} > + > +void __net_exit seg6_hmac_net_exit(struct net *net) > +{ > + seg6_hmac_info_flush(net); > +} > -- > 2.7.3 >