Skip to content

Commit

Permalink
netfilter: introduce nft_set_pktinfo_{ipv4, ipv6}_validate()
Browse files Browse the repository at this point in the history
These functions are extracted from the netdev family, they initialize
the pktinfo structure and validate that the IPv4 and IPv6 headers are
well-formed given that these functions are called from a path where
layer 3 sanitization did not happen yet.

These functions are placed in include/net/netfilter/nf_tables_ipv{4,6}.h
so they can be reused by a follow up patch to use them from the bridge
family too.

Signed-off-by: Pablo Neira Ayuso <[email protected]>
  • Loading branch information
ummakynes committed Sep 12, 2016
1 parent 8df9e32 commit ddc8b60
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 77 deletions.
42 changes: 42 additions & 0 deletions include/net/netfilter/nf_tables_ipv4.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,48 @@ nft_set_pktinfo_ipv4(struct nft_pktinfo *pkt,
pkt->xt.fragoff = ntohs(ip->frag_off) & IP_OFFSET;
}

static inline int
__nft_set_pktinfo_ipv4_validate(struct nft_pktinfo *pkt,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
struct iphdr *iph, _iph;
u32 len, thoff;

iph = skb_header_pointer(skb, skb_network_offset(skb), sizeof(*iph),
&_iph);
if (!iph)
return -1;

iph = ip_hdr(skb);
if (iph->ihl < 5 || iph->version != 4)
return -1;

len = ntohs(iph->tot_len);
thoff = iph->ihl * 4;
if (skb->len < len)
return -1;
else if (len < thoff)
return -1;

pkt->tprot_set = true;
pkt->tprot = iph->protocol;
pkt->xt.thoff = thoff;
pkt->xt.fragoff = ntohs(iph->frag_off) & IP_OFFSET;

return 0;
}

static inline void
nft_set_pktinfo_ipv4_validate(struct nft_pktinfo *pkt,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
nft_set_pktinfo(pkt, skb, state);
if (__nft_set_pktinfo_ipv4_validate(pkt, skb, state) < 0)
nft_set_pktinfo_proto_unspec(pkt, skb);
}

extern struct nft_af_info nft_af_ipv4;

#endif
49 changes: 49 additions & 0 deletions include/net/netfilter/nf_tables_ipv6.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,55 @@ nft_set_pktinfo_ipv6(struct nft_pktinfo *pkt,
return 0;
}

static inline int
__nft_set_pktinfo_ipv6_validate(struct nft_pktinfo *pkt,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
#if IS_ENABLED(CONFIG_IPV6)
struct ipv6hdr *ip6h, _ip6h;
unsigned int thoff = 0;
unsigned short frag_off;
int protohdr;
u32 pkt_len;

ip6h = skb_header_pointer(skb, skb_network_offset(skb), sizeof(*ip6h),
&_ip6h);
if (!ip6h)
return -1;

if (ip6h->version != 6)
return -1;

pkt_len = ntohs(ip6h->payload_len);
if (pkt_len + sizeof(*ip6h) > skb->len)
return -1;

protohdr = ipv6_find_hdr(pkt->skb, &thoff, -1, &frag_off, NULL);
if (protohdr < 0)
return -1;

pkt->tprot_set = true;
pkt->tprot = protohdr;
pkt->xt.thoff = thoff;
pkt->xt.fragoff = frag_off;

return 0;
#else
return -1;
#endif
}

static inline void
nft_set_pktinfo_ipv6_validate(struct nft_pktinfo *pkt,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
nft_set_pktinfo(pkt, skb, state);
if (__nft_set_pktinfo_ipv6_validate(pkt, skb, state) < 0)
nft_set_pktinfo_proto_unspec(pkt, skb);
}

extern struct nft_af_info nft_af_ipv6;

#endif
79 changes: 2 additions & 77 deletions net/netfilter/nf_tables_netdev.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,81 +15,6 @@
#include <net/netfilter/nf_tables_ipv4.h>
#include <net/netfilter/nf_tables_ipv6.h>

static inline void
nft_netdev_set_pktinfo_ipv4(struct nft_pktinfo *pkt,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
struct iphdr *iph, _iph;
u32 len, thoff;

nft_set_pktinfo(pkt, skb, state);

iph = skb_header_pointer(skb, skb_network_offset(skb), sizeof(*iph),
&_iph);
if (!iph)
return;

iph = ip_hdr(skb);
if (iph->ihl < 5 || iph->version != 4)
return;

len = ntohs(iph->tot_len);
thoff = iph->ihl * 4;
if (skb->len < len)
return;
else if (len < thoff)
return;

pkt->tprot_set = true;
pkt->tprot = iph->protocol;
pkt->xt.thoff = thoff;
pkt->xt.fragoff = ntohs(iph->frag_off) & IP_OFFSET;
}

static inline void
__nft_netdev_set_pktinfo_ipv6(struct nft_pktinfo *pkt,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
#if IS_ENABLED(CONFIG_IPV6)
struct ipv6hdr *ip6h, _ip6h;
unsigned int thoff = 0;
unsigned short frag_off;
int protohdr;
u32 pkt_len;

ip6h = skb_header_pointer(skb, skb_network_offset(skb), sizeof(*ip6h),
&_ip6h);
if (!ip6h)
return;

if (ip6h->version != 6)
return;

pkt_len = ntohs(ip6h->payload_len);
if (pkt_len + sizeof(*ip6h) > skb->len)
return;

protohdr = ipv6_find_hdr(pkt->skb, &thoff, -1, &frag_off, NULL);
if (protohdr < 0)
return;

pkt->tprot_set = true;
pkt->tprot = protohdr;
pkt->xt.thoff = thoff;
pkt->xt.fragoff = frag_off;
#endif
}

static inline void nft_netdev_set_pktinfo_ipv6(struct nft_pktinfo *pkt,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
nft_set_pktinfo(pkt, skb, state);
__nft_netdev_set_pktinfo_ipv6(pkt, skb, state);
}

static unsigned int
nft_do_chain_netdev(void *priv, struct sk_buff *skb,
const struct nf_hook_state *state)
Expand All @@ -98,10 +23,10 @@ nft_do_chain_netdev(void *priv, struct sk_buff *skb,

switch (skb->protocol) {
case htons(ETH_P_IP):
nft_netdev_set_pktinfo_ipv4(&pkt, skb, state);
nft_set_pktinfo_ipv4_validate(&pkt, skb, state);
break;
case htons(ETH_P_IPV6):
nft_netdev_set_pktinfo_ipv6(&pkt, skb, state);
nft_set_pktinfo_ipv6_validate(&pkt, skb, state);
break;
default:
nft_set_pktinfo_unspec(&pkt, skb, state);
Expand Down

0 comments on commit ddc8b60

Please sign in to comment.