GNU Linux-libre 4.14.290-gnu1
[releases.git] / net / netfilter / nft_exthdr.c
1 /*
2  * Copyright (c) 2008 Patrick McHardy <kaber@trash.net>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License version 2 as
6  * published by the Free Software Foundation.
7  *
8  * Development of this code funded by Astaro AG (http://www.astaro.com/)
9  */
10
11 #include <asm/unaligned.h>
12 #include <linux/kernel.h>
13 #include <linux/init.h>
14 #include <linux/module.h>
15 #include <linux/netlink.h>
16 #include <linux/netfilter.h>
17 #include <linux/netfilter/nf_tables.h>
18 #include <net/netfilter/nf_tables.h>
19 #include <net/tcp.h>
20
21 struct nft_exthdr {
22         u8                      type;
23         u8                      offset;
24         u8                      len;
25         u8                      op;
26         enum nft_registers      dreg:8;
27         enum nft_registers      sreg:8;
28         u8                      flags;
29 };
30
31 static unsigned int optlen(const u8 *opt, unsigned int offset)
32 {
33         /* Beware zero-length options: make finite progress */
34         if (opt[offset] <= TCPOPT_NOP || opt[offset + 1] == 0)
35                 return 1;
36         else
37                 return opt[offset + 1];
38 }
39
40 static void nft_exthdr_ipv6_eval(const struct nft_expr *expr,
41                                  struct nft_regs *regs,
42                                  const struct nft_pktinfo *pkt)
43 {
44         struct nft_exthdr *priv = nft_expr_priv(expr);
45         u32 *dest = &regs->data[priv->dreg];
46         unsigned int offset = 0;
47         int err;
48
49         if (pkt->skb->protocol != htons(ETH_P_IPV6))
50                 goto err;
51
52         err = ipv6_find_hdr(pkt->skb, &offset, priv->type, NULL, NULL);
53         if (priv->flags & NFT_EXTHDR_F_PRESENT) {
54                 *dest = (err >= 0);
55                 return;
56         } else if (err < 0) {
57                 goto err;
58         }
59         offset += priv->offset;
60
61         dest[priv->len / NFT_REG32_SIZE] = 0;
62         if (skb_copy_bits(pkt->skb, offset, dest, priv->len) < 0)
63                 goto err;
64         return;
65 err:
66         regs->verdict.code = NFT_BREAK;
67 }
68
69 static void *
70 nft_tcp_header_pointer(const struct nft_pktinfo *pkt,
71                        unsigned int len, void *buffer, unsigned int *tcphdr_len)
72 {
73         struct tcphdr *tcph;
74
75         if (!pkt->tprot_set || pkt->tprot != IPPROTO_TCP)
76                 return NULL;
77
78         tcph = skb_header_pointer(pkt->skb, pkt->xt.thoff, sizeof(*tcph), buffer);
79         if (!tcph)
80                 return NULL;
81
82         *tcphdr_len = __tcp_hdrlen(tcph);
83         if (*tcphdr_len < sizeof(*tcph) || *tcphdr_len > len)
84                 return NULL;
85
86         return skb_header_pointer(pkt->skb, pkt->xt.thoff, *tcphdr_len, buffer);
87 }
88
89 static void nft_exthdr_tcp_eval(const struct nft_expr *expr,
90                                 struct nft_regs *regs,
91                                 const struct nft_pktinfo *pkt)
92 {
93         u8 buff[sizeof(struct tcphdr) + MAX_TCP_OPTION_SPACE];
94         struct nft_exthdr *priv = nft_expr_priv(expr);
95         unsigned int i, optl, tcphdr_len, offset;
96         u32 *dest = &regs->data[priv->dreg];
97         struct tcphdr *tcph;
98         u8 *opt;
99
100         tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff, &tcphdr_len);
101         if (!tcph)
102                 goto err;
103
104         opt = (u8 *)tcph;
105         for (i = sizeof(*tcph); i < tcphdr_len - 1; i += optl) {
106                 optl = optlen(opt, i);
107
108                 if (priv->type != opt[i])
109                         continue;
110
111                 if (i + optl > tcphdr_len || priv->len + priv->offset > optl)
112                         goto err;
113
114                 offset = i + priv->offset;
115                 if (priv->flags & NFT_EXTHDR_F_PRESENT) {
116                         *dest = 1;
117                 } else {
118                         dest[priv->len / NFT_REG32_SIZE] = 0;
119                         memcpy(dest, opt + offset, priv->len);
120                 }
121
122                 return;
123         }
124
125 err:
126         if (priv->flags & NFT_EXTHDR_F_PRESENT)
127                 *dest = 0;
128         else
129                 regs->verdict.code = NFT_BREAK;
130 }
131
132 static void nft_exthdr_tcp_set_eval(const struct nft_expr *expr,
133                                     struct nft_regs *regs,
134                                     const struct nft_pktinfo *pkt)
135 {
136         u8 buff[sizeof(struct tcphdr) + MAX_TCP_OPTION_SPACE];
137         struct nft_exthdr *priv = nft_expr_priv(expr);
138         unsigned int i, optl, tcphdr_len, offset;
139         struct tcphdr *tcph;
140         u8 *opt;
141
142         tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff, &tcphdr_len);
143         if (!tcph)
144                 return;
145
146         opt = (u8 *)tcph;
147         for (i = sizeof(*tcph); i < tcphdr_len - 1; i += optl) {
148                 union {
149                         __be16 v16;
150                         __be32 v32;
151                 } old, new;
152
153                 optl = optlen(opt, i);
154
155                 if (priv->type != opt[i])
156                         continue;
157
158                 if (i + optl > tcphdr_len || priv->len + priv->offset > optl)
159                         return;
160
161                 if (!skb_make_writable(pkt->skb, pkt->xt.thoff + i + priv->len))
162                         return;
163
164                 tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff,
165                                               &tcphdr_len);
166                 if (!tcph)
167                         return;
168
169                 offset = i + priv->offset;
170
171                 switch (priv->len) {
172                 case 2:
173                         old.v16 = get_unaligned((u16 *)(opt + offset));
174                         new.v16 = (__force __be16)nft_reg_load16(
175                                 &regs->data[priv->sreg]);
176
177                         switch (priv->type) {
178                         case TCPOPT_MSS:
179                                 /* increase can cause connection to stall */
180                                 if (ntohs(old.v16) <= ntohs(new.v16))
181                                         return;
182                         break;
183                         }
184
185                         if (old.v16 == new.v16)
186                                 return;
187
188                         put_unaligned(new.v16, (u16*)(opt + offset));
189                         inet_proto_csum_replace2(&tcph->check, pkt->skb,
190                                                  old.v16, new.v16, false);
191                         break;
192                 case 4:
193                         new.v32 = regs->data[priv->sreg];
194                         old.v32 = get_unaligned((u32 *)(opt + offset));
195
196                         if (old.v32 == new.v32)
197                                 return;
198
199                         put_unaligned(new.v32, (u32*)(opt + offset));
200                         inet_proto_csum_replace4(&tcph->check, pkt->skb,
201                                                  old.v32, new.v32, false);
202                         break;
203                 default:
204                         WARN_ON_ONCE(1);
205                         break;
206                 }
207
208                 return;
209         }
210 }
211
212 static const struct nla_policy nft_exthdr_policy[NFTA_EXTHDR_MAX + 1] = {
213         [NFTA_EXTHDR_DREG]              = { .type = NLA_U32 },
214         [NFTA_EXTHDR_TYPE]              = { .type = NLA_U8 },
215         [NFTA_EXTHDR_OFFSET]            = { .type = NLA_U32 },
216         [NFTA_EXTHDR_LEN]               = { .type = NLA_U32 },
217         [NFTA_EXTHDR_FLAGS]             = { .type = NLA_U32 },
218 };
219
220 static int nft_exthdr_init(const struct nft_ctx *ctx,
221                            const struct nft_expr *expr,
222                            const struct nlattr * const tb[])
223 {
224         struct nft_exthdr *priv = nft_expr_priv(expr);
225         u32 offset, len, flags = 0, op = NFT_EXTHDR_OP_IPV6;
226         int err;
227
228         if (!tb[NFTA_EXTHDR_DREG] ||
229             !tb[NFTA_EXTHDR_TYPE] ||
230             !tb[NFTA_EXTHDR_OFFSET] ||
231             !tb[NFTA_EXTHDR_LEN])
232                 return -EINVAL;
233
234         err = nft_parse_u32_check(tb[NFTA_EXTHDR_OFFSET], U8_MAX, &offset);
235         if (err < 0)
236                 return err;
237
238         err = nft_parse_u32_check(tb[NFTA_EXTHDR_LEN], U8_MAX, &len);
239         if (err < 0)
240                 return err;
241
242         if (tb[NFTA_EXTHDR_FLAGS]) {
243                 err = nft_parse_u32_check(tb[NFTA_EXTHDR_FLAGS], U8_MAX, &flags);
244                 if (err < 0)
245                         return err;
246
247                 if (flags & ~NFT_EXTHDR_F_PRESENT)
248                         return -EINVAL;
249         }
250
251         if (tb[NFTA_EXTHDR_OP]) {
252                 err = nft_parse_u32_check(tb[NFTA_EXTHDR_OP], U8_MAX, &op);
253                 if (err < 0)
254                         return err;
255         }
256
257         priv->type   = nla_get_u8(tb[NFTA_EXTHDR_TYPE]);
258         priv->offset = offset;
259         priv->len    = len;
260         priv->dreg   = nft_parse_register(tb[NFTA_EXTHDR_DREG]);
261         priv->flags  = flags;
262         priv->op     = op;
263
264         return nft_validate_register_store(ctx, priv->dreg, NULL,
265                                            NFT_DATA_VALUE, priv->len);
266 }
267
268 static int nft_exthdr_tcp_set_init(const struct nft_ctx *ctx,
269                                    const struct nft_expr *expr,
270                                    const struct nlattr * const tb[])
271 {
272         struct nft_exthdr *priv = nft_expr_priv(expr);
273         u32 offset, len, flags = 0, op = NFT_EXTHDR_OP_IPV6;
274         int err;
275
276         if (!tb[NFTA_EXTHDR_SREG] ||
277             !tb[NFTA_EXTHDR_TYPE] ||
278             !tb[NFTA_EXTHDR_OFFSET] ||
279             !tb[NFTA_EXTHDR_LEN])
280                 return -EINVAL;
281
282         if (tb[NFTA_EXTHDR_DREG] || tb[NFTA_EXTHDR_FLAGS])
283                 return -EINVAL;
284
285         err = nft_parse_u32_check(tb[NFTA_EXTHDR_OFFSET], U8_MAX, &offset);
286         if (err < 0)
287                 return err;
288
289         err = nft_parse_u32_check(tb[NFTA_EXTHDR_LEN], U8_MAX, &len);
290         if (err < 0)
291                 return err;
292
293         if (offset < 2)
294                 return -EOPNOTSUPP;
295
296         switch (len) {
297         case 2: break;
298         case 4: break;
299         default:
300                 return -EOPNOTSUPP;
301         }
302
303         err = nft_parse_u32_check(tb[NFTA_EXTHDR_OP], U8_MAX, &op);
304         if (err < 0)
305                 return err;
306
307         priv->type   = nla_get_u8(tb[NFTA_EXTHDR_TYPE]);
308         priv->offset = offset;
309         priv->len    = len;
310         priv->sreg   = nft_parse_register(tb[NFTA_EXTHDR_SREG]);
311         priv->flags  = flags;
312         priv->op     = op;
313
314         return nft_validate_register_load(priv->sreg, priv->len);
315 }
316
317 static int nft_exthdr_dump_common(struct sk_buff *skb, const struct nft_exthdr *priv)
318 {
319         if (nla_put_u8(skb, NFTA_EXTHDR_TYPE, priv->type))
320                 goto nla_put_failure;
321         if (nla_put_be32(skb, NFTA_EXTHDR_OFFSET, htonl(priv->offset)))
322                 goto nla_put_failure;
323         if (nla_put_be32(skb, NFTA_EXTHDR_LEN, htonl(priv->len)))
324                 goto nla_put_failure;
325         if (nla_put_be32(skb, NFTA_EXTHDR_FLAGS, htonl(priv->flags)))
326                 goto nla_put_failure;
327         if (nla_put_be32(skb, NFTA_EXTHDR_OP, htonl(priv->op)))
328                 goto nla_put_failure;
329         return 0;
330
331 nla_put_failure:
332         return -1;
333 }
334
335 static int nft_exthdr_dump(struct sk_buff *skb, const struct nft_expr *expr)
336 {
337         const struct nft_exthdr *priv = nft_expr_priv(expr);
338
339         if (nft_dump_register(skb, NFTA_EXTHDR_DREG, priv->dreg))
340                 return -1;
341
342         return nft_exthdr_dump_common(skb, priv);
343 }
344
345 static int nft_exthdr_dump_set(struct sk_buff *skb, const struct nft_expr *expr)
346 {
347         const struct nft_exthdr *priv = nft_expr_priv(expr);
348
349         if (nft_dump_register(skb, NFTA_EXTHDR_SREG, priv->sreg))
350                 return -1;
351
352         return nft_exthdr_dump_common(skb, priv);
353 }
354
355 static struct nft_expr_type nft_exthdr_type;
356 static const struct nft_expr_ops nft_exthdr_ipv6_ops = {
357         .type           = &nft_exthdr_type,
358         .size           = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)),
359         .eval           = nft_exthdr_ipv6_eval,
360         .init           = nft_exthdr_init,
361         .dump           = nft_exthdr_dump,
362 };
363
364 static const struct nft_expr_ops nft_exthdr_tcp_ops = {
365         .type           = &nft_exthdr_type,
366         .size           = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)),
367         .eval           = nft_exthdr_tcp_eval,
368         .init           = nft_exthdr_init,
369         .dump           = nft_exthdr_dump,
370 };
371
372 static const struct nft_expr_ops nft_exthdr_tcp_set_ops = {
373         .type           = &nft_exthdr_type,
374         .size           = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)),
375         .eval           = nft_exthdr_tcp_set_eval,
376         .init           = nft_exthdr_tcp_set_init,
377         .dump           = nft_exthdr_dump_set,
378 };
379
380 static const struct nft_expr_ops *
381 nft_exthdr_select_ops(const struct nft_ctx *ctx,
382                       const struct nlattr * const tb[])
383 {
384         u32 op;
385
386         if (!tb[NFTA_EXTHDR_OP])
387                 return &nft_exthdr_ipv6_ops;
388
389         if (tb[NFTA_EXTHDR_SREG] && tb[NFTA_EXTHDR_DREG])
390                 return ERR_PTR(-EOPNOTSUPP);
391
392         op = ntohl(nla_get_be32(tb[NFTA_EXTHDR_OP]));
393         switch (op) {
394         case NFT_EXTHDR_OP_TCPOPT:
395                 if (tb[NFTA_EXTHDR_SREG])
396                         return &nft_exthdr_tcp_set_ops;
397                 if (tb[NFTA_EXTHDR_DREG])
398                         return &nft_exthdr_tcp_ops;
399                 break;
400         case NFT_EXTHDR_OP_IPV6:
401                 if (tb[NFTA_EXTHDR_DREG])
402                         return &nft_exthdr_ipv6_ops;
403                 break;
404         }
405
406         return ERR_PTR(-EOPNOTSUPP);
407 }
408
409 static struct nft_expr_type nft_exthdr_type __read_mostly = {
410         .name           = "exthdr",
411         .select_ops     = nft_exthdr_select_ops,
412         .policy         = nft_exthdr_policy,
413         .maxattr        = NFTA_EXTHDR_MAX,
414         .owner          = THIS_MODULE,
415 };
416
417 static int __init nft_exthdr_module_init(void)
418 {
419         return nft_register_expr(&nft_exthdr_type);
420 }
421
422 static void __exit nft_exthdr_module_exit(void)
423 {
424         nft_unregister_expr(&nft_exthdr_type);
425 }
426
427 module_init(nft_exthdr_module_init);
428 module_exit(nft_exthdr_module_exit);
429
430 MODULE_LICENSE("GPL");
431 MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
432 MODULE_ALIAS_NFT_EXPR("exthdr");