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