GNU Linux-libre 5.10.153-gnu1
[releases.git] / net / netfilter / nft_exthdr.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (c) 2008 Patrick McHardy <kaber@trash.net>
4  *
5  * Development of this code funded by Astaro AG (http://www.astaro.com/)
6  */
7
8 #include <asm/unaligned.h>
9 #include <linux/kernel.h>
10 #include <linux/netlink.h>
11 #include <linux/netfilter.h>
12 #include <linux/netfilter/nf_tables.h>
13 #include <net/netfilter/nf_tables_core.h>
14 #include <net/netfilter/nf_tables.h>
15 #include <net/tcp.h>
16
17 struct nft_exthdr {
18         u8                      type;
19         u8                      offset;
20         u8                      len;
21         u8                      op;
22         u8                      dreg;
23         u8                      sreg;
24         u8                      flags;
25 };
26
27 static unsigned int optlen(const u8 *opt, unsigned int offset)
28 {
29         /* Beware zero-length options: make finite progress */
30         if (opt[offset] <= TCPOPT_NOP || opt[offset + 1] == 0)
31                 return 1;
32         else
33                 return opt[offset + 1];
34 }
35
36 static void nft_exthdr_ipv6_eval(const struct nft_expr *expr,
37                                  struct nft_regs *regs,
38                                  const struct nft_pktinfo *pkt)
39 {
40         struct nft_exthdr *priv = nft_expr_priv(expr);
41         u32 *dest = &regs->data[priv->dreg];
42         unsigned int offset = 0;
43         int err;
44
45         if (pkt->skb->protocol != htons(ETH_P_IPV6))
46                 goto err;
47
48         err = ipv6_find_hdr(pkt->skb, &offset, priv->type, NULL, NULL);
49         if (priv->flags & NFT_EXTHDR_F_PRESENT) {
50                 nft_reg_store8(dest, err >= 0);
51                 return;
52         } else if (err < 0) {
53                 goto err;
54         }
55         offset += priv->offset;
56
57         dest[priv->len / NFT_REG32_SIZE] = 0;
58         if (skb_copy_bits(pkt->skb, offset, dest, priv->len) < 0)
59                 goto err;
60         return;
61 err:
62         regs->verdict.code = NFT_BREAK;
63 }
64
65 /* find the offset to specified option.
66  *
67  * If target header is found, its offset is set in *offset and return option
68  * number. Otherwise, return negative error.
69  *
70  * If the first fragment doesn't contain the End of Options it is considered
71  * invalid.
72  */
73 static int ipv4_find_option(struct net *net, struct sk_buff *skb,
74                             unsigned int *offset, int target)
75 {
76         unsigned char optbuf[sizeof(struct ip_options) + 40];
77         struct ip_options *opt = (struct ip_options *)optbuf;
78         struct iphdr *iph, _iph;
79         unsigned int start;
80         bool found = false;
81         __be32 info;
82         int optlen;
83
84         iph = skb_header_pointer(skb, 0, sizeof(_iph), &_iph);
85         if (!iph)
86                 return -EBADMSG;
87         start = sizeof(struct iphdr);
88
89         optlen = iph->ihl * 4 - (int)sizeof(struct iphdr);
90         if (optlen <= 0)
91                 return -ENOENT;
92
93         memset(opt, 0, sizeof(struct ip_options));
94         /* Copy the options since __ip_options_compile() modifies
95          * the options.
96          */
97         if (skb_copy_bits(skb, start, opt->__data, optlen))
98                 return -EBADMSG;
99         opt->optlen = optlen;
100
101         if (__ip_options_compile(net, opt, NULL, &info))
102                 return -EBADMSG;
103
104         switch (target) {
105         case IPOPT_SSRR:
106         case IPOPT_LSRR:
107                 if (!opt->srr)
108                         break;
109                 found = target == IPOPT_SSRR ? opt->is_strictroute :
110                                                !opt->is_strictroute;
111                 if (found)
112                         *offset = opt->srr + start;
113                 break;
114         case IPOPT_RR:
115                 if (!opt->rr)
116                         break;
117                 *offset = opt->rr + start;
118                 found = true;
119                 break;
120         case IPOPT_RA:
121                 if (!opt->router_alert)
122                         break;
123                 *offset = opt->router_alert + start;
124                 found = true;
125                 break;
126         default:
127                 return -EOPNOTSUPP;
128         }
129         return found ? target : -ENOENT;
130 }
131
132 static void nft_exthdr_ipv4_eval(const struct nft_expr *expr,
133                                  struct nft_regs *regs,
134                                  const struct nft_pktinfo *pkt)
135 {
136         struct nft_exthdr *priv = nft_expr_priv(expr);
137         u32 *dest = &regs->data[priv->dreg];
138         struct sk_buff *skb = pkt->skb;
139         unsigned int offset;
140         int err;
141
142         if (skb->protocol != htons(ETH_P_IP))
143                 goto err;
144
145         err = ipv4_find_option(nft_net(pkt), skb, &offset, priv->type);
146         if (priv->flags & NFT_EXTHDR_F_PRESENT) {
147                 nft_reg_store8(dest, err >= 0);
148                 return;
149         } else if (err < 0) {
150                 goto err;
151         }
152         offset += priv->offset;
153
154         dest[priv->len / NFT_REG32_SIZE] = 0;
155         if (skb_copy_bits(pkt->skb, offset, dest, priv->len) < 0)
156                 goto err;
157         return;
158 err:
159         regs->verdict.code = NFT_BREAK;
160 }
161
162 static void *
163 nft_tcp_header_pointer(const struct nft_pktinfo *pkt,
164                        unsigned int len, void *buffer, unsigned int *tcphdr_len)
165 {
166         struct tcphdr *tcph;
167
168         if (!pkt->tprot_set || pkt->tprot != IPPROTO_TCP)
169                 return NULL;
170
171         tcph = skb_header_pointer(pkt->skb, pkt->xt.thoff, sizeof(*tcph), buffer);
172         if (!tcph)
173                 return NULL;
174
175         *tcphdr_len = __tcp_hdrlen(tcph);
176         if (*tcphdr_len < sizeof(*tcph) || *tcphdr_len > len)
177                 return NULL;
178
179         return skb_header_pointer(pkt->skb, pkt->xt.thoff, *tcphdr_len, buffer);
180 }
181
182 static void nft_exthdr_tcp_eval(const struct nft_expr *expr,
183                                 struct nft_regs *regs,
184                                 const struct nft_pktinfo *pkt)
185 {
186         u8 buff[sizeof(struct tcphdr) + MAX_TCP_OPTION_SPACE];
187         struct nft_exthdr *priv = nft_expr_priv(expr);
188         unsigned int i, optl, tcphdr_len, offset;
189         u32 *dest = &regs->data[priv->dreg];
190         struct tcphdr *tcph;
191         u8 *opt;
192
193         tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff, &tcphdr_len);
194         if (!tcph)
195                 goto err;
196
197         opt = (u8 *)tcph;
198         for (i = sizeof(*tcph); i < tcphdr_len - 1; i += optl) {
199                 optl = optlen(opt, i);
200
201                 if (priv->type != opt[i])
202                         continue;
203
204                 if (i + optl > tcphdr_len || priv->len + priv->offset > optl)
205                         goto err;
206
207                 offset = i + priv->offset;
208                 if (priv->flags & NFT_EXTHDR_F_PRESENT) {
209                         *dest = 1;
210                 } else {
211                         dest[priv->len / NFT_REG32_SIZE] = 0;
212                         memcpy(dest, opt + offset, priv->len);
213                 }
214
215                 return;
216         }
217
218 err:
219         if (priv->flags & NFT_EXTHDR_F_PRESENT)
220                 *dest = 0;
221         else
222                 regs->verdict.code = NFT_BREAK;
223 }
224
225 static void nft_exthdr_tcp_set_eval(const struct nft_expr *expr,
226                                     struct nft_regs *regs,
227                                     const struct nft_pktinfo *pkt)
228 {
229         u8 buff[sizeof(struct tcphdr) + MAX_TCP_OPTION_SPACE];
230         struct nft_exthdr *priv = nft_expr_priv(expr);
231         unsigned int i, optl, tcphdr_len, offset;
232         struct tcphdr *tcph;
233         u8 *opt;
234
235         tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff, &tcphdr_len);
236         if (!tcph)
237                 return;
238
239         opt = (u8 *)tcph;
240         for (i = sizeof(*tcph); i < tcphdr_len - 1; i += optl) {
241                 union {
242                         __be16 v16;
243                         __be32 v32;
244                 } old, new;
245
246                 optl = optlen(opt, i);
247
248                 if (priv->type != opt[i])
249                         continue;
250
251                 if (i + optl > tcphdr_len || priv->len + priv->offset > optl)
252                         return;
253
254                 if (skb_ensure_writable(pkt->skb,
255                                         pkt->xt.thoff + i + priv->len))
256                         return;
257
258                 tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff,
259                                               &tcphdr_len);
260                 if (!tcph)
261                         return;
262
263                 offset = i + priv->offset;
264
265                 switch (priv->len) {
266                 case 2:
267                         old.v16 = get_unaligned((u16 *)(opt + offset));
268                         new.v16 = (__force __be16)nft_reg_load16(
269                                 &regs->data[priv->sreg]);
270
271                         switch (priv->type) {
272                         case TCPOPT_MSS:
273                                 /* increase can cause connection to stall */
274                                 if (ntohs(old.v16) <= ntohs(new.v16))
275                                         return;
276                         break;
277                         }
278
279                         if (old.v16 == new.v16)
280                                 return;
281
282                         put_unaligned(new.v16, (u16*)(opt + offset));
283                         inet_proto_csum_replace2(&tcph->check, pkt->skb,
284                                                  old.v16, new.v16, false);
285                         break;
286                 case 4:
287                         new.v32 = regs->data[priv->sreg];
288                         old.v32 = get_unaligned((u32 *)(opt + offset));
289
290                         if (old.v32 == new.v32)
291                                 return;
292
293                         put_unaligned(new.v32, (u32*)(opt + offset));
294                         inet_proto_csum_replace4(&tcph->check, pkt->skb,
295                                                  old.v32, new.v32, false);
296                         break;
297                 default:
298                         WARN_ON_ONCE(1);
299                         break;
300                 }
301
302                 return;
303         }
304 }
305
306 static const struct nla_policy nft_exthdr_policy[NFTA_EXTHDR_MAX + 1] = {
307         [NFTA_EXTHDR_DREG]              = { .type = NLA_U32 },
308         [NFTA_EXTHDR_TYPE]              = { .type = NLA_U8 },
309         [NFTA_EXTHDR_OFFSET]            = { .type = NLA_U32 },
310         [NFTA_EXTHDR_LEN]               = { .type = NLA_U32 },
311         [NFTA_EXTHDR_FLAGS]             = { .type = NLA_U32 },
312         [NFTA_EXTHDR_OP]                = { .type = NLA_U32 },
313         [NFTA_EXTHDR_SREG]              = { .type = NLA_U32 },
314 };
315
316 static int nft_exthdr_init(const struct nft_ctx *ctx,
317                            const struct nft_expr *expr,
318                            const struct nlattr * const tb[])
319 {
320         struct nft_exthdr *priv = nft_expr_priv(expr);
321         u32 offset, len, flags = 0, op = NFT_EXTHDR_OP_IPV6;
322         int err;
323
324         if (!tb[NFTA_EXTHDR_DREG] ||
325             !tb[NFTA_EXTHDR_TYPE] ||
326             !tb[NFTA_EXTHDR_OFFSET] ||
327             !tb[NFTA_EXTHDR_LEN])
328                 return -EINVAL;
329
330         err = nft_parse_u32_check(tb[NFTA_EXTHDR_OFFSET], U8_MAX, &offset);
331         if (err < 0)
332                 return err;
333
334         err = nft_parse_u32_check(tb[NFTA_EXTHDR_LEN], U8_MAX, &len);
335         if (err < 0)
336                 return err;
337
338         if (tb[NFTA_EXTHDR_FLAGS]) {
339                 err = nft_parse_u32_check(tb[NFTA_EXTHDR_FLAGS], U8_MAX, &flags);
340                 if (err < 0)
341                         return err;
342
343                 if (flags & ~NFT_EXTHDR_F_PRESENT)
344                         return -EINVAL;
345         }
346
347         if (tb[NFTA_EXTHDR_OP]) {
348                 err = nft_parse_u32_check(tb[NFTA_EXTHDR_OP], U8_MAX, &op);
349                 if (err < 0)
350                         return err;
351         }
352
353         priv->type   = nla_get_u8(tb[NFTA_EXTHDR_TYPE]);
354         priv->offset = offset;
355         priv->len    = len;
356         priv->flags  = flags;
357         priv->op     = op;
358
359         return nft_parse_register_store(ctx, tb[NFTA_EXTHDR_DREG],
360                                         &priv->dreg, NULL, NFT_DATA_VALUE,
361                                         priv->len);
362 }
363
364 static int nft_exthdr_tcp_set_init(const struct nft_ctx *ctx,
365                                    const struct nft_expr *expr,
366                                    const struct nlattr * const tb[])
367 {
368         struct nft_exthdr *priv = nft_expr_priv(expr);
369         u32 offset, len, flags = 0, op = NFT_EXTHDR_OP_IPV6;
370         int err;
371
372         if (!tb[NFTA_EXTHDR_SREG] ||
373             !tb[NFTA_EXTHDR_TYPE] ||
374             !tb[NFTA_EXTHDR_OFFSET] ||
375             !tb[NFTA_EXTHDR_LEN])
376                 return -EINVAL;
377
378         if (tb[NFTA_EXTHDR_DREG] || tb[NFTA_EXTHDR_FLAGS])
379                 return -EINVAL;
380
381         err = nft_parse_u32_check(tb[NFTA_EXTHDR_OFFSET], U8_MAX, &offset);
382         if (err < 0)
383                 return err;
384
385         err = nft_parse_u32_check(tb[NFTA_EXTHDR_LEN], U8_MAX, &len);
386         if (err < 0)
387                 return err;
388
389         if (offset < 2)
390                 return -EOPNOTSUPP;
391
392         switch (len) {
393         case 2: break;
394         case 4: break;
395         default:
396                 return -EOPNOTSUPP;
397         }
398
399         err = nft_parse_u32_check(tb[NFTA_EXTHDR_OP], U8_MAX, &op);
400         if (err < 0)
401                 return err;
402
403         priv->type   = nla_get_u8(tb[NFTA_EXTHDR_TYPE]);
404         priv->offset = offset;
405         priv->len    = len;
406         priv->flags  = flags;
407         priv->op     = op;
408
409         return nft_parse_register_load(tb[NFTA_EXTHDR_SREG], &priv->sreg,
410                                        priv->len);
411 }
412
413 static int nft_exthdr_ipv4_init(const struct nft_ctx *ctx,
414                                 const struct nft_expr *expr,
415                                 const struct nlattr * const tb[])
416 {
417         struct nft_exthdr *priv = nft_expr_priv(expr);
418         int err = nft_exthdr_init(ctx, expr, tb);
419
420         if (err < 0)
421                 return err;
422
423         switch (priv->type) {
424         case IPOPT_SSRR:
425         case IPOPT_LSRR:
426         case IPOPT_RR:
427         case IPOPT_RA:
428                 break;
429         default:
430                 return -EOPNOTSUPP;
431         }
432         return 0;
433 }
434
435 static int nft_exthdr_dump_common(struct sk_buff *skb, const struct nft_exthdr *priv)
436 {
437         if (nla_put_u8(skb, NFTA_EXTHDR_TYPE, priv->type))
438                 goto nla_put_failure;
439         if (nla_put_be32(skb, NFTA_EXTHDR_OFFSET, htonl(priv->offset)))
440                 goto nla_put_failure;
441         if (nla_put_be32(skb, NFTA_EXTHDR_LEN, htonl(priv->len)))
442                 goto nla_put_failure;
443         if (nla_put_be32(skb, NFTA_EXTHDR_FLAGS, htonl(priv->flags)))
444                 goto nla_put_failure;
445         if (nla_put_be32(skb, NFTA_EXTHDR_OP, htonl(priv->op)))
446                 goto nla_put_failure;
447         return 0;
448
449 nla_put_failure:
450         return -1;
451 }
452
453 static int nft_exthdr_dump(struct sk_buff *skb, const struct nft_expr *expr)
454 {
455         const struct nft_exthdr *priv = nft_expr_priv(expr);
456
457         if (nft_dump_register(skb, NFTA_EXTHDR_DREG, priv->dreg))
458                 return -1;
459
460         return nft_exthdr_dump_common(skb, priv);
461 }
462
463 static int nft_exthdr_dump_set(struct sk_buff *skb, const struct nft_expr *expr)
464 {
465         const struct nft_exthdr *priv = nft_expr_priv(expr);
466
467         if (nft_dump_register(skb, NFTA_EXTHDR_SREG, priv->sreg))
468                 return -1;
469
470         return nft_exthdr_dump_common(skb, priv);
471 }
472
473 static const struct nft_expr_ops nft_exthdr_ipv6_ops = {
474         .type           = &nft_exthdr_type,
475         .size           = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)),
476         .eval           = nft_exthdr_ipv6_eval,
477         .init           = nft_exthdr_init,
478         .dump           = nft_exthdr_dump,
479 };
480
481 static const struct nft_expr_ops nft_exthdr_ipv4_ops = {
482         .type           = &nft_exthdr_type,
483         .size           = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)),
484         .eval           = nft_exthdr_ipv4_eval,
485         .init           = nft_exthdr_ipv4_init,
486         .dump           = nft_exthdr_dump,
487 };
488
489 static const struct nft_expr_ops nft_exthdr_tcp_ops = {
490         .type           = &nft_exthdr_type,
491         .size           = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)),
492         .eval           = nft_exthdr_tcp_eval,
493         .init           = nft_exthdr_init,
494         .dump           = nft_exthdr_dump,
495 };
496
497 static const struct nft_expr_ops nft_exthdr_tcp_set_ops = {
498         .type           = &nft_exthdr_type,
499         .size           = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)),
500         .eval           = nft_exthdr_tcp_set_eval,
501         .init           = nft_exthdr_tcp_set_init,
502         .dump           = nft_exthdr_dump_set,
503 };
504
505 static const struct nft_expr_ops *
506 nft_exthdr_select_ops(const struct nft_ctx *ctx,
507                       const struct nlattr * const tb[])
508 {
509         u32 op;
510
511         if (!tb[NFTA_EXTHDR_OP])
512                 return &nft_exthdr_ipv6_ops;
513
514         if (tb[NFTA_EXTHDR_SREG] && tb[NFTA_EXTHDR_DREG])
515                 return ERR_PTR(-EOPNOTSUPP);
516
517         op = ntohl(nla_get_be32(tb[NFTA_EXTHDR_OP]));
518         switch (op) {
519         case NFT_EXTHDR_OP_TCPOPT:
520                 if (tb[NFTA_EXTHDR_SREG])
521                         return &nft_exthdr_tcp_set_ops;
522                 if (tb[NFTA_EXTHDR_DREG])
523                         return &nft_exthdr_tcp_ops;
524                 break;
525         case NFT_EXTHDR_OP_IPV6:
526                 if (tb[NFTA_EXTHDR_DREG])
527                         return &nft_exthdr_ipv6_ops;
528                 break;
529         case NFT_EXTHDR_OP_IPV4:
530                 if (ctx->family != NFPROTO_IPV6) {
531                         if (tb[NFTA_EXTHDR_DREG])
532                                 return &nft_exthdr_ipv4_ops;
533                 }
534                 break;
535         }
536
537         return ERR_PTR(-EOPNOTSUPP);
538 }
539
540 struct nft_expr_type nft_exthdr_type __read_mostly = {
541         .name           = "exthdr",
542         .select_ops     = nft_exthdr_select_ops,
543         .policy         = nft_exthdr_policy,
544         .maxattr        = NFTA_EXTHDR_MAX,
545         .owner          = THIS_MODULE,
546 };