GNU Linux-libre 4.19.207-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/netlink.h>
14 #include <linux/netfilter.h>
15 #include <linux/netfilter/nf_tables.h>
16 #include <net/netfilter/nf_tables_core.h>
17 #include <net/netfilter/nf_tables.h>
18 #include <net/tcp.h>
19
20 struct nft_exthdr {
21         u8                      type;
22         u8                      offset;
23         u8                      len;
24         u8                      op;
25         enum nft_registers      dreg:8;
26         enum nft_registers      sreg:8;
27         u8                      flags;
28 };
29
30 static unsigned int optlen(const u8 *opt, unsigned int offset)
31 {
32         /* Beware zero-length options: make finite progress */
33         if (opt[offset] <= TCPOPT_NOP || opt[offset + 1] == 0)
34                 return 1;
35         else
36                 return opt[offset + 1];
37 }
38
39 static void nft_exthdr_ipv6_eval(const struct nft_expr *expr,
40                                  struct nft_regs *regs,
41                                  const struct nft_pktinfo *pkt)
42 {
43         struct nft_exthdr *priv = nft_expr_priv(expr);
44         u32 *dest = &regs->data[priv->dreg];
45         unsigned int offset = 0;
46         int err;
47
48         if (pkt->skb->protocol != htons(ETH_P_IPV6))
49                 goto err;
50
51         err = ipv6_find_hdr(pkt->skb, &offset, priv->type, NULL, NULL);
52         if (priv->flags & NFT_EXTHDR_F_PRESENT) {
53                 *dest = (err >= 0);
54                 return;
55         } else if (err < 0) {
56                 goto err;
57         }
58         offset += priv->offset;
59
60         dest[priv->len / NFT_REG32_SIZE] = 0;
61         if (skb_copy_bits(pkt->skb, offset, dest, priv->len) < 0)
62                 goto err;
63         return;
64 err:
65         regs->verdict.code = NFT_BREAK;
66 }
67
68 static void *
69 nft_tcp_header_pointer(const struct nft_pktinfo *pkt,
70                        unsigned int len, void *buffer, unsigned int *tcphdr_len)
71 {
72         struct tcphdr *tcph;
73
74         if (!pkt->tprot_set || pkt->tprot != IPPROTO_TCP)
75                 return NULL;
76
77         tcph = skb_header_pointer(pkt->skb, pkt->xt.thoff, sizeof(*tcph), buffer);
78         if (!tcph)
79                 return NULL;
80
81         *tcphdr_len = __tcp_hdrlen(tcph);
82         if (*tcphdr_len < sizeof(*tcph) || *tcphdr_len > len)
83                 return NULL;
84
85         return skb_header_pointer(pkt->skb, pkt->xt.thoff, *tcphdr_len, buffer);
86 }
87
88 static void nft_exthdr_tcp_eval(const struct nft_expr *expr,
89                                 struct nft_regs *regs,
90                                 const struct nft_pktinfo *pkt)
91 {
92         u8 buff[sizeof(struct tcphdr) + MAX_TCP_OPTION_SPACE];
93         struct nft_exthdr *priv = nft_expr_priv(expr);
94         unsigned int i, optl, tcphdr_len, offset;
95         u32 *dest = &regs->data[priv->dreg];
96         struct tcphdr *tcph;
97         u8 *opt;
98
99         tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff, &tcphdr_len);
100         if (!tcph)
101                 goto err;
102
103         opt = (u8 *)tcph;
104         for (i = sizeof(*tcph); i < tcphdr_len - 1; i += optl) {
105                 optl = optlen(opt, i);
106
107                 if (priv->type != opt[i])
108                         continue;
109
110                 if (i + optl > tcphdr_len || priv->len + priv->offset > optl)
111                         goto err;
112
113                 offset = i + priv->offset;
114                 if (priv->flags & NFT_EXTHDR_F_PRESENT) {
115                         *dest = 1;
116                 } else {
117                         dest[priv->len / NFT_REG32_SIZE] = 0;
118                         memcpy(dest, opt + offset, priv->len);
119                 }
120
121                 return;
122         }
123
124 err:
125         if (priv->flags & NFT_EXTHDR_F_PRESENT)
126                 *dest = 0;
127         else
128                 regs->verdict.code = NFT_BREAK;
129 }
130
131 static void nft_exthdr_tcp_set_eval(const struct nft_expr *expr,
132                                     struct nft_regs *regs,
133                                     const struct nft_pktinfo *pkt)
134 {
135         u8 buff[sizeof(struct tcphdr) + MAX_TCP_OPTION_SPACE];
136         struct nft_exthdr *priv = nft_expr_priv(expr);
137         unsigned int i, optl, tcphdr_len, offset;
138         struct tcphdr *tcph;
139         u8 *opt;
140
141         tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff, &tcphdr_len);
142         if (!tcph)
143                 return;
144
145         opt = (u8 *)tcph;
146         for (i = sizeof(*tcph); i < tcphdr_len - 1; i += optl) {
147                 union {
148                         __be16 v16;
149                         __be32 v32;
150                 } old, new;
151
152                 optl = optlen(opt, i);
153
154                 if (priv->type != opt[i])
155                         continue;
156
157                 if (i + optl > tcphdr_len || priv->len + priv->offset > optl)
158                         return;
159
160                 if (!skb_make_writable(pkt->skb, pkt->xt.thoff + i + priv->len))
161                         return;
162
163                 tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff,
164                                               &tcphdr_len);
165                 if (!tcph)
166                         return;
167
168                 offset = i + priv->offset;
169
170                 switch (priv->len) {
171                 case 2:
172                         old.v16 = get_unaligned((u16 *)(opt + offset));
173                         new.v16 = (__force __be16)nft_reg_load16(
174                                 &regs->data[priv->sreg]);
175
176                         switch (priv->type) {
177                         case TCPOPT_MSS:
178                                 /* increase can cause connection to stall */
179                                 if (ntohs(old.v16) <= ntohs(new.v16))
180                                         return;
181                         break;
182                         }
183
184                         if (old.v16 == new.v16)
185                                 return;
186
187                         put_unaligned(new.v16, (u16*)(opt + offset));
188                         inet_proto_csum_replace2(&tcph->check, pkt->skb,
189                                                  old.v16, new.v16, false);
190                         break;
191                 case 4:
192                         new.v32 = regs->data[priv->sreg];
193                         old.v32 = get_unaligned((u32 *)(opt + offset));
194
195                         if (old.v32 == new.v32)
196                                 return;
197
198                         put_unaligned(new.v32, (u32*)(opt + offset));
199                         inet_proto_csum_replace4(&tcph->check, pkt->skb,
200                                                  old.v32, new.v32, false);
201                         break;
202                 default:
203                         WARN_ON_ONCE(1);
204                         break;
205                 }
206
207                 return;
208         }
209 }
210
211 static const struct nla_policy nft_exthdr_policy[NFTA_EXTHDR_MAX + 1] = {
212         [NFTA_EXTHDR_DREG]              = { .type = NLA_U32 },
213         [NFTA_EXTHDR_TYPE]              = { .type = NLA_U8 },
214         [NFTA_EXTHDR_OFFSET]            = { .type = NLA_U32 },
215         [NFTA_EXTHDR_LEN]               = { .type = NLA_U32 },
216         [NFTA_EXTHDR_FLAGS]             = { .type = NLA_U32 },
217         [NFTA_EXTHDR_OP]                = { .type = NLA_U32 },
218         [NFTA_EXTHDR_SREG]              = { .type = NLA_U32 },
219 };
220
221 static int nft_exthdr_init(const struct nft_ctx *ctx,
222                            const struct nft_expr *expr,
223                            const struct nlattr * const tb[])
224 {
225         struct nft_exthdr *priv = nft_expr_priv(expr);
226         u32 offset, len, flags = 0, op = NFT_EXTHDR_OP_IPV6;
227         int err;
228
229         if (!tb[NFTA_EXTHDR_DREG] ||
230             !tb[NFTA_EXTHDR_TYPE] ||
231             !tb[NFTA_EXTHDR_OFFSET] ||
232             !tb[NFTA_EXTHDR_LEN])
233                 return -EINVAL;
234
235         err = nft_parse_u32_check(tb[NFTA_EXTHDR_OFFSET], U8_MAX, &offset);
236         if (err < 0)
237                 return err;
238
239         err = nft_parse_u32_check(tb[NFTA_EXTHDR_LEN], U8_MAX, &len);
240         if (err < 0)
241                 return err;
242
243         if (tb[NFTA_EXTHDR_FLAGS]) {
244                 err = nft_parse_u32_check(tb[NFTA_EXTHDR_FLAGS], U8_MAX, &flags);
245                 if (err < 0)
246                         return err;
247
248                 if (flags & ~NFT_EXTHDR_F_PRESENT)
249                         return -EINVAL;
250         }
251
252         if (tb[NFTA_EXTHDR_OP]) {
253                 err = nft_parse_u32_check(tb[NFTA_EXTHDR_OP], U8_MAX, &op);
254                 if (err < 0)
255                         return err;
256         }
257
258         priv->type   = nla_get_u8(tb[NFTA_EXTHDR_TYPE]);
259         priv->offset = offset;
260         priv->len    = len;
261         priv->dreg   = nft_parse_register(tb[NFTA_EXTHDR_DREG]);
262         priv->flags  = flags;
263         priv->op     = op;
264
265         return nft_validate_register_store(ctx, priv->dreg, NULL,
266                                            NFT_DATA_VALUE, priv->len);
267 }
268
269 static int nft_exthdr_tcp_set_init(const struct nft_ctx *ctx,
270                                    const struct nft_expr *expr,
271                                    const struct nlattr * const tb[])
272 {
273         struct nft_exthdr *priv = nft_expr_priv(expr);
274         u32 offset, len, flags = 0, op = NFT_EXTHDR_OP_IPV6;
275         int err;
276
277         if (!tb[NFTA_EXTHDR_SREG] ||
278             !tb[NFTA_EXTHDR_TYPE] ||
279             !tb[NFTA_EXTHDR_OFFSET] ||
280             !tb[NFTA_EXTHDR_LEN])
281                 return -EINVAL;
282
283         if (tb[NFTA_EXTHDR_DREG] || tb[NFTA_EXTHDR_FLAGS])
284                 return -EINVAL;
285
286         err = nft_parse_u32_check(tb[NFTA_EXTHDR_OFFSET], U8_MAX, &offset);
287         if (err < 0)
288                 return err;
289
290         err = nft_parse_u32_check(tb[NFTA_EXTHDR_LEN], U8_MAX, &len);
291         if (err < 0)
292                 return err;
293
294         if (offset < 2)
295                 return -EOPNOTSUPP;
296
297         switch (len) {
298         case 2: break;
299         case 4: break;
300         default:
301                 return -EOPNOTSUPP;
302         }
303
304         err = nft_parse_u32_check(tb[NFTA_EXTHDR_OP], U8_MAX, &op);
305         if (err < 0)
306                 return err;
307
308         priv->type   = nla_get_u8(tb[NFTA_EXTHDR_TYPE]);
309         priv->offset = offset;
310         priv->len    = len;
311         priv->sreg   = nft_parse_register(tb[NFTA_EXTHDR_SREG]);
312         priv->flags  = flags;
313         priv->op     = op;
314
315         return nft_validate_register_load(priv->sreg, priv->len);
316 }
317
318 static int nft_exthdr_dump_common(struct sk_buff *skb, const struct nft_exthdr *priv)
319 {
320         if (nla_put_u8(skb, NFTA_EXTHDR_TYPE, priv->type))
321                 goto nla_put_failure;
322         if (nla_put_be32(skb, NFTA_EXTHDR_OFFSET, htonl(priv->offset)))
323                 goto nla_put_failure;
324         if (nla_put_be32(skb, NFTA_EXTHDR_LEN, htonl(priv->len)))
325                 goto nla_put_failure;
326         if (nla_put_be32(skb, NFTA_EXTHDR_FLAGS, htonl(priv->flags)))
327                 goto nla_put_failure;
328         if (nla_put_be32(skb, NFTA_EXTHDR_OP, htonl(priv->op)))
329                 goto nla_put_failure;
330         return 0;
331
332 nla_put_failure:
333         return -1;
334 }
335
336 static int nft_exthdr_dump(struct sk_buff *skb, const struct nft_expr *expr)
337 {
338         const struct nft_exthdr *priv = nft_expr_priv(expr);
339
340         if (nft_dump_register(skb, NFTA_EXTHDR_DREG, priv->dreg))
341                 return -1;
342
343         return nft_exthdr_dump_common(skb, priv);
344 }
345
346 static int nft_exthdr_dump_set(struct sk_buff *skb, const struct nft_expr *expr)
347 {
348         const struct nft_exthdr *priv = nft_expr_priv(expr);
349
350         if (nft_dump_register(skb, NFTA_EXTHDR_SREG, priv->sreg))
351                 return -1;
352
353         return nft_exthdr_dump_common(skb, priv);
354 }
355
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 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 };