Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

netfilter: nft_payload: skbuff vlan metadata mangle support

Userspace assumes vlan header is present at a given offset, but vlan
offload allows to store this in metadata fields of the skbuff. Hence
mangling vlan results in a garbled packet. Handle this transparently by
adding a parser to the kernel.

If vlan metadata is present and payload offset is over 12 bytes (source
and destination mac address fields), then subtract vlan header present
in vlan metadata, otherwise mangle vlan metadata based on offset and
length, extracting data from the source register.

This is similar to:

8cfd23e67401 ("netfilter: nft_payload: work around vlan header stripping")

to deal with vlan payload mangling.

Fixes: 7ec3f7b47b8d ("netfilter: nft_payload: add packet mangling support")
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>

+65 -7
+65 -7
net/netfilter/nft_payload.c
··· 145 145 return pkt->inneroff; 146 146 } 147 147 148 - static bool nft_payload_need_vlan_copy(const struct nft_payload *priv) 148 + static bool nft_payload_need_vlan_adjust(u32 offset, u32 len) 149 149 { 150 - unsigned int len = priv->offset + priv->len; 150 + unsigned int boundary = offset + len; 151 151 152 152 /* data past ether src/dst requested, copy needed */ 153 - if (len > offsetof(struct ethhdr, h_proto)) 153 + if (boundary > offsetof(struct ethhdr, h_proto)) 154 154 return true; 155 155 156 156 return false; ··· 174 174 goto err; 175 175 176 176 if (skb_vlan_tag_present(skb) && 177 - nft_payload_need_vlan_copy(priv)) { 177 + nft_payload_need_vlan_adjust(priv->offset, priv->len)) { 178 178 if (!nft_payload_copy_vlan(dest, skb, 179 179 priv->offset, priv->len)) 180 180 goto err; ··· 801 801 u8 csum_flags; 802 802 }; 803 803 804 + /* This is not struct vlan_hdr. */ 805 + struct nft_payload_vlan_hdr { 806 + __be16 h_vlan_proto; 807 + __be16 h_vlan_TCI; 808 + }; 809 + 810 + static bool 811 + nft_payload_set_vlan(const u32 *src, struct sk_buff *skb, u8 offset, u8 len, 812 + int *vlan_hlen) 813 + { 814 + struct nft_payload_vlan_hdr *vlanh; 815 + __be16 vlan_proto; 816 + u16 vlan_tci; 817 + 818 + if (offset >= offsetof(struct vlan_ethhdr, h_vlan_encapsulated_proto)) { 819 + *vlan_hlen = VLAN_HLEN; 820 + return true; 821 + } 822 + 823 + switch (offset) { 824 + case offsetof(struct vlan_ethhdr, h_vlan_proto): 825 + if (len == 2) { 826 + vlan_proto = nft_reg_load_be16(src); 827 + skb->vlan_proto = vlan_proto; 828 + } else if (len == 4) { 829 + vlanh = (struct nft_payload_vlan_hdr *)src; 830 + __vlan_hwaccel_put_tag(skb, vlanh->h_vlan_proto, 831 + ntohs(vlanh->h_vlan_TCI)); 832 + } else { 833 + return false; 834 + } 835 + break; 836 + case offsetof(struct vlan_ethhdr, h_vlan_TCI): 837 + if (len != 2) 838 + return false; 839 + 840 + vlan_tci = ntohs(nft_reg_load_be16(src)); 841 + skb->vlan_tci = vlan_tci; 842 + break; 843 + default: 844 + return false; 845 + } 846 + 847 + return true; 848 + } 849 + 804 850 static void nft_payload_set_eval(const struct nft_expr *expr, 805 851 struct nft_regs *regs, 806 852 const struct nft_pktinfo *pkt) 807 853 { 808 854 const struct nft_payload_set *priv = nft_expr_priv(expr); 809 - struct sk_buff *skb = pkt->skb; 810 855 const u32 *src = &regs->data[priv->sreg]; 811 - int offset, csum_offset; 856 + int offset, csum_offset, vlan_hlen = 0; 857 + struct sk_buff *skb = pkt->skb; 812 858 __wsum fsum, tsum; 813 859 814 860 switch (priv->base) { 815 861 case NFT_PAYLOAD_LL_HEADER: 816 862 if (!skb_mac_header_was_set(skb)) 817 863 goto err; 818 - offset = skb_mac_header(skb) - skb->data; 864 + 865 + if (skb_vlan_tag_present(skb) && 866 + nft_payload_need_vlan_adjust(priv->offset, priv->len)) { 867 + if (!nft_payload_set_vlan(src, skb, 868 + priv->offset, priv->len, 869 + &vlan_hlen)) 870 + goto err; 871 + 872 + if (!vlan_hlen) 873 + return; 874 + } 875 + 876 + offset = skb_mac_header(skb) - skb->data - vlan_hlen; 819 877 break; 820 878 case NFT_PAYLOAD_NETWORK_HEADER: 821 879 offset = skb_network_offset(skb);