xfrm: Reinject transport-mode packets through tasklet

commit acf568ee859f098279eadf551612f103afdacb4e upstream.

This is an old bugbear of mine:

https://www.mail-archive.com/netdev@vger.kernel.org/msg03894.html

By crafting special packets, it is possible to cause recursion
in our kernel when processing transport-mode packets at levels
that are only limited by packet size.

The easiest one is with DNAT, but an even worse one is where
UDP encapsulation is used in which case you just have to insert
an UDP encapsulation header in between each level of recursion.

This patch avoids this problem by reinjecting tranport-mode packets
through a tasklet.

Fixes: b05e106698 ("[IPV4/6]: Netfilter IPsec input hooks")
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
[bwh: Backported to 3.2:
 - netfilter finish callbacks only receive an sk_buff pointer
 - Adjust context]
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
This commit is contained in:
Herbert Xu 2017-12-15 16:40:44 +11:00 committed by syphyr
parent 5ebcb3c532
commit e0409e42a1
4 changed files with 75 additions and 2 deletions

View File

@ -1464,6 +1464,7 @@ extern int xfrm_prepare_input(struct xfrm_state *x, struct sk_buff *skb);
extern int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi,
int encap_type);
extern int xfrm_input_resume(struct sk_buff *skb, int nexthdr);
int xfrm_trans_queue(struct sk_buff *skb, int (*finish)(struct sk_buff *));
extern int xfrm_output_resume(struct sk_buff *skb, int err);
extern int xfrm_output(struct sk_buff *skb);
extern int xfrm_inner_extract_output(struct xfrm_state *x, struct sk_buff *skb);

View File

@ -22,6 +22,11 @@ int xfrm4_extract_input(struct xfrm_state *x, struct sk_buff *skb)
return xfrm4_extract_header(skb);
}
static int xfrm4_rcv_encap_finish2(struct sk_buff *skb)
{
return dst_input(skb);
}
static inline int xfrm4_rcv_encap_finish(struct sk_buff *skb)
{
if (skb_dst(skb) == NULL) {
@ -31,7 +36,11 @@ static inline int xfrm4_rcv_encap_finish(struct sk_buff *skb)
iph->tos, skb->dev))
goto drop;
}
return dst_input(skb);
if (xfrm_trans_queue(skb, xfrm4_rcv_encap_finish2))
goto drop;
return 0;
drop:
kfree_skb(skb);
return NET_RX_DROP;

View File

@ -29,6 +29,13 @@ int xfrm6_rcv_spi(struct sk_buff *skb, int nexthdr, __be32 spi)
}
EXPORT_SYMBOL(xfrm6_rcv_spi);
static int xfrm6_transport_finish2(struct sk_buff *skb)
{
if (xfrm_trans_queue(skb, ip6_rcv_finish))
__kfree_skb(skb);
return -1;
}
int xfrm6_transport_finish(struct sk_buff *skb, int async)
{
skb_network_header(skb)[IP6CB(skb)->nhoff] =
@ -43,7 +50,7 @@ int xfrm6_transport_finish(struct sk_buff *skb, int async)
__skb_push(skb, skb->data - skb_network_header(skb));
NF_HOOK(NFPROTO_IPV6, NF_INET_PRE_ROUTING, skb, skb->dev, NULL,
ip6_rcv_finish);
xfrm6_transport_finish2);
return -1;
}

View File

@ -7,15 +7,31 @@
*
*/
#include <linux/bottom_half.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/percpu.h>
#include <net/dst.h>
#include <net/ip.h>
#include <net/xfrm.h>
struct xfrm_trans_tasklet {
struct tasklet_struct tasklet;
struct sk_buff_head queue;
};
struct xfrm_trans_cb {
int (*finish)(struct sk_buff *skb);
};
#define XFRM_TRANS_SKB_CB(__skb) ((struct xfrm_trans_cb *)&((__skb)->cb[0]))
static struct kmem_cache *secpath_cachep __read_mostly;
static DEFINE_PER_CPU(struct xfrm_trans_tasklet, xfrm_trans_tasklet);
void __secpath_destroy(struct sec_path *sp)
{
int i;
@ -282,10 +298,50 @@ int xfrm_input_resume(struct sk_buff *skb, int nexthdr)
}
EXPORT_SYMBOL(xfrm_input_resume);
static void xfrm_trans_reinject(unsigned long data)
{
struct xfrm_trans_tasklet *trans = (void *)data;
struct sk_buff_head queue;
struct sk_buff *skb;
__skb_queue_head_init(&queue);
skb_queue_splice_init(&trans->queue, &queue);
while ((skb = __skb_dequeue(&queue)))
XFRM_TRANS_SKB_CB(skb)->finish(skb);
}
int xfrm_trans_queue(struct sk_buff *skb, int (*finish)(struct sk_buff *))
{
struct xfrm_trans_tasklet *trans;
trans = this_cpu_ptr(&xfrm_trans_tasklet);
if (skb_queue_len(&trans->queue) >= netdev_max_backlog)
return -ENOBUFS;
XFRM_TRANS_SKB_CB(skb)->finish = finish;
skb_queue_tail(&trans->queue, skb);
tasklet_schedule(&trans->tasklet);
return 0;
}
EXPORT_SYMBOL(xfrm_trans_queue);
void __init xfrm_input_init(void)
{
int i;
secpath_cachep = kmem_cache_create("secpath_cache",
sizeof(struct sec_path),
0, SLAB_HWCACHE_ALIGN|SLAB_PANIC,
NULL);
for_each_possible_cpu(i) {
struct xfrm_trans_tasklet *trans;
trans = &per_cpu(xfrm_trans_tasklet, i);
__skb_queue_head_init(&trans->queue);
tasklet_init(&trans->tasklet, xfrm_trans_reinject,
(unsigned long)trans);
}
}