162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci#define pr_fmt(fmt) "IPsec: " fmt 362306a36Sopenharmony_ci 462306a36Sopenharmony_ci#include <crypto/algapi.h> 562306a36Sopenharmony_ci#include <crypto/hash.h> 662306a36Sopenharmony_ci#include <linux/err.h> 762306a36Sopenharmony_ci#include <linux/module.h> 862306a36Sopenharmony_ci#include <linux/slab.h> 962306a36Sopenharmony_ci#include <net/ip.h> 1062306a36Sopenharmony_ci#include <net/xfrm.h> 1162306a36Sopenharmony_ci#include <net/ah.h> 1262306a36Sopenharmony_ci#include <linux/crypto.h> 1362306a36Sopenharmony_ci#include <linux/pfkeyv2.h> 1462306a36Sopenharmony_ci#include <linux/scatterlist.h> 1562306a36Sopenharmony_ci#include <net/icmp.h> 1662306a36Sopenharmony_ci#include <net/protocol.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_cistruct ah_skb_cb { 1962306a36Sopenharmony_ci struct xfrm_skb_cb xfrm; 2062306a36Sopenharmony_ci void *tmp; 2162306a36Sopenharmony_ci}; 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#define AH_SKB_CB(__skb) ((struct ah_skb_cb *)&((__skb)->cb[0])) 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_cistatic void *ah_alloc_tmp(struct crypto_ahash *ahash, int nfrags, 2662306a36Sopenharmony_ci unsigned int size) 2762306a36Sopenharmony_ci{ 2862306a36Sopenharmony_ci unsigned int len; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci len = size + crypto_ahash_digestsize(ahash) + 3162306a36Sopenharmony_ci (crypto_ahash_alignmask(ahash) & 3262306a36Sopenharmony_ci ~(crypto_tfm_ctx_alignment() - 1)); 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci len = ALIGN(len, crypto_tfm_ctx_alignment()); 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci len += sizeof(struct ahash_request) + crypto_ahash_reqsize(ahash); 3762306a36Sopenharmony_ci len = ALIGN(len, __alignof__(struct scatterlist)); 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci len += sizeof(struct scatterlist) * nfrags; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci return kmalloc(len, GFP_ATOMIC); 4262306a36Sopenharmony_ci} 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistatic inline u8 *ah_tmp_auth(void *tmp, unsigned int offset) 4562306a36Sopenharmony_ci{ 4662306a36Sopenharmony_ci return tmp + offset; 4762306a36Sopenharmony_ci} 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_cistatic inline u8 *ah_tmp_icv(struct crypto_ahash *ahash, void *tmp, 5062306a36Sopenharmony_ci unsigned int offset) 5162306a36Sopenharmony_ci{ 5262306a36Sopenharmony_ci return PTR_ALIGN((u8 *)tmp + offset, crypto_ahash_alignmask(ahash) + 1); 5362306a36Sopenharmony_ci} 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cistatic inline struct ahash_request *ah_tmp_req(struct crypto_ahash *ahash, 5662306a36Sopenharmony_ci u8 *icv) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci struct ahash_request *req; 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci req = (void *)PTR_ALIGN(icv + crypto_ahash_digestsize(ahash), 6162306a36Sopenharmony_ci crypto_tfm_ctx_alignment()); 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci ahash_request_set_tfm(req, ahash); 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci return req; 6662306a36Sopenharmony_ci} 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_cistatic inline struct scatterlist *ah_req_sg(struct crypto_ahash *ahash, 6962306a36Sopenharmony_ci struct ahash_request *req) 7062306a36Sopenharmony_ci{ 7162306a36Sopenharmony_ci return (void *)ALIGN((unsigned long)(req + 1) + 7262306a36Sopenharmony_ci crypto_ahash_reqsize(ahash), 7362306a36Sopenharmony_ci __alignof__(struct scatterlist)); 7462306a36Sopenharmony_ci} 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci/* Clear mutable options and find final destination to substitute 7762306a36Sopenharmony_ci * into IP header for icv calculation. Options are already checked 7862306a36Sopenharmony_ci * for validity, so paranoia is not required. */ 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_cistatic int ip_clear_mutable_options(const struct iphdr *iph, __be32 *daddr) 8162306a36Sopenharmony_ci{ 8262306a36Sopenharmony_ci unsigned char *optptr = (unsigned char *)(iph+1); 8362306a36Sopenharmony_ci int l = iph->ihl*4 - sizeof(struct iphdr); 8462306a36Sopenharmony_ci int optlen; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci while (l > 0) { 8762306a36Sopenharmony_ci switch (*optptr) { 8862306a36Sopenharmony_ci case IPOPT_END: 8962306a36Sopenharmony_ci return 0; 9062306a36Sopenharmony_ci case IPOPT_NOOP: 9162306a36Sopenharmony_ci l--; 9262306a36Sopenharmony_ci optptr++; 9362306a36Sopenharmony_ci continue; 9462306a36Sopenharmony_ci } 9562306a36Sopenharmony_ci optlen = optptr[1]; 9662306a36Sopenharmony_ci if (optlen<2 || optlen>l) 9762306a36Sopenharmony_ci return -EINVAL; 9862306a36Sopenharmony_ci switch (*optptr) { 9962306a36Sopenharmony_ci case IPOPT_SEC: 10062306a36Sopenharmony_ci case 0x85: /* Some "Extended Security" crap. */ 10162306a36Sopenharmony_ci case IPOPT_CIPSO: 10262306a36Sopenharmony_ci case IPOPT_RA: 10362306a36Sopenharmony_ci case 0x80|21: /* RFC1770 */ 10462306a36Sopenharmony_ci break; 10562306a36Sopenharmony_ci case IPOPT_LSRR: 10662306a36Sopenharmony_ci case IPOPT_SSRR: 10762306a36Sopenharmony_ci if (optlen < 6) 10862306a36Sopenharmony_ci return -EINVAL; 10962306a36Sopenharmony_ci memcpy(daddr, optptr+optlen-4, 4); 11062306a36Sopenharmony_ci fallthrough; 11162306a36Sopenharmony_ci default: 11262306a36Sopenharmony_ci memset(optptr, 0, optlen); 11362306a36Sopenharmony_ci } 11462306a36Sopenharmony_ci l -= optlen; 11562306a36Sopenharmony_ci optptr += optlen; 11662306a36Sopenharmony_ci } 11762306a36Sopenharmony_ci return 0; 11862306a36Sopenharmony_ci} 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_cistatic void ah_output_done(void *data, int err) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci u8 *icv; 12362306a36Sopenharmony_ci struct iphdr *iph; 12462306a36Sopenharmony_ci struct sk_buff *skb = data; 12562306a36Sopenharmony_ci struct xfrm_state *x = skb_dst(skb)->xfrm; 12662306a36Sopenharmony_ci struct ah_data *ahp = x->data; 12762306a36Sopenharmony_ci struct iphdr *top_iph = ip_hdr(skb); 12862306a36Sopenharmony_ci struct ip_auth_hdr *ah = ip_auth_hdr(skb); 12962306a36Sopenharmony_ci int ihl = ip_hdrlen(skb); 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci iph = AH_SKB_CB(skb)->tmp; 13262306a36Sopenharmony_ci icv = ah_tmp_icv(ahp->ahash, iph, ihl); 13362306a36Sopenharmony_ci memcpy(ah->auth_data, icv, ahp->icv_trunc_len); 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci top_iph->tos = iph->tos; 13662306a36Sopenharmony_ci top_iph->ttl = iph->ttl; 13762306a36Sopenharmony_ci top_iph->frag_off = iph->frag_off; 13862306a36Sopenharmony_ci if (top_iph->ihl != 5) { 13962306a36Sopenharmony_ci top_iph->daddr = iph->daddr; 14062306a36Sopenharmony_ci memcpy(top_iph+1, iph+1, top_iph->ihl*4 - sizeof(struct iphdr)); 14162306a36Sopenharmony_ci } 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci kfree(AH_SKB_CB(skb)->tmp); 14462306a36Sopenharmony_ci xfrm_output_resume(skb->sk, skb, err); 14562306a36Sopenharmony_ci} 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_cistatic int ah_output(struct xfrm_state *x, struct sk_buff *skb) 14862306a36Sopenharmony_ci{ 14962306a36Sopenharmony_ci int err; 15062306a36Sopenharmony_ci int nfrags; 15162306a36Sopenharmony_ci int ihl; 15262306a36Sopenharmony_ci u8 *icv; 15362306a36Sopenharmony_ci struct sk_buff *trailer; 15462306a36Sopenharmony_ci struct crypto_ahash *ahash; 15562306a36Sopenharmony_ci struct ahash_request *req; 15662306a36Sopenharmony_ci struct scatterlist *sg; 15762306a36Sopenharmony_ci struct iphdr *iph, *top_iph; 15862306a36Sopenharmony_ci struct ip_auth_hdr *ah; 15962306a36Sopenharmony_ci struct ah_data *ahp; 16062306a36Sopenharmony_ci int seqhi_len = 0; 16162306a36Sopenharmony_ci __be32 *seqhi; 16262306a36Sopenharmony_ci int sglists = 0; 16362306a36Sopenharmony_ci struct scatterlist *seqhisg; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci ahp = x->data; 16662306a36Sopenharmony_ci ahash = ahp->ahash; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci if ((err = skb_cow_data(skb, 0, &trailer)) < 0) 16962306a36Sopenharmony_ci goto out; 17062306a36Sopenharmony_ci nfrags = err; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci skb_push(skb, -skb_network_offset(skb)); 17362306a36Sopenharmony_ci ah = ip_auth_hdr(skb); 17462306a36Sopenharmony_ci ihl = ip_hdrlen(skb); 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci if (x->props.flags & XFRM_STATE_ESN) { 17762306a36Sopenharmony_ci sglists = 1; 17862306a36Sopenharmony_ci seqhi_len = sizeof(*seqhi); 17962306a36Sopenharmony_ci } 18062306a36Sopenharmony_ci err = -ENOMEM; 18162306a36Sopenharmony_ci iph = ah_alloc_tmp(ahash, nfrags + sglists, ihl + seqhi_len); 18262306a36Sopenharmony_ci if (!iph) 18362306a36Sopenharmony_ci goto out; 18462306a36Sopenharmony_ci seqhi = (__be32 *)((char *)iph + ihl); 18562306a36Sopenharmony_ci icv = ah_tmp_icv(ahash, seqhi, seqhi_len); 18662306a36Sopenharmony_ci req = ah_tmp_req(ahash, icv); 18762306a36Sopenharmony_ci sg = ah_req_sg(ahash, req); 18862306a36Sopenharmony_ci seqhisg = sg + nfrags; 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci memset(ah->auth_data, 0, ahp->icv_trunc_len); 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci top_iph = ip_hdr(skb); 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci iph->tos = top_iph->tos; 19562306a36Sopenharmony_ci iph->ttl = top_iph->ttl; 19662306a36Sopenharmony_ci iph->frag_off = top_iph->frag_off; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci if (top_iph->ihl != 5) { 19962306a36Sopenharmony_ci iph->daddr = top_iph->daddr; 20062306a36Sopenharmony_ci memcpy(iph+1, top_iph+1, top_iph->ihl*4 - sizeof(struct iphdr)); 20162306a36Sopenharmony_ci err = ip_clear_mutable_options(top_iph, &top_iph->daddr); 20262306a36Sopenharmony_ci if (err) 20362306a36Sopenharmony_ci goto out_free; 20462306a36Sopenharmony_ci } 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci ah->nexthdr = *skb_mac_header(skb); 20762306a36Sopenharmony_ci *skb_mac_header(skb) = IPPROTO_AH; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci top_iph->tos = 0; 21062306a36Sopenharmony_ci top_iph->tot_len = htons(skb->len); 21162306a36Sopenharmony_ci top_iph->frag_off = 0; 21262306a36Sopenharmony_ci top_iph->ttl = 0; 21362306a36Sopenharmony_ci top_iph->check = 0; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci if (x->props.flags & XFRM_STATE_ALIGN4) 21662306a36Sopenharmony_ci ah->hdrlen = (XFRM_ALIGN4(sizeof(*ah) + ahp->icv_trunc_len) >> 2) - 2; 21762306a36Sopenharmony_ci else 21862306a36Sopenharmony_ci ah->hdrlen = (XFRM_ALIGN8(sizeof(*ah) + ahp->icv_trunc_len) >> 2) - 2; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci ah->reserved = 0; 22162306a36Sopenharmony_ci ah->spi = x->id.spi; 22262306a36Sopenharmony_ci ah->seq_no = htonl(XFRM_SKB_CB(skb)->seq.output.low); 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci sg_init_table(sg, nfrags + sglists); 22562306a36Sopenharmony_ci err = skb_to_sgvec_nomark(skb, sg, 0, skb->len); 22662306a36Sopenharmony_ci if (unlikely(err < 0)) 22762306a36Sopenharmony_ci goto out_free; 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci if (x->props.flags & XFRM_STATE_ESN) { 23062306a36Sopenharmony_ci /* Attach seqhi sg right after packet payload */ 23162306a36Sopenharmony_ci *seqhi = htonl(XFRM_SKB_CB(skb)->seq.output.hi); 23262306a36Sopenharmony_ci sg_set_buf(seqhisg, seqhi, seqhi_len); 23362306a36Sopenharmony_ci } 23462306a36Sopenharmony_ci ahash_request_set_crypt(req, sg, icv, skb->len + seqhi_len); 23562306a36Sopenharmony_ci ahash_request_set_callback(req, 0, ah_output_done, skb); 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci AH_SKB_CB(skb)->tmp = iph; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci err = crypto_ahash_digest(req); 24062306a36Sopenharmony_ci if (err) { 24162306a36Sopenharmony_ci if (err == -EINPROGRESS) 24262306a36Sopenharmony_ci goto out; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci if (err == -ENOSPC) 24562306a36Sopenharmony_ci err = NET_XMIT_DROP; 24662306a36Sopenharmony_ci goto out_free; 24762306a36Sopenharmony_ci } 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci memcpy(ah->auth_data, icv, ahp->icv_trunc_len); 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci top_iph->tos = iph->tos; 25262306a36Sopenharmony_ci top_iph->ttl = iph->ttl; 25362306a36Sopenharmony_ci top_iph->frag_off = iph->frag_off; 25462306a36Sopenharmony_ci if (top_iph->ihl != 5) { 25562306a36Sopenharmony_ci top_iph->daddr = iph->daddr; 25662306a36Sopenharmony_ci memcpy(top_iph+1, iph+1, top_iph->ihl*4 - sizeof(struct iphdr)); 25762306a36Sopenharmony_ci } 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ciout_free: 26062306a36Sopenharmony_ci kfree(iph); 26162306a36Sopenharmony_ciout: 26262306a36Sopenharmony_ci return err; 26362306a36Sopenharmony_ci} 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_cistatic void ah_input_done(void *data, int err) 26662306a36Sopenharmony_ci{ 26762306a36Sopenharmony_ci u8 *auth_data; 26862306a36Sopenharmony_ci u8 *icv; 26962306a36Sopenharmony_ci struct iphdr *work_iph; 27062306a36Sopenharmony_ci struct sk_buff *skb = data; 27162306a36Sopenharmony_ci struct xfrm_state *x = xfrm_input_state(skb); 27262306a36Sopenharmony_ci struct ah_data *ahp = x->data; 27362306a36Sopenharmony_ci struct ip_auth_hdr *ah = ip_auth_hdr(skb); 27462306a36Sopenharmony_ci int ihl = ip_hdrlen(skb); 27562306a36Sopenharmony_ci int ah_hlen = (ah->hdrlen + 2) << 2; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci if (err) 27862306a36Sopenharmony_ci goto out; 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci work_iph = AH_SKB_CB(skb)->tmp; 28162306a36Sopenharmony_ci auth_data = ah_tmp_auth(work_iph, ihl); 28262306a36Sopenharmony_ci icv = ah_tmp_icv(ahp->ahash, auth_data, ahp->icv_trunc_len); 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci err = crypto_memneq(icv, auth_data, ahp->icv_trunc_len) ? -EBADMSG : 0; 28562306a36Sopenharmony_ci if (err) 28662306a36Sopenharmony_ci goto out; 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci err = ah->nexthdr; 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci skb->network_header += ah_hlen; 29162306a36Sopenharmony_ci memcpy(skb_network_header(skb), work_iph, ihl); 29262306a36Sopenharmony_ci __skb_pull(skb, ah_hlen + ihl); 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci if (x->props.mode == XFRM_MODE_TUNNEL) 29562306a36Sopenharmony_ci skb_reset_transport_header(skb); 29662306a36Sopenharmony_ci else 29762306a36Sopenharmony_ci skb_set_transport_header(skb, -ihl); 29862306a36Sopenharmony_ciout: 29962306a36Sopenharmony_ci kfree(AH_SKB_CB(skb)->tmp); 30062306a36Sopenharmony_ci xfrm_input_resume(skb, err); 30162306a36Sopenharmony_ci} 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_cistatic int ah_input(struct xfrm_state *x, struct sk_buff *skb) 30462306a36Sopenharmony_ci{ 30562306a36Sopenharmony_ci int ah_hlen; 30662306a36Sopenharmony_ci int ihl; 30762306a36Sopenharmony_ci int nexthdr; 30862306a36Sopenharmony_ci int nfrags; 30962306a36Sopenharmony_ci u8 *auth_data; 31062306a36Sopenharmony_ci u8 *icv; 31162306a36Sopenharmony_ci struct sk_buff *trailer; 31262306a36Sopenharmony_ci struct crypto_ahash *ahash; 31362306a36Sopenharmony_ci struct ahash_request *req; 31462306a36Sopenharmony_ci struct scatterlist *sg; 31562306a36Sopenharmony_ci struct iphdr *iph, *work_iph; 31662306a36Sopenharmony_ci struct ip_auth_hdr *ah; 31762306a36Sopenharmony_ci struct ah_data *ahp; 31862306a36Sopenharmony_ci int err = -ENOMEM; 31962306a36Sopenharmony_ci int seqhi_len = 0; 32062306a36Sopenharmony_ci __be32 *seqhi; 32162306a36Sopenharmony_ci int sglists = 0; 32262306a36Sopenharmony_ci struct scatterlist *seqhisg; 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci if (!pskb_may_pull(skb, sizeof(*ah))) 32562306a36Sopenharmony_ci goto out; 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci ah = (struct ip_auth_hdr *)skb->data; 32862306a36Sopenharmony_ci ahp = x->data; 32962306a36Sopenharmony_ci ahash = ahp->ahash; 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci nexthdr = ah->nexthdr; 33262306a36Sopenharmony_ci ah_hlen = (ah->hdrlen + 2) << 2; 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci if (x->props.flags & XFRM_STATE_ALIGN4) { 33562306a36Sopenharmony_ci if (ah_hlen != XFRM_ALIGN4(sizeof(*ah) + ahp->icv_full_len) && 33662306a36Sopenharmony_ci ah_hlen != XFRM_ALIGN4(sizeof(*ah) + ahp->icv_trunc_len)) 33762306a36Sopenharmony_ci goto out; 33862306a36Sopenharmony_ci } else { 33962306a36Sopenharmony_ci if (ah_hlen != XFRM_ALIGN8(sizeof(*ah) + ahp->icv_full_len) && 34062306a36Sopenharmony_ci ah_hlen != XFRM_ALIGN8(sizeof(*ah) + ahp->icv_trunc_len)) 34162306a36Sopenharmony_ci goto out; 34262306a36Sopenharmony_ci } 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci if (!pskb_may_pull(skb, ah_hlen)) 34562306a36Sopenharmony_ci goto out; 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci /* We are going to _remove_ AH header to keep sockets happy, 34862306a36Sopenharmony_ci * so... Later this can change. */ 34962306a36Sopenharmony_ci if (skb_unclone(skb, GFP_ATOMIC)) 35062306a36Sopenharmony_ci goto out; 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci skb->ip_summed = CHECKSUM_NONE; 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci if ((err = skb_cow_data(skb, 0, &trailer)) < 0) 35662306a36Sopenharmony_ci goto out; 35762306a36Sopenharmony_ci nfrags = err; 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci ah = (struct ip_auth_hdr *)skb->data; 36062306a36Sopenharmony_ci iph = ip_hdr(skb); 36162306a36Sopenharmony_ci ihl = ip_hdrlen(skb); 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci if (x->props.flags & XFRM_STATE_ESN) { 36462306a36Sopenharmony_ci sglists = 1; 36562306a36Sopenharmony_ci seqhi_len = sizeof(*seqhi); 36662306a36Sopenharmony_ci } 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_ci work_iph = ah_alloc_tmp(ahash, nfrags + sglists, ihl + 36962306a36Sopenharmony_ci ahp->icv_trunc_len + seqhi_len); 37062306a36Sopenharmony_ci if (!work_iph) { 37162306a36Sopenharmony_ci err = -ENOMEM; 37262306a36Sopenharmony_ci goto out; 37362306a36Sopenharmony_ci } 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci seqhi = (__be32 *)((char *)work_iph + ihl); 37662306a36Sopenharmony_ci auth_data = ah_tmp_auth(seqhi, seqhi_len); 37762306a36Sopenharmony_ci icv = ah_tmp_icv(ahash, auth_data, ahp->icv_trunc_len); 37862306a36Sopenharmony_ci req = ah_tmp_req(ahash, icv); 37962306a36Sopenharmony_ci sg = ah_req_sg(ahash, req); 38062306a36Sopenharmony_ci seqhisg = sg + nfrags; 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci memcpy(work_iph, iph, ihl); 38362306a36Sopenharmony_ci memcpy(auth_data, ah->auth_data, ahp->icv_trunc_len); 38462306a36Sopenharmony_ci memset(ah->auth_data, 0, ahp->icv_trunc_len); 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci iph->ttl = 0; 38762306a36Sopenharmony_ci iph->tos = 0; 38862306a36Sopenharmony_ci iph->frag_off = 0; 38962306a36Sopenharmony_ci iph->check = 0; 39062306a36Sopenharmony_ci if (ihl > sizeof(*iph)) { 39162306a36Sopenharmony_ci __be32 dummy; 39262306a36Sopenharmony_ci err = ip_clear_mutable_options(iph, &dummy); 39362306a36Sopenharmony_ci if (err) 39462306a36Sopenharmony_ci goto out_free; 39562306a36Sopenharmony_ci } 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci skb_push(skb, ihl); 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_ci sg_init_table(sg, nfrags + sglists); 40062306a36Sopenharmony_ci err = skb_to_sgvec_nomark(skb, sg, 0, skb->len); 40162306a36Sopenharmony_ci if (unlikely(err < 0)) 40262306a36Sopenharmony_ci goto out_free; 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci if (x->props.flags & XFRM_STATE_ESN) { 40562306a36Sopenharmony_ci /* Attach seqhi sg right after packet payload */ 40662306a36Sopenharmony_ci *seqhi = XFRM_SKB_CB(skb)->seq.input.hi; 40762306a36Sopenharmony_ci sg_set_buf(seqhisg, seqhi, seqhi_len); 40862306a36Sopenharmony_ci } 40962306a36Sopenharmony_ci ahash_request_set_crypt(req, sg, icv, skb->len + seqhi_len); 41062306a36Sopenharmony_ci ahash_request_set_callback(req, 0, ah_input_done, skb); 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci AH_SKB_CB(skb)->tmp = work_iph; 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci err = crypto_ahash_digest(req); 41562306a36Sopenharmony_ci if (err) { 41662306a36Sopenharmony_ci if (err == -EINPROGRESS) 41762306a36Sopenharmony_ci goto out; 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_ci goto out_free; 42062306a36Sopenharmony_ci } 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci err = crypto_memneq(icv, auth_data, ahp->icv_trunc_len) ? -EBADMSG : 0; 42362306a36Sopenharmony_ci if (err) 42462306a36Sopenharmony_ci goto out_free; 42562306a36Sopenharmony_ci 42662306a36Sopenharmony_ci skb->network_header += ah_hlen; 42762306a36Sopenharmony_ci memcpy(skb_network_header(skb), work_iph, ihl); 42862306a36Sopenharmony_ci __skb_pull(skb, ah_hlen + ihl); 42962306a36Sopenharmony_ci if (x->props.mode == XFRM_MODE_TUNNEL) 43062306a36Sopenharmony_ci skb_reset_transport_header(skb); 43162306a36Sopenharmony_ci else 43262306a36Sopenharmony_ci skb_set_transport_header(skb, -ihl); 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci err = nexthdr; 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_ciout_free: 43762306a36Sopenharmony_ci kfree (work_iph); 43862306a36Sopenharmony_ciout: 43962306a36Sopenharmony_ci return err; 44062306a36Sopenharmony_ci} 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_cistatic int ah4_err(struct sk_buff *skb, u32 info) 44362306a36Sopenharmony_ci{ 44462306a36Sopenharmony_ci struct net *net = dev_net(skb->dev); 44562306a36Sopenharmony_ci const struct iphdr *iph = (const struct iphdr *)skb->data; 44662306a36Sopenharmony_ci struct ip_auth_hdr *ah = (struct ip_auth_hdr *)(skb->data+(iph->ihl<<2)); 44762306a36Sopenharmony_ci struct xfrm_state *x; 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci switch (icmp_hdr(skb)->type) { 45062306a36Sopenharmony_ci case ICMP_DEST_UNREACH: 45162306a36Sopenharmony_ci if (icmp_hdr(skb)->code != ICMP_FRAG_NEEDED) 45262306a36Sopenharmony_ci return 0; 45362306a36Sopenharmony_ci break; 45462306a36Sopenharmony_ci case ICMP_REDIRECT: 45562306a36Sopenharmony_ci break; 45662306a36Sopenharmony_ci default: 45762306a36Sopenharmony_ci return 0; 45862306a36Sopenharmony_ci } 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_ci x = xfrm_state_lookup(net, skb->mark, (const xfrm_address_t *)&iph->daddr, 46162306a36Sopenharmony_ci ah->spi, IPPROTO_AH, AF_INET); 46262306a36Sopenharmony_ci if (!x) 46362306a36Sopenharmony_ci return 0; 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci if (icmp_hdr(skb)->type == ICMP_DEST_UNREACH) 46662306a36Sopenharmony_ci ipv4_update_pmtu(skb, net, info, 0, IPPROTO_AH); 46762306a36Sopenharmony_ci else 46862306a36Sopenharmony_ci ipv4_redirect(skb, net, 0, IPPROTO_AH); 46962306a36Sopenharmony_ci xfrm_state_put(x); 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci return 0; 47262306a36Sopenharmony_ci} 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_cistatic int ah_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack) 47562306a36Sopenharmony_ci{ 47662306a36Sopenharmony_ci struct ah_data *ahp = NULL; 47762306a36Sopenharmony_ci struct xfrm_algo_desc *aalg_desc; 47862306a36Sopenharmony_ci struct crypto_ahash *ahash; 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci if (!x->aalg) { 48162306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "AH requires a state with an AUTH algorithm"); 48262306a36Sopenharmony_ci goto error; 48362306a36Sopenharmony_ci } 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_ci if (x->encap) { 48662306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "AH is not compatible with encapsulation"); 48762306a36Sopenharmony_ci goto error; 48862306a36Sopenharmony_ci } 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_ci ahp = kzalloc(sizeof(*ahp), GFP_KERNEL); 49162306a36Sopenharmony_ci if (!ahp) 49262306a36Sopenharmony_ci return -ENOMEM; 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_ci ahash = crypto_alloc_ahash(x->aalg->alg_name, 0, 0); 49562306a36Sopenharmony_ci if (IS_ERR(ahash)) { 49662306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Kernel was unable to initialize cryptographic operations"); 49762306a36Sopenharmony_ci goto error; 49862306a36Sopenharmony_ci } 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_ci ahp->ahash = ahash; 50162306a36Sopenharmony_ci if (crypto_ahash_setkey(ahash, x->aalg->alg_key, 50262306a36Sopenharmony_ci (x->aalg->alg_key_len + 7) / 8)) { 50362306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Kernel was unable to initialize cryptographic operations"); 50462306a36Sopenharmony_ci goto error; 50562306a36Sopenharmony_ci } 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_ci /* 50862306a36Sopenharmony_ci * Lookup the algorithm description maintained by xfrm_algo, 50962306a36Sopenharmony_ci * verify crypto transform properties, and store information 51062306a36Sopenharmony_ci * we need for AH processing. This lookup cannot fail here 51162306a36Sopenharmony_ci * after a successful crypto_alloc_ahash(). 51262306a36Sopenharmony_ci */ 51362306a36Sopenharmony_ci aalg_desc = xfrm_aalg_get_byname(x->aalg->alg_name, 0); 51462306a36Sopenharmony_ci BUG_ON(!aalg_desc); 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ci if (aalg_desc->uinfo.auth.icv_fullbits/8 != 51762306a36Sopenharmony_ci crypto_ahash_digestsize(ahash)) { 51862306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Kernel was unable to initialize cryptographic operations"); 51962306a36Sopenharmony_ci goto error; 52062306a36Sopenharmony_ci } 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_ci ahp->icv_full_len = aalg_desc->uinfo.auth.icv_fullbits/8; 52362306a36Sopenharmony_ci ahp->icv_trunc_len = x->aalg->alg_trunc_len/8; 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_ci if (x->props.flags & XFRM_STATE_ALIGN4) 52662306a36Sopenharmony_ci x->props.header_len = XFRM_ALIGN4(sizeof(struct ip_auth_hdr) + 52762306a36Sopenharmony_ci ahp->icv_trunc_len); 52862306a36Sopenharmony_ci else 52962306a36Sopenharmony_ci x->props.header_len = XFRM_ALIGN8(sizeof(struct ip_auth_hdr) + 53062306a36Sopenharmony_ci ahp->icv_trunc_len); 53162306a36Sopenharmony_ci if (x->props.mode == XFRM_MODE_TUNNEL) 53262306a36Sopenharmony_ci x->props.header_len += sizeof(struct iphdr); 53362306a36Sopenharmony_ci x->data = ahp; 53462306a36Sopenharmony_ci 53562306a36Sopenharmony_ci return 0; 53662306a36Sopenharmony_ci 53762306a36Sopenharmony_cierror: 53862306a36Sopenharmony_ci if (ahp) { 53962306a36Sopenharmony_ci crypto_free_ahash(ahp->ahash); 54062306a36Sopenharmony_ci kfree(ahp); 54162306a36Sopenharmony_ci } 54262306a36Sopenharmony_ci return -EINVAL; 54362306a36Sopenharmony_ci} 54462306a36Sopenharmony_ci 54562306a36Sopenharmony_cistatic void ah_destroy(struct xfrm_state *x) 54662306a36Sopenharmony_ci{ 54762306a36Sopenharmony_ci struct ah_data *ahp = x->data; 54862306a36Sopenharmony_ci 54962306a36Sopenharmony_ci if (!ahp) 55062306a36Sopenharmony_ci return; 55162306a36Sopenharmony_ci 55262306a36Sopenharmony_ci crypto_free_ahash(ahp->ahash); 55362306a36Sopenharmony_ci kfree(ahp); 55462306a36Sopenharmony_ci} 55562306a36Sopenharmony_ci 55662306a36Sopenharmony_cistatic int ah4_rcv_cb(struct sk_buff *skb, int err) 55762306a36Sopenharmony_ci{ 55862306a36Sopenharmony_ci return 0; 55962306a36Sopenharmony_ci} 56062306a36Sopenharmony_ci 56162306a36Sopenharmony_cistatic const struct xfrm_type ah_type = 56262306a36Sopenharmony_ci{ 56362306a36Sopenharmony_ci .owner = THIS_MODULE, 56462306a36Sopenharmony_ci .proto = IPPROTO_AH, 56562306a36Sopenharmony_ci .flags = XFRM_TYPE_REPLAY_PROT, 56662306a36Sopenharmony_ci .init_state = ah_init_state, 56762306a36Sopenharmony_ci .destructor = ah_destroy, 56862306a36Sopenharmony_ci .input = ah_input, 56962306a36Sopenharmony_ci .output = ah_output 57062306a36Sopenharmony_ci}; 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_cistatic struct xfrm4_protocol ah4_protocol = { 57362306a36Sopenharmony_ci .handler = xfrm4_rcv, 57462306a36Sopenharmony_ci .input_handler = xfrm_input, 57562306a36Sopenharmony_ci .cb_handler = ah4_rcv_cb, 57662306a36Sopenharmony_ci .err_handler = ah4_err, 57762306a36Sopenharmony_ci .priority = 0, 57862306a36Sopenharmony_ci}; 57962306a36Sopenharmony_ci 58062306a36Sopenharmony_cistatic int __init ah4_init(void) 58162306a36Sopenharmony_ci{ 58262306a36Sopenharmony_ci if (xfrm_register_type(&ah_type, AF_INET) < 0) { 58362306a36Sopenharmony_ci pr_info("%s: can't add xfrm type\n", __func__); 58462306a36Sopenharmony_ci return -EAGAIN; 58562306a36Sopenharmony_ci } 58662306a36Sopenharmony_ci if (xfrm4_protocol_register(&ah4_protocol, IPPROTO_AH) < 0) { 58762306a36Sopenharmony_ci pr_info("%s: can't add protocol\n", __func__); 58862306a36Sopenharmony_ci xfrm_unregister_type(&ah_type, AF_INET); 58962306a36Sopenharmony_ci return -EAGAIN; 59062306a36Sopenharmony_ci } 59162306a36Sopenharmony_ci return 0; 59262306a36Sopenharmony_ci} 59362306a36Sopenharmony_ci 59462306a36Sopenharmony_cistatic void __exit ah4_fini(void) 59562306a36Sopenharmony_ci{ 59662306a36Sopenharmony_ci if (xfrm4_protocol_deregister(&ah4_protocol, IPPROTO_AH) < 0) 59762306a36Sopenharmony_ci pr_info("%s: can't remove protocol\n", __func__); 59862306a36Sopenharmony_ci xfrm_unregister_type(&ah_type, AF_INET); 59962306a36Sopenharmony_ci} 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_cimodule_init(ah4_init); 60262306a36Sopenharmony_cimodule_exit(ah4_fini); 60362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 60462306a36Sopenharmony_ciMODULE_ALIAS_XFRM_TYPE(AF_INET, XFRM_PROTO_AH); 605