diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c index b8aaf39cb4d8..c727668b0c5b 100644 --- a/net/netfilter/nfnetlink_queue.c +++ b/net/netfilter/nfnetlink_queue.c @@ -28,10 +28,17 @@ #include #include #include +#include +#include +#include #include +#include #include #include #include +#include +#include +#include #include #include #include @@ -1206,10 +1213,62 @@ static bool nfqnl_validate_ipopts(const struct iphdr *iph_new, return memcmp(iph_new + 1, ip_hdr(e->skb) + 1, ihl - sizeof(*iph_orig)) == 0; } +static bool nfqnl_validate_l4(const u8 *data, unsigned int data_len, + const struct nf_queue_entry *e, u8 proto, + bool fragment) +{ +#if IS_ENABLED(CONFIG_NF_CONNTRACK) + enum ip_conntrack_info ctinfo; + const struct nf_conn *ct; + + ct = nf_ct_get(e->skb, &ctinfo); + if (ct && !nf_ct_is_template(ct)) { + if (fragment || nf_ct_protonum(ct) != proto) + return false; + } +#endif + + if (fragment) + return true; + + switch (proto) { + case IPPROTO_TCP: { + const struct tcphdr *th = (const struct tcphdr *)data; + unsigned int thlen; + + if (data_len < sizeof(*th)) + return false; + + thlen = __tcp_hdrlen(th); + if (thlen < sizeof(*th) || data_len < thlen) + return false; + + return true; + } + case IPPROTO_UDP: + return data_len >= sizeof(struct udphdr); + case IPPROTO_ICMP: + return data_len >= sizeof(struct icmphdr); + case IPPROTO_ICMPV6: + return data_len >= sizeof(struct icmp6hdr); + case IPPROTO_SCTP: + return data_len >= sizeof(struct sctphdr); + case IPPROTO_GRE: + return data_len >= sizeof(struct gre_base_hdr); + case IPPROTO_AH: + return data_len >= sizeof(struct ip_auth_hdr); + case IPPROTO_ESP: + return data_len >= sizeof(struct ip_esp_hdr); + } + + return true; +} + static bool nfqnl_validate_ip4(const struct iphdr *iph, unsigned int data_len, const struct nf_queue_entry *e) { unsigned int ihl; + bool fragment; if (data_len < sizeof(*iph)) return false; @@ -1226,10 +1285,14 @@ static bool nfqnl_validate_ip4(const struct iphdr *iph, unsigned int data_len, if (ntohs(iph->tot_len) != data_len) return false; + fragment = iph->frag_off & htons(IP_MF | IP_OFFSET); + /* support for ipopts mangling would require * recompile + skb transport header update. */ - return nfqnl_validate_ipopts(iph, e); + return nfqnl_validate_ipopts(iph, e) && + nfqnl_validate_l4((const u8 *)iph + ihl, data_len - ihl, e, + iph->protocol, fragment); } static bool nfqnl_validate_one_exthdr(const u8 *data, @@ -1273,6 +1336,7 @@ static bool nfqnl_validate_exthdr(const struct ipv6hdr *ip6_new, const u8 *data = (const u8 *)ip6_new; u8 orig_nexthdr = ip6_orig->nexthdr; u8 new_nexthdr = ip6_new->nexthdr; + bool fragment = false; if (new_nexthdr != orig_nexthdr) return false; @@ -1286,7 +1350,8 @@ static bool nfqnl_validate_exthdr(const struct ipv6hdr *ip6_new, int hdrlen; if (orig_nexthdr == NEXTHDR_NONE) - return true; + return nfqnl_validate_l4(data, data_len, e, + new_nexthdr, fragment); if (unlikely(exthdr_cnt++ >= IP6_MAX_EXT_HDRS_CNT)) return false; @@ -1297,6 +1362,7 @@ static bool nfqnl_validate_exthdr(const struct ipv6hdr *ip6_new, switch (orig_nexthdr) { case NEXTHDR_FRAGMENT: + fragment = true; hdrlen = sizeof(struct frag_hdr); break; case NEXTHDR_AUTH: @@ -1323,7 +1389,7 @@ static bool nfqnl_validate_exthdr(const struct ipv6hdr *ip6_new, data += hdrlen; } - return true; + return nfqnl_validate_l4(data, data_len, e, new_nexthdr, fragment); } static bool nfqnl_validate_ip6(const struct ipv6hdr *ip6, unsigned int data_len, diff --git a/net/netfilter/nft_payload.c b/net/netfilter/nft_payload.c index 8a4472fd77d9..e315d35f73d4 100644 --- a/net/netfilter/nft_payload.c +++ b/net/netfilter/nft_payload.c @@ -1067,6 +1067,17 @@ static bool nft_payload_csum_write_ok(const struct nft_pktinfo *pkt, return false; } +static bool nft_th_write_ok(const struct nft_pktinfo *pkt, + const struct nft_payload_set *priv) +{ + unsigned int doff = offsetof(struct tcphdr, ack_seq) + sizeof(__be32); + + if (pkt->tprot != IPPROTO_TCP) + return true; + + return priv->offset > doff || priv->offset + priv->len <= doff; +} + static void nft_payload_set_eval(const struct nft_expr *expr, struct nft_regs *regs, const struct nft_pktinfo *pkt) @@ -1105,6 +1116,8 @@ static void nft_payload_set_eval(const struct nft_expr *expr, case NFT_PAYLOAD_TRANSPORT_HEADER: if (!(pkt->flags & NFT_PKTINFO_L4PROTO) || pkt->fragoff) goto err; + if (!nft_th_write_ok(pkt, priv)) + goto err; offset = nft_thoff(pkt); break; case NFT_PAYLOAD_INNER_HEADER: