1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * ebt_among |
4 | * |
5 | * Authors: |
6 | * Grzegorz Borowiak <grzes@gnu.univ.gda.pl> |
7 | * |
8 | * August, 2003 |
9 | * |
10 | */ |
11 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
12 | #include <linux/ip.h> |
13 | #include <linux/if_arp.h> |
14 | #include <linux/module.h> |
15 | #include <linux/netfilter/x_tables.h> |
16 | #include <linux/netfilter_bridge/ebtables.h> |
17 | #include <linux/netfilter_bridge/ebt_among.h> |
18 | |
19 | static bool ebt_mac_wormhash_contains(const struct ebt_mac_wormhash *wh, |
20 | const char *mac, __be32 ip) |
21 | { |
22 | /* You may be puzzled as to how this code works. |
23 | * Some tricks were used, refer to |
24 | * include/linux/netfilter_bridge/ebt_among.h |
25 | * as there you can find a solution of this mystery. |
26 | */ |
27 | const struct ebt_mac_wormhash_tuple *p; |
28 | int start, limit, i; |
29 | uint32_t cmp[2] = { 0, 0 }; |
30 | int key = ((const unsigned char *)mac)[5]; |
31 | |
32 | ether_addr_copy(dst: ((char *) cmp) + 2, src: mac); |
33 | start = wh->table[key]; |
34 | limit = wh->table[key + 1]; |
35 | if (ip) { |
36 | for (i = start; i < limit; i++) { |
37 | p = &wh->pool[i]; |
38 | if (cmp[1] == p->cmp[1] && cmp[0] == p->cmp[0]) |
39 | if (p->ip == 0 || p->ip == ip) |
40 | return true; |
41 | } |
42 | } else { |
43 | for (i = start; i < limit; i++) { |
44 | p = &wh->pool[i]; |
45 | if (cmp[1] == p->cmp[1] && cmp[0] == p->cmp[0]) |
46 | if (p->ip == 0) |
47 | return true; |
48 | } |
49 | } |
50 | return false; |
51 | } |
52 | |
53 | static int ebt_mac_wormhash_check_integrity(const struct ebt_mac_wormhash |
54 | *wh) |
55 | { |
56 | int i; |
57 | |
58 | for (i = 0; i < 256; i++) { |
59 | if (wh->table[i] > wh->table[i + 1]) |
60 | return -0x100 - i; |
61 | if (wh->table[i] < 0) |
62 | return -0x200 - i; |
63 | if (wh->table[i] > wh->poolsize) |
64 | return -0x300 - i; |
65 | } |
66 | if (wh->table[256] > wh->poolsize) |
67 | return -0xc00; |
68 | return 0; |
69 | } |
70 | |
71 | static int get_ip_dst(const struct sk_buff *skb, __be32 *addr) |
72 | { |
73 | if (eth_hdr(skb)->h_proto == htons(ETH_P_IP)) { |
74 | const struct iphdr *ih; |
75 | struct iphdr _iph; |
76 | |
77 | ih = skb_header_pointer(skb, offset: 0, len: sizeof(_iph), buffer: &_iph); |
78 | if (ih == NULL) |
79 | return -1; |
80 | *addr = ih->daddr; |
81 | } else if (eth_hdr(skb)->h_proto == htons(ETH_P_ARP)) { |
82 | const struct arphdr *ah; |
83 | struct arphdr _arph; |
84 | const __be32 *bp; |
85 | __be32 buf; |
86 | |
87 | ah = skb_header_pointer(skb, offset: 0, len: sizeof(_arph), buffer: &_arph); |
88 | if (ah == NULL || |
89 | ah->ar_pln != sizeof(__be32) || |
90 | ah->ar_hln != ETH_ALEN) |
91 | return -1; |
92 | bp = skb_header_pointer(skb, offset: sizeof(struct arphdr) + |
93 | 2 * ETH_ALEN + sizeof(__be32), |
94 | len: sizeof(__be32), buffer: &buf); |
95 | if (bp == NULL) |
96 | return -1; |
97 | *addr = *bp; |
98 | } |
99 | return 0; |
100 | } |
101 | |
102 | static int get_ip_src(const struct sk_buff *skb, __be32 *addr) |
103 | { |
104 | if (eth_hdr(skb)->h_proto == htons(ETH_P_IP)) { |
105 | const struct iphdr *ih; |
106 | struct iphdr _iph; |
107 | |
108 | ih = skb_header_pointer(skb, offset: 0, len: sizeof(_iph), buffer: &_iph); |
109 | if (ih == NULL) |
110 | return -1; |
111 | *addr = ih->saddr; |
112 | } else if (eth_hdr(skb)->h_proto == htons(ETH_P_ARP)) { |
113 | const struct arphdr *ah; |
114 | struct arphdr _arph; |
115 | const __be32 *bp; |
116 | __be32 buf; |
117 | |
118 | ah = skb_header_pointer(skb, offset: 0, len: sizeof(_arph), buffer: &_arph); |
119 | if (ah == NULL || |
120 | ah->ar_pln != sizeof(__be32) || |
121 | ah->ar_hln != ETH_ALEN) |
122 | return -1; |
123 | bp = skb_header_pointer(skb, offset: sizeof(struct arphdr) + |
124 | ETH_ALEN, len: sizeof(__be32), buffer: &buf); |
125 | if (bp == NULL) |
126 | return -1; |
127 | *addr = *bp; |
128 | } |
129 | return 0; |
130 | } |
131 | |
132 | static bool |
133 | ebt_among_mt(const struct sk_buff *skb, struct xt_action_param *par) |
134 | { |
135 | const struct ebt_among_info *info = par->matchinfo; |
136 | const char *dmac, *smac; |
137 | const struct ebt_mac_wormhash *wh_dst, *wh_src; |
138 | __be32 dip = 0, sip = 0; |
139 | |
140 | wh_dst = ebt_among_wh_dst(info); |
141 | wh_src = ebt_among_wh_src(info); |
142 | |
143 | if (wh_src) { |
144 | smac = eth_hdr(skb)->h_source; |
145 | if (get_ip_src(skb, addr: &sip)) |
146 | return false; |
147 | if (!(info->bitmask & EBT_AMONG_SRC_NEG)) { |
148 | /* we match only if it contains */ |
149 | if (!ebt_mac_wormhash_contains(wh: wh_src, mac: smac, ip: sip)) |
150 | return false; |
151 | } else { |
152 | /* we match only if it DOES NOT contain */ |
153 | if (ebt_mac_wormhash_contains(wh: wh_src, mac: smac, ip: sip)) |
154 | return false; |
155 | } |
156 | } |
157 | |
158 | if (wh_dst) { |
159 | dmac = eth_hdr(skb)->h_dest; |
160 | if (get_ip_dst(skb, addr: &dip)) |
161 | return false; |
162 | if (!(info->bitmask & EBT_AMONG_DST_NEG)) { |
163 | /* we match only if it contains */ |
164 | if (!ebt_mac_wormhash_contains(wh: wh_dst, mac: dmac, ip: dip)) |
165 | return false; |
166 | } else { |
167 | /* we match only if it DOES NOT contain */ |
168 | if (ebt_mac_wormhash_contains(wh: wh_dst, mac: dmac, ip: dip)) |
169 | return false; |
170 | } |
171 | } |
172 | |
173 | return true; |
174 | } |
175 | |
176 | static bool poolsize_invalid(const struct ebt_mac_wormhash *w) |
177 | { |
178 | return w && w->poolsize >= (INT_MAX / sizeof(struct ebt_mac_wormhash_tuple)); |
179 | } |
180 | |
181 | static bool wormhash_offset_invalid(int off, unsigned int len) |
182 | { |
183 | if (off == 0) /* not present */ |
184 | return false; |
185 | |
186 | if (off < (int)sizeof(struct ebt_among_info) || |
187 | off % __alignof__(struct ebt_mac_wormhash)) |
188 | return true; |
189 | |
190 | off += sizeof(struct ebt_mac_wormhash); |
191 | |
192 | return off > len; |
193 | } |
194 | |
195 | static bool wormhash_sizes_valid(const struct ebt_mac_wormhash *wh, int a, int b) |
196 | { |
197 | if (a == 0) |
198 | a = sizeof(struct ebt_among_info); |
199 | |
200 | return ebt_mac_wormhash_size(wh) + a == b; |
201 | } |
202 | |
203 | static int ebt_among_mt_check(const struct xt_mtchk_param *par) |
204 | { |
205 | const struct ebt_among_info *info = par->matchinfo; |
206 | const struct ebt_entry_match *em = |
207 | container_of(par->matchinfo, const struct ebt_entry_match, data); |
208 | unsigned int expected_length = sizeof(struct ebt_among_info); |
209 | const struct ebt_mac_wormhash *wh_dst, *wh_src; |
210 | int err; |
211 | |
212 | if (expected_length > em->match_size) |
213 | return -EINVAL; |
214 | |
215 | if (wormhash_offset_invalid(off: info->wh_dst_ofs, len: em->match_size) || |
216 | wormhash_offset_invalid(off: info->wh_src_ofs, len: em->match_size)) |
217 | return -EINVAL; |
218 | |
219 | wh_dst = ebt_among_wh_dst(info); |
220 | if (poolsize_invalid(w: wh_dst)) |
221 | return -EINVAL; |
222 | |
223 | expected_length += ebt_mac_wormhash_size(wh_dst); |
224 | if (expected_length > em->match_size) |
225 | return -EINVAL; |
226 | |
227 | wh_src = ebt_among_wh_src(info); |
228 | if (poolsize_invalid(w: wh_src)) |
229 | return -EINVAL; |
230 | |
231 | if (info->wh_src_ofs < info->wh_dst_ofs) { |
232 | if (!wormhash_sizes_valid(wh: wh_src, a: info->wh_src_ofs, b: info->wh_dst_ofs)) |
233 | return -EINVAL; |
234 | } else { |
235 | if (!wormhash_sizes_valid(wh: wh_dst, a: info->wh_dst_ofs, b: info->wh_src_ofs)) |
236 | return -EINVAL; |
237 | } |
238 | |
239 | expected_length += ebt_mac_wormhash_size(wh_src); |
240 | |
241 | if (em->match_size != EBT_ALIGN(expected_length)) { |
242 | pr_err_ratelimited("wrong size: %d against expected %d, rounded to %zd\n" , |
243 | em->match_size, expected_length, |
244 | EBT_ALIGN(expected_length)); |
245 | return -EINVAL; |
246 | } |
247 | if (wh_dst && (err = ebt_mac_wormhash_check_integrity(wh: wh_dst))) { |
248 | pr_err_ratelimited("dst integrity fail: %x\n" , -err); |
249 | return -EINVAL; |
250 | } |
251 | if (wh_src && (err = ebt_mac_wormhash_check_integrity(wh: wh_src))) { |
252 | pr_err_ratelimited("src integrity fail: %x\n" , -err); |
253 | return -EINVAL; |
254 | } |
255 | return 0; |
256 | } |
257 | |
258 | static struct xt_match ebt_among_mt_reg __read_mostly = { |
259 | .name = "among" , |
260 | .revision = 0, |
261 | .family = NFPROTO_BRIDGE, |
262 | .match = ebt_among_mt, |
263 | .checkentry = ebt_among_mt_check, |
264 | .matchsize = -1, /* special case */ |
265 | .me = THIS_MODULE, |
266 | }; |
267 | |
268 | static int __init ebt_among_init(void) |
269 | { |
270 | return xt_register_match(target: &ebt_among_mt_reg); |
271 | } |
272 | |
273 | static void __exit ebt_among_fini(void) |
274 | { |
275 | xt_unregister_match(target: &ebt_among_mt_reg); |
276 | } |
277 | |
278 | module_init(ebt_among_init); |
279 | module_exit(ebt_among_fini); |
280 | MODULE_DESCRIPTION("Ebtables: Combined MAC/IP address list matching" ); |
281 | MODULE_LICENSE("GPL" ); |
282 | |