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

netfilter: nf_tables: fix mismatch in big-endian system

Currently, there are two different methods to store an u16 integer to
the u32 data register. For example:
u32 *dest = &regs->data[priv->dreg];
1. *dest = 0; *(u16 *) dest = val_u16;
2. *dest = val_u16;

For method 1, the u16 value will be stored like this, either in
big-endian or little-endian system:
0 15 31
+-+-+-+-+-+-+-+-+-+-+-+-+
| Value | 0 |
+-+-+-+-+-+-+-+-+-+-+-+-+

For method 2, in little-endian system, the u16 value will be the same
as listed above. But in big-endian system, the u16 value will be stored
like this:
0 15 31
+-+-+-+-+-+-+-+-+-+-+-+-+
| 0 | Value |
+-+-+-+-+-+-+-+-+-+-+-+-+

So later we use "memcmp(&regs->data[priv->sreg], data, 2);" to do
compare in nft_cmp, nft_lookup expr ..., method 2 will get the wrong
result in big-endian system, as 0~15 bits will always be zero.

For the similar reason, when loading an u16 value from the u32 data
register, we should use "*(u16 *) sreg;" instead of "(u16)*sreg;",
the 2nd method will get the wrong value in the big-endian system.

So introduce some wrapper functions to store/load an u8 or u16
integer to/from the u32 data register, and use them in the right
place.

Signed-off-by: Liping Zhang <zlpnobody@gmail.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>

authored by

Liping Zhang and committed by
Pablo Neira Ayuso
10596608 fd89b23a

+80 -47
+29
include/net/netfilter/nf_tables.h
··· 103 103 }; 104 104 }; 105 105 106 + /* Store/load an u16 or u8 integer to/from the u32 data register. 107 + * 108 + * Note, when using concatenations, register allocation happens at 32-bit 109 + * level. So for store instruction, pad the rest part with zero to avoid 110 + * garbage values. 111 + */ 112 + 113 + static inline void nft_reg_store16(u32 *dreg, u16 val) 114 + { 115 + *dreg = 0; 116 + *(u16 *)dreg = val; 117 + } 118 + 119 + static inline void nft_reg_store8(u32 *dreg, u8 val) 120 + { 121 + *dreg = 0; 122 + *(u8 *)dreg = val; 123 + } 124 + 125 + static inline u16 nft_reg_load16(u32 *sreg) 126 + { 127 + return *(u16 *)sreg; 128 + } 129 + 130 + static inline u8 nft_reg_load8(u32 *sreg) 131 + { 132 + return *(u8 *)sreg; 133 + } 134 + 106 135 static inline void nft_data_copy(u32 *dst, const struct nft_data *src, 107 136 unsigned int len) 108 137 {
+4 -4
net/ipv4/netfilter/nft_masq_ipv4.c
··· 26 26 memset(&range, 0, sizeof(range)); 27 27 range.flags = priv->flags; 28 28 if (priv->sreg_proto_min) { 29 - range.min_proto.all = 30 - *(__be16 *)&regs->data[priv->sreg_proto_min]; 31 - range.max_proto.all = 32 - *(__be16 *)&regs->data[priv->sreg_proto_max]; 29 + range.min_proto.all = (__force __be16)nft_reg_load16( 30 + &regs->data[priv->sreg_proto_min]); 31 + range.max_proto.all = (__force __be16)nft_reg_load16( 32 + &regs->data[priv->sreg_proto_max]); 33 33 } 34 34 regs->verdict.code = nf_nat_masquerade_ipv4(pkt->skb, nft_hook(pkt), 35 35 &range, nft_out(pkt));
+4 -4
net/ipv4/netfilter/nft_redir_ipv4.c
··· 26 26 27 27 memset(&mr, 0, sizeof(mr)); 28 28 if (priv->sreg_proto_min) { 29 - mr.range[0].min.all = 30 - *(__be16 *)&regs->data[priv->sreg_proto_min]; 31 - mr.range[0].max.all = 32 - *(__be16 *)&regs->data[priv->sreg_proto_max]; 29 + mr.range[0].min.all = (__force __be16)nft_reg_load16( 30 + &regs->data[priv->sreg_proto_min]); 31 + mr.range[0].max.all = (__force __be16)nft_reg_load16( 32 + &regs->data[priv->sreg_proto_max]); 33 33 mr.range[0].flags |= NF_NAT_RANGE_PROTO_SPECIFIED; 34 34 } 35 35
+4 -4
net/ipv6/netfilter/nft_masq_ipv6.c
··· 27 27 memset(&range, 0, sizeof(range)); 28 28 range.flags = priv->flags; 29 29 if (priv->sreg_proto_min) { 30 - range.min_proto.all = 31 - *(__be16 *)&regs->data[priv->sreg_proto_min]; 32 - range.max_proto.all = 33 - *(__be16 *)&regs->data[priv->sreg_proto_max]; 30 + range.min_proto.all = (__force __be16)nft_reg_load16( 31 + &regs->data[priv->sreg_proto_min]); 32 + range.max_proto.all = (__force __be16)nft_reg_load16( 33 + &regs->data[priv->sreg_proto_max]); 34 34 } 35 35 regs->verdict.code = nf_nat_masquerade_ipv6(pkt->skb, &range, 36 36 nft_out(pkt));
+4 -4
net/ipv6/netfilter/nft_redir_ipv6.c
··· 26 26 27 27 memset(&range, 0, sizeof(range)); 28 28 if (priv->sreg_proto_min) { 29 - range.min_proto.all = 30 - *(__be16 *)&regs->data[priv->sreg_proto_min], 31 - range.max_proto.all = 32 - *(__be16 *)&regs->data[priv->sreg_proto_max], 29 + range.min_proto.all = (__force __be16)nft_reg_load16( 30 + &regs->data[priv->sreg_proto_min]); 31 + range.max_proto.all = (__force __be16)nft_reg_load16( 32 + &regs->data[priv->sreg_proto_max]); 33 33 range.flags |= NF_NAT_RANGE_PROTO_SPECIFIED; 34 34 } 35 35
+10 -8
net/netfilter/nft_ct.c
··· 83 83 84 84 switch (priv->key) { 85 85 case NFT_CT_DIRECTION: 86 - *dest = CTINFO2DIR(ctinfo); 86 + nft_reg_store8(dest, CTINFO2DIR(ctinfo)); 87 87 return; 88 88 case NFT_CT_STATUS: 89 89 *dest = ct->status; ··· 151 151 return; 152 152 } 153 153 case NFT_CT_L3PROTOCOL: 154 - *dest = nf_ct_l3num(ct); 154 + nft_reg_store8(dest, nf_ct_l3num(ct)); 155 155 return; 156 156 case NFT_CT_PROTOCOL: 157 - *dest = nf_ct_protonum(ct); 157 + nft_reg_store8(dest, nf_ct_protonum(ct)); 158 158 return; 159 159 #ifdef CONFIG_NF_CONNTRACK_ZONES 160 160 case NFT_CT_ZONE: { 161 161 const struct nf_conntrack_zone *zone = nf_ct_zone(ct); 162 + u16 zoneid; 162 163 163 164 if (priv->dir < IP_CT_DIR_MAX) 164 - *dest = nf_ct_zone_id(zone, priv->dir); 165 + zoneid = nf_ct_zone_id(zone, priv->dir); 165 166 else 166 - *dest = zone->id; 167 + zoneid = zone->id; 167 168 169 + nft_reg_store16(dest, zoneid); 168 170 return; 169 171 } 170 172 #endif ··· 185 183 nf_ct_l3num(ct) == NFPROTO_IPV4 ? 4 : 16); 186 184 return; 187 185 case NFT_CT_PROTO_SRC: 188 - *dest = (__force __u16)tuple->src.u.all; 186 + nft_reg_store16(dest, (__force u16)tuple->src.u.all); 189 187 return; 190 188 case NFT_CT_PROTO_DST: 191 - *dest = (__force __u16)tuple->dst.u.all; 189 + nft_reg_store16(dest, (__force u16)tuple->dst.u.all); 192 190 return; 193 191 default: 194 192 break; ··· 207 205 const struct nft_ct *priv = nft_expr_priv(expr); 208 206 struct sk_buff *skb = pkt->skb; 209 207 enum ip_conntrack_info ctinfo; 210 - u16 value = regs->data[priv->sreg]; 208 + u16 value = nft_reg_load16(&regs->data[priv->sreg]); 211 209 struct nf_conn *ct; 212 210 213 211 ct = nf_ct_get(skb, &ctinfo);
+21 -19
net/netfilter/nft_meta.c
··· 45 45 *dest = skb->len; 46 46 break; 47 47 case NFT_META_PROTOCOL: 48 - *dest = 0; 49 - *(__be16 *)dest = skb->protocol; 48 + nft_reg_store16(dest, (__force u16)skb->protocol); 50 49 break; 51 50 case NFT_META_NFPROTO: 52 - *dest = nft_pf(pkt); 51 + nft_reg_store8(dest, nft_pf(pkt)); 53 52 break; 54 53 case NFT_META_L4PROTO: 55 54 if (!pkt->tprot_set) 56 55 goto err; 57 - *dest = pkt->tprot; 56 + nft_reg_store8(dest, pkt->tprot); 58 57 break; 59 58 case NFT_META_PRIORITY: 60 59 *dest = skb->priority; ··· 84 85 case NFT_META_IIFTYPE: 85 86 if (in == NULL) 86 87 goto err; 87 - *dest = 0; 88 - *(u16 *)dest = in->type; 88 + nft_reg_store16(dest, in->type); 89 89 break; 90 90 case NFT_META_OIFTYPE: 91 91 if (out == NULL) 92 92 goto err; 93 - *dest = 0; 94 - *(u16 *)dest = out->type; 93 + nft_reg_store16(dest, out->type); 95 94 break; 96 95 case NFT_META_SKUID: 97 96 sk = skb_to_full_sk(skb); ··· 139 142 #endif 140 143 case NFT_META_PKTTYPE: 141 144 if (skb->pkt_type != PACKET_LOOPBACK) { 142 - *dest = skb->pkt_type; 145 + nft_reg_store8(dest, skb->pkt_type); 143 146 break; 144 147 } 145 148 146 149 switch (nft_pf(pkt)) { 147 150 case NFPROTO_IPV4: 148 151 if (ipv4_is_multicast(ip_hdr(skb)->daddr)) 149 - *dest = PACKET_MULTICAST; 152 + nft_reg_store8(dest, PACKET_MULTICAST); 150 153 else 151 - *dest = PACKET_BROADCAST; 154 + nft_reg_store8(dest, PACKET_BROADCAST); 152 155 break; 153 156 case NFPROTO_IPV6: 154 - *dest = PACKET_MULTICAST; 157 + nft_reg_store8(dest, PACKET_MULTICAST); 155 158 break; 156 159 case NFPROTO_NETDEV: 157 160 switch (skb->protocol) { ··· 165 168 goto err; 166 169 167 170 if (ipv4_is_multicast(iph->daddr)) 168 - *dest = PACKET_MULTICAST; 171 + nft_reg_store8(dest, PACKET_MULTICAST); 169 172 else 170 - *dest = PACKET_BROADCAST; 173 + nft_reg_store8(dest, PACKET_BROADCAST); 171 174 172 175 break; 173 176 } 174 177 case htons(ETH_P_IPV6): 175 - *dest = PACKET_MULTICAST; 178 + nft_reg_store8(dest, PACKET_MULTICAST); 176 179 break; 177 180 default: 178 181 WARN_ON_ONCE(1); ··· 227 230 { 228 231 const struct nft_meta *meta = nft_expr_priv(expr); 229 232 struct sk_buff *skb = pkt->skb; 230 - u32 value = regs->data[meta->sreg]; 233 + u32 *sreg = &regs->data[meta->sreg]; 234 + u32 value = *sreg; 235 + u8 pkt_type; 231 236 232 237 switch (meta->key) { 233 238 case NFT_META_MARK: ··· 239 240 skb->priority = value; 240 241 break; 241 242 case NFT_META_PKTTYPE: 242 - if (skb->pkt_type != value && 243 - skb_pkt_type_ok(value) && skb_pkt_type_ok(skb->pkt_type)) 244 - skb->pkt_type = value; 243 + pkt_type = nft_reg_load8(sreg); 244 + 245 + if (skb->pkt_type != pkt_type && 246 + skb_pkt_type_ok(pkt_type) && 247 + skb_pkt_type_ok(skb->pkt_type)) 248 + skb->pkt_type = pkt_type; 245 249 break; 246 250 case NFT_META_NFTRACE: 247 251 skb->nf_trace = !!value;
+4 -4
net/netfilter/nft_nat.c
··· 65 65 } 66 66 67 67 if (priv->sreg_proto_min) { 68 - range.min_proto.all = 69 - *(__be16 *)&regs->data[priv->sreg_proto_min]; 70 - range.max_proto.all = 71 - *(__be16 *)&regs->data[priv->sreg_proto_max]; 68 + range.min_proto.all = (__force __be16)nft_reg_load16( 69 + &regs->data[priv->sreg_proto_min]); 70 + range.max_proto.all = (__force __be16)nft_reg_load16( 71 + &regs->data[priv->sreg_proto_max]); 72 72 range.flags |= NF_NAT_RANGE_PROTO_SPECIFIED; 73 73 } 74 74