Multiple places in the net stack open-code the same minimal IPv4 header validation: pull the fixed header, check version 4 and IHL >= 5 (where 5 is the fixed header size in 32-bit units) - and locate the transport header at IHL * 4 past the network header.
Add a helper for doing that: it returns the validated header and the transport header offset, or NULL if truncated or malformed. Signed-off-by: Michael S. Tsirkin <[email protected]> Assisted-by: LLM --- include/linux/ip.h | 24 ++++++++++++++++++++++++ include/linux/virtio_net.h | 28 +--------------------------- net/bridge/br_netfilter_hooks.c | 15 +++++---------- net/ipv4/ip_input.c | 13 +++++-------- net/ipv4/netfilter/nf_reject_ipv4.c | 13 +++++-------- 5 files changed, 40 insertions(+), 53 deletions(-) diff --git a/include/linux/ip.h b/include/linux/ip.h index d11c25f5030a..98012f6d0efb 100644 --- a/include/linux/ip.h +++ b/include/linux/ip.h @@ -56,4 +56,28 @@ static inline void iph_set_totlen(struct iphdr *iph, unsigned int len) { iph->tot_len = len <= IP_MAX_MTU ? htons(len) : 0; } + +/* + * Pull and minimally validate the IPv4 header at @nhoff. + * Returns the header and stores the transport header offset in + * @thoff, or NULL if truncated or malformed. Only the fixed header + * is guaranteed linear; callers that need the options or the + * transport header must pull further. + */ +static inline struct iphdr *ip4_parse(struct sk_buff *skb, + unsigned int nhoff, + unsigned int *thoff) +{ + struct iphdr *iph; + + if (!pskb_may_pull(skb, nhoff + sizeof(struct iphdr))) + return NULL; + + iph = (struct iphdr *)(skb->data + nhoff); + if (iph->version != 4 || iph->ihl < sizeof(struct iphdr) / sizeof(u32)) + return NULL; + + *thoff = nhoff + iph->ihl * sizeof(u32); + return iph; +} #endif /* _LINUX_IP_H */ diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h index aa2f8ac4657c..85ec1039f73a 100644 --- a/include/linux/virtio_net.h +++ b/include/linux/virtio_net.h @@ -53,32 +53,6 @@ static inline int virtio_net_hdr_set_proto(struct sk_buff *skb, return 0; } -/* TODO: nothing virtio specific here, move to ip.h */ - -/* - * Pull and minimally validate the IPv4 header at @nhoff. - * Returns the header and stores the transport header offset in - * @thoff, or NULL if truncated or malformed. Only the fixed header - * is guaranteed linear; callers that need the options or the - * transport header must pull further. - */ -static inline struct iphdr *virtio_net_ip4_parse(struct sk_buff *skb, - unsigned int nhoff, - unsigned int *thoff) -{ - struct iphdr *iph; - - if (!pskb_may_pull(skb, nhoff + sizeof(struct iphdr))) - return NULL; - - iph = (struct iphdr *)(skb->data + nhoff); - if (iph->version != 4 || - iph->ihl < sizeof(struct iphdr) / sizeof(u32)) - return NULL; - - *thoff = nhoff + iph->ihl * sizeof(u32); - return iph; -} /* Defined in net/core/dev.c, enabled while any network namespace * has net.core.virtio_net_hdr_csum_check set to 0. */ @@ -126,7 +100,7 @@ virtio_net_l4_start(struct sk_buff *skb, unsigned int nhoff, __be16 proto, unsigned int *thoff, int *ip_proto) { if (proto == htons(ETH_P_IP)) { - const struct iphdr *iph = virtio_net_ip4_parse(skb, nhoff, thoff); + const struct iphdr *iph = ip4_parse(skb, nhoff, thoff); if (!iph || ip_is_fragment(iph)) return false; diff --git a/net/bridge/br_netfilter_hooks.c b/net/bridge/br_netfilter_hooks.c index 0a394e5f4391..e89da0e41542 100644 --- a/net/bridge/br_netfilter_hooks.c +++ b/net/bridge/br_netfilter_hooks.c @@ -206,18 +206,13 @@ static inline void nf_bridge_pull_encap_header_rcsum(struct sk_buff *skb) static int br_validate_ipv4(struct net *net, struct sk_buff *skb) { const struct iphdr *iph; - u32 len; + u32 len, thoff; - if (!pskb_may_pull(skb, sizeof(struct iphdr))) + iph = ip4_parse(skb, 0, &thoff); + if (!iph) goto inhdr_error; - iph = ip_hdr(skb); - - /* Basic sanity checks */ - if (iph->ihl < 5 || iph->version != 4) - goto inhdr_error; - - if (!pskb_may_pull(skb, iph->ihl*4)) + if (!pskb_may_pull(skb, thoff)) goto inhdr_error; iph = ip_hdr(skb); @@ -228,7 +223,7 @@ static int br_validate_ipv4(struct net *net, struct sk_buff *skb) if (skb->len < len) { __IP_INC_STATS(net, IPSTATS_MIB_INTRUNCATEDPKTS); goto drop; - } else if (len < (iph->ihl*4)) + } else if (len < thoff) goto inhdr_error; if (pskb_trim_rcsum(skb, len)) { diff --git a/net/ipv4/ip_input.c b/net/ipv4/ip_input.c index 9860178752b8..5adf01763e85 100644 --- a/net/ipv4/ip_input.c +++ b/net/ipv4/ip_input.c @@ -500,7 +500,7 @@ static struct sk_buff *ip_rcv_core(struct sk_buff *skb, struct net *net) { const struct iphdr *iph; int drop_reason; - u32 len; + u32 len, thoff; /* When the interface is in promisc. mode, drop all the crap * that it receives, do not try to analyse it. @@ -520,10 +520,6 @@ static struct sk_buff *ip_rcv_core(struct sk_buff *skb, struct net *net) } drop_reason = SKB_DROP_REASON_NOT_SPECIFIED; - if (!pskb_may_pull(skb, sizeof(struct iphdr))) - goto inhdr_error; - - iph = ip_hdr(skb); /* * RFC1122: 3.2.1.2 MUST silently discard any IP frame that fails the checksum. @@ -536,7 +532,8 @@ static struct sk_buff *ip_rcv_core(struct sk_buff *skb, struct net *net) * 4. Doesn't have a bogus length */ - if (iph->ihl < 5 || iph->version != 4) + iph = ip4_parse(skb, 0, &thoff); + if (!iph) goto inhdr_error; BUILD_BUG_ON(IPSTATS_MIB_ECT1PKTS != IPSTATS_MIB_NOECTPKTS + INET_ECN_ECT_1); @@ -546,7 +543,7 @@ static struct sk_buff *ip_rcv_core(struct sk_buff *skb, struct net *net) IPSTATS_MIB_NOECTPKTS + (iph->tos & INET_ECN_MASK), max_t(unsigned short, 1, skb_shinfo(skb)->gso_segs)); - if (!pskb_may_pull(skb, iph->ihl*4)) + if (!pskb_may_pull(skb, thoff)) goto inhdr_error; iph = ip_hdr(skb); @@ -559,7 +556,7 @@ static struct sk_buff *ip_rcv_core(struct sk_buff *skb, struct net *net) drop_reason = SKB_DROP_REASON_PKT_TOO_SMALL; __IP_INC_STATS(net, IPSTATS_MIB_INTRUNCATEDPKTS); goto drop; - } else if (len < (iph->ihl*4)) + } else if (len < thoff) goto inhdr_error; /* Our transport medium may have padded the buffer out. Now we know it diff --git a/net/ipv4/netfilter/nf_reject_ipv4.c b/net/ipv4/netfilter/nf_reject_ipv4.c index 59ec465a9df9..42d639854ddf 100644 --- a/net/ipv4/netfilter/nf_reject_ipv4.c +++ b/net/ipv4/netfilter/nf_reject_ipv4.c @@ -25,22 +25,19 @@ nf_reject_ip_tcphdr_get(struct sk_buff *oldskb, static int nf_reject_iphdr_validate(struct sk_buff *skb) { struct iphdr *iph; - u32 len; + u32 len, thoff; - if (!pskb_may_pull(skb, sizeof(struct iphdr))) - return 0; - - iph = ip_hdr(skb); - if (iph->ihl < 5 || iph->version != 4) + iph = ip4_parse(skb, 0, &thoff); + if (!iph) return 0; len = ntohs(iph->tot_len); if (skb->len < len) return 0; - else if (len < (iph->ihl*4)) + else if (len < thoff) return 0; - if (!pskb_may_pull(skb, iph->ihl*4)) + if (!pskb_may_pull(skb, thoff)) return 0; return 1; -- MST

