LCOV - code coverage report
Current view: top level - libbpfilter/cgen/matcher - cmp.c (source / functions) Coverage Total Hit
Test: coverage.lcov Lines: 88.6 % 123 109
Test Date: 2026-05-11 12:01:08 Functions: 100.0 % 7 7
Branches: 53.4 % 131 70

             Branch data     Line data    Source code
       1                 :             : /* SPDX-License-Identifier: GPL-2.0-only */
       2                 :             : /*
       3                 :             :  * Copyright (c) Meta Platforms, Inc. and affiliates.
       4                 :             :  */
       5                 :             : 
       6                 :             : #include "cgen/matcher/cmp.h"
       7                 :             : 
       8                 :             : #include <linux/bpf.h>
       9                 :             : #include <linux/bpf_common.h>
      10                 :             : 
      11                 :             : #include <assert.h>
      12                 :             : #include <errno.h>
      13                 :             : #include <stdbool.h>
      14                 :             : #include <string.h>
      15                 :             : 
      16                 :             : #include <bpfilter/logger.h>
      17                 :             : #include <bpfilter/matcher.h>
      18                 :             : 
      19                 :             : #include "cgen/jmp.h"
      20                 :             : #include "cgen/program.h"
      21                 :             : 
      22                 :        2431 : uint8_t bf_cmp_get_jmp_ins(const struct bf_matcher *matcher)
      23                 :             : {
      24                 :             :     bool continue_on_equal;
      25                 :             : 
      26                 :             :     assert(matcher);
      27                 :             : 
      28      [ +  -  + ]:        2431 :     switch (bf_matcher_get_op(matcher)) {
      29                 :             :     case BF_MATCHER_EQ:
      30                 :             :     case BF_MATCHER_ALL:
      31                 :             :         continue_on_equal = true;
      32                 :             :         break;
      33                 :         325 :     case BF_MATCHER_ANY:
      34                 :             :     case BF_MATCHER_IN:
      35                 :             :         continue_on_equal = false;
      36                 :         325 :         break;
      37                 :           0 :     default:
      38         [ #  # ]:           0 :         bf_abort("invalid matcher op to get jmp instruction %d",
      39                 :             :                  bf_matcher_get_op(matcher));
      40                 :             :     }
      41                 :             : 
      42                 :        2431 :     continue_on_equal ^= bf_matcher_get_negate(matcher);
      43                 :             : 
      44         [ +  + ]:        2431 :     return continue_on_equal ? BPF_JNE : BPF_JEQ;
      45                 :             : }
      46                 :             : 
      47                 :             : #define _BF_MASK_LAST_BYTE 15
      48                 :             : 
      49                 :             : static inline uint64_t _bf_read_u64(const void *ptr)
      50                 :             : {
      51                 :             :     uint64_t val;
      52                 :             : 
      53                 :         978 :     memcpy(&val, ptr, sizeof(val));
      54                 :             : 
      55                 :             :     return val;
      56                 :             : }
      57                 :             : 
      58                 :             : /**
      59                 :             :  * @brief Emit a 4-instruction sequence to build a 64-bit immediate from 8 bytes.
      60                 :             :  *
      61                 :             :  * Produces:
      62                 :             :  * @code
      63                 :             :  * MOV32_IMM(dst, high32) -> LSH(dst, 32) -> MOV32_IMM(scratch, low32) -> OR(dst, scratch)
      64                 :             :  * @endcode
      65                 :             :  *
      66                 :             :  * @param program Program to emit into. Can't be NULL.
      67                 :             :  * @param dst_reg Destination register for the 64-bit value.
      68                 :             :  * @param scratch_reg Scratch register (clobbered).
      69                 :             :  * @param data 64-bit value to load.
      70                 :             :  */
      71                 :         978 : static int _bf_cmp_build_imm64(struct bf_program *program, int dst_reg,
      72                 :             :                                int scratch_reg, uint64_t data)
      73                 :             : {
      74         [ +  - ]:         978 :     EMIT(program, BPF_MOV32_IMM(dst_reg, (uint32_t)(data >> 32)));
      75         [ +  - ]:         978 :     EMIT(program, BPF_ALU64_IMM(BPF_LSH, dst_reg, 32));
      76         [ +  - ]:         978 :     EMIT(program, BPF_MOV32_IMM(scratch_reg, (uint32_t)data));
      77                 :         978 :     EMIT(program, BPF_ALU64_REG(BPF_OR, dst_reg, scratch_reg));
      78                 :             : 
      79                 :             :     return 0;
      80                 :             : }
      81                 :             : 
      82                 :             : /**
      83                 :             :  * @brief Compute a network prefix mask.
      84                 :             :  *
      85                 :             :  * @param prefixlen Prefix length in bits.
      86                 :             :  * @param mask Output buffer. Can't be NULL.
      87                 :             :  * @param mask_len Size of mask buffer in bytes (4 or 16).
      88                 :             :  */
      89                 :         393 : static void _bf_prefix_to_mask(unsigned int prefixlen, uint8_t *mask,
      90                 :             :                                size_t mask_len)
      91                 :             : {
      92                 :             :     assert(mask);
      93                 :             : 
      94                 :         393 :     memset(mask, 0x00, mask_len);
      95                 :         393 :     memset(mask, 0xff, prefixlen / 8);
      96         [ +  + ]:         393 :     if (prefixlen % 8)
      97                 :          41 :         mask[prefixlen / 8] = (0xff << (8 - prefixlen % 8)) & 0xff;
      98                 :         393 : }
      99                 :             : 
     100                 :        1493 : int bf_cmp_value(struct bf_program *program, const struct bf_matcher *matcher,
     101                 :             :                  const void *ref, unsigned int size, int reg)
     102                 :             : {
     103                 :        1493 :     enum bf_matcher_op op = bf_matcher_get_op(matcher);
     104                 :             :     uint8_t jmp_op;
     105                 :             : 
     106                 :             :     assert(program);
     107                 :             :     assert(matcher);
     108                 :             :     assert(ref);
     109                 :             : 
     110         [ -  + ]:        1493 :     if (op != BF_MATCHER_EQ)
     111         [ #  # ]:           0 :         return bf_err_r(-EINVAL, "unsupported operator %d", op);
     112                 :             : 
     113                 :        1493 :     jmp_op = bf_cmp_get_jmp_ins(matcher);
     114                 :             : 
     115   [ +  +  -  +  :        1493 :     switch (size) {
                      - ]
     116                 :        1179 :     case 1:
     117                 :             :     case 2: {
     118                 :             :         /* Small values: compare directly via JMP_IMM.
     119                 :             :          * For size 1, ref is uint8_t; for size 2, ref is uint16_t.
     120                 :             :          * Both fit in a signed 32-bit immediate. */
     121                 :        1179 :         uint32_t val =
     122         [ +  + ]:        1179 :             (size == 1) ? *(const uint8_t *)ref : *(const uint16_t *)ref;
     123                 :             : 
     124         [ -  + ]:        1179 :         EMIT_FIXUP_JMP_NEXT_RULE(program, BPF_JMP_IMM(jmp_op, reg, val, 0));
     125                 :        1179 :         break;
     126                 :             :     }
     127                 :         177 :     case 4: {
     128                 :             :         /* 32-bit values: may exceed signed 32-bit immediate range, so
     129                 :             :          * use MOV32_IMM into R2 + JMP_REG. */
     130                 :         177 :         uint32_t val = *(const uint32_t *)ref;
     131                 :             : 
     132         [ +  - ]:         177 :         EMIT(program, BPF_MOV32_IMM(BPF_REG_2, val));
     133         [ -  + ]:         177 :         EMIT_FIXUP_JMP_NEXT_RULE(program,
     134                 :             :                                  BPF_JMP_REG(jmp_op, reg, BPF_REG_2, 0));
     135                 :         177 :         break;
     136                 :             :     }
     137                 :             :     case 8: {
     138                 :             :         /* 64-bit values: build immediate in R2 via `_bf_cmp_build_imm64`,
     139                 :             :          * then compare with `JMP_REG`. */
     140                 :             :         int r;
     141                 :             : 
     142                 :           0 :         r = _bf_cmp_build_imm64(program, BPF_REG_2, BPF_REG_3,
     143                 :             :                                 _bf_read_u64(ref));
     144         [ #  # ]:           0 :         if (r)
     145                 :             :             return r;
     146         [ #  # ]:           0 :         EMIT_FIXUP_JMP_NEXT_RULE(program,
     147                 :             :                                  BPF_JMP_REG(jmp_op, reg, BPF_REG_2, 0));
     148                 :           0 :         break;
     149                 :             :     }
     150                 :         137 :     case 16: {
     151                 :             :         /* 128-bit values: reg holds low 64 bits, reg+1 holds high 64 bits.
     152                 :             :          * Compare each half against the reference. */
     153                 :             :         const uint8_t *addr = ref;
     154                 :             :         int r;
     155                 :             : 
     156                 :         137 :         r = _bf_cmp_build_imm64(program, BPF_REG_3, BPF_REG_4,
     157                 :             :                                 _bf_read_u64(addr));
     158         [ +  - ]:         137 :         if (r)
     159                 :             :             return r;
     160                 :             : 
     161         [ +  + ]:         137 :         if (jmp_op == BPF_JNE) {
     162         [ -  + ]:          66 :             EMIT_FIXUP_JMP_NEXT_RULE(program,
     163                 :             :                                      BPF_JMP_REG(BPF_JNE, reg, BPF_REG_3, 0));
     164                 :             : 
     165                 :          66 :             r = _bf_cmp_build_imm64(program, BPF_REG_3, BPF_REG_4,
     166                 :             :                                     _bf_read_u64(addr + 8));
     167         [ +  - ]:          66 :             if (r)
     168                 :             :                 return r;
     169         [ -  + ]:          66 :             EMIT_FIXUP_JMP_NEXT_RULE(
     170                 :             :                 program, BPF_JMP_REG(BPF_JNE, reg + 1, BPF_REG_3, 0));
     171                 :             :         } else {
     172                 :             :             /* JEQ: the address must differ in at least one half.
     173                 :             :              * If the first half differs, the matcher matched — jump
     174                 :             :              * past the second half check and the unconditional
     175                 :             :              * jump-to-next-rule. If the first half matches, check the
     176                 :             :              * second half: if it also matches, the full address is
     177                 :             :              * equal, so the matcher fails — jump to next rule. */
     178                 :          71 :             _clean_bf_jmpctx_ struct bf_jmpctx j0 = bf_jmpctx_default();
     179                 :          71 :             _clean_bf_jmpctx_ struct bf_jmpctx j1 = bf_jmpctx_default();
     180                 :             : 
     181                 :          71 :             j0 =
     182         [ +  - ]:          71 :                 bf_jmpctx_get(program, BPF_JMP_REG(BPF_JNE, reg, BPF_REG_3, 0));
     183                 :             : 
     184                 :          71 :             r = _bf_cmp_build_imm64(program, BPF_REG_3, BPF_REG_4,
     185                 :             :                                     _bf_read_u64(addr + 8));
     186         [ +  - ]:          71 :             if (r)
     187                 :             :                 return r;
     188         [ +  - ]:          71 :             j1 = bf_jmpctx_get(program,
     189                 :             :                                BPF_JMP_REG(BPF_JNE, reg + 1, BPF_REG_3, 0));
     190                 :             : 
     191         [ -  + ]:          71 :             EMIT_FIXUP_JMP_NEXT_RULE(program, BPF_JMP_A(0));
     192                 :             :         }
     193                 :             :         break;
     194                 :             :     }
     195                 :           0 :     default:
     196         [ #  # ]:           0 :         return bf_err_r(-EINVAL, "unsupported comparison size %u", size);
     197                 :             :     }
     198                 :             : 
     199                 :             :     return 0;
     200                 :             : }
     201                 :             : 
     202                 :         393 : int bf_cmp_masked_value(struct bf_program *program,
     203                 :             :                         const struct bf_matcher *matcher, const void *ref,
     204                 :             :                         unsigned int prefixlen, unsigned int size, int reg)
     205                 :             : {
     206                 :         393 :     enum bf_matcher_op op = bf_matcher_get_op(matcher);
     207                 :             :     uint8_t jmp_op;
     208                 :             : 
     209                 :             :     assert(program);
     210                 :             :     assert(matcher);
     211                 :             :     assert(ref);
     212                 :             : 
     213         [ -  + ]:         393 :     if (op != BF_MATCHER_EQ)
     214         [ #  # ]:           0 :         return bf_err_r(-EINVAL, "unsupported operator %d", op);
     215                 :             : 
     216                 :         393 :     jmp_op = bf_cmp_get_jmp_ins(matcher);
     217                 :             : 
     218      [ +  +  - ]:         393 :     switch (size) {
     219                 :         176 :     case 4: {
     220                 :             :         uint32_t mask;
     221                 :             :         const uint32_t *addr = ref;
     222                 :             : 
     223                 :         176 :         _bf_prefix_to_mask(prefixlen, (uint8_t *)&mask, 4);
     224                 :             : 
     225         [ +  - ]:         176 :         EMIT(program, BPF_MOV32_IMM(BPF_REG_2, *addr));
     226                 :             : 
     227         [ +  + ]:         176 :         if (mask != ~0U) {
     228         [ +  - ]:         136 :             EMIT(program, BPF_MOV32_IMM(BPF_REG_3, mask));
     229         [ +  - ]:         136 :             EMIT(program, BPF_ALU32_REG(BPF_AND, reg, BPF_REG_3));
     230         [ +  - ]:         136 :             EMIT(program, BPF_ALU32_REG(BPF_AND, BPF_REG_2, BPF_REG_3));
     231                 :             :         }
     232                 :             : 
     233         [ -  + ]:         176 :         EMIT_FIXUP_JMP_NEXT_RULE(program,
     234                 :             :                                  BPF_JMP_REG(jmp_op, reg, BPF_REG_2, 0));
     235                 :         176 :         break;
     236                 :             :     }
     237                 :         217 :     case 16: {
     238                 :             :         uint8_t mask[16];
     239                 :             :         uint8_t masked_lo[8], masked_hi[8];
     240                 :             :         const uint8_t *addr = ref;
     241                 :             :         int r;
     242                 :             : 
     243                 :         217 :         _bf_prefix_to_mask(prefixlen, mask, 16);
     244                 :             : 
     245                 :             :         // Apply mask to loaded reg/reg+1 if not a full /128
     246         [ +  + ]:         217 :         if (mask[_BF_MASK_LAST_BYTE] != (uint8_t)~0) {
     247                 :         135 :             r = _bf_cmp_build_imm64(program, BPF_REG_3, BPF_REG_4,
     248                 :             :                                     _bf_read_u64(mask));
     249         [ +  - ]:         135 :             if (r)
     250                 :           0 :                 return r;
     251         [ +  - ]:         135 :             EMIT(program, BPF_ALU64_REG(BPF_AND, reg, BPF_REG_3));
     252                 :             : 
     253                 :         135 :             r = _bf_cmp_build_imm64(program, BPF_REG_3, BPF_REG_4,
     254                 :             :                                     _bf_read_u64(mask + 8));
     255         [ +  - ]:         135 :             if (r)
     256                 :             :                 return r;
     257         [ +  - ]:         135 :             EMIT(program, BPF_ALU64_REG(BPF_AND, reg + 1, BPF_REG_3));
     258                 :             :         }
     259                 :             : 
     260         [ +  + ]:        1953 :         for (int i = 0; i < 8; i++)
     261                 :        1736 :             masked_lo[i] = addr[i] & mask[i];
     262         [ +  + ]:        1953 :         for (int i = 0; i < 8; i++)
     263                 :        1736 :             masked_hi[i] = addr[i + 8] & mask[i + 8];
     264                 :             : 
     265                 :         217 :         r = _bf_cmp_build_imm64(program, BPF_REG_3, BPF_REG_4,
     266                 :             :                                 _bf_read_u64(masked_lo));
     267         [ +  - ]:         217 :         if (r)
     268                 :             :             return r;
     269                 :             : 
     270         [ +  + ]:         217 :         if (jmp_op == BPF_JNE) {
     271         [ -  + ]:         116 :             EMIT_FIXUP_JMP_NEXT_RULE(program,
     272                 :             :                                      BPF_JMP_REG(BPF_JNE, reg, BPF_REG_3, 0));
     273                 :             : 
     274                 :         116 :             r = _bf_cmp_build_imm64(program, BPF_REG_3, BPF_REG_4,
     275                 :             :                                     _bf_read_u64(masked_hi));
     276         [ +  - ]:         116 :             if (r)
     277                 :             :                 return r;
     278         [ -  + ]:         116 :             EMIT_FIXUP_JMP_NEXT_RULE(
     279                 :             :                 program, BPF_JMP_REG(BPF_JNE, reg + 1, BPF_REG_3, 0));
     280                 :             :         } else {
     281                 :         101 :             _clean_bf_jmpctx_ struct bf_jmpctx j0 = bf_jmpctx_default();
     282                 :         101 :             _clean_bf_jmpctx_ struct bf_jmpctx j1 = bf_jmpctx_default();
     283                 :             : 
     284                 :         101 :             j0 =
     285         [ +  - ]:         101 :                 bf_jmpctx_get(program, BPF_JMP_REG(BPF_JNE, reg, BPF_REG_3, 0));
     286                 :             : 
     287                 :         101 :             r = _bf_cmp_build_imm64(program, BPF_REG_3, BPF_REG_4,
     288                 :             :                                     _bf_read_u64(masked_hi));
     289         [ +  - ]:         101 :             if (r)
     290                 :             :                 return r;
     291         [ +  - ]:         101 :             j1 = bf_jmpctx_get(program,
     292                 :             :                                BPF_JMP_REG(BPF_JNE, reg + 1, BPF_REG_3, 0));
     293                 :             : 
     294         [ -  + ]:         101 :             EMIT_FIXUP_JMP_NEXT_RULE(program, BPF_JMP_A(0));
     295                 :             :         }
     296                 :         217 :         break;
     297                 :             :     }
     298                 :           0 :     default:
     299         [ #  # ]:           0 :         return bf_err_r(-EINVAL, "unsupported masked comparison size %u", size);
     300                 :             :     }
     301                 :             : 
     302                 :             :     return 0;
     303                 :             : }
     304                 :             : 
     305                 :         222 : int bf_cmp_range(struct bf_program *program, const struct bf_matcher *matcher,
     306                 :             :                  uint32_t min, uint32_t max, int reg)
     307                 :             : {
     308                 :             :     assert(program);
     309                 :             :     assert(matcher);
     310                 :             : 
     311         [ +  + ]:         222 :     if (bf_matcher_get_negate(matcher)) {
     312                 :          10 :         _clean_bf_jmpctx_ struct bf_jmpctx j0 = bf_jmpctx_default();
     313                 :          10 :         _clean_bf_jmpctx_ struct bf_jmpctx j1 = bf_jmpctx_default();
     314                 :             : 
     315         [ +  - ]:          10 :         j0 = bf_jmpctx_get(program, BPF_JMP32_IMM(BPF_JLT, reg, min, 0));
     316         [ +  - ]:          10 :         j1 = bf_jmpctx_get(program, BPF_JMP32_IMM(BPF_JGT, reg, max, 0));
     317         [ -  + ]:          10 :         EMIT_FIXUP_JMP_NEXT_RULE(program, BPF_JMP_A(0));
     318                 :             : 
     319                 :          10 :         return 0;
     320                 :             :     }
     321                 :             : 
     322         [ -  + ]:         212 :     EMIT_FIXUP_JMP_NEXT_RULE(program, BPF_JMP32_IMM(BPF_JLT, reg, min, 0));
     323         [ -  + ]:         212 :     EMIT_FIXUP_JMP_NEXT_RULE(program, BPF_JMP32_IMM(BPF_JGT, reg, max, 0));
     324                 :             : 
     325                 :         212 :     return 0;
     326                 :             : }
     327                 :             : 
     328                 :         100 : int bf_cmp_bitfield(struct bf_program *program,
     329                 :             :                     const struct bf_matcher *matcher, uint32_t flags, int reg)
     330                 :             : {
     331                 :         100 :     enum bf_matcher_op op = bf_matcher_get_op(matcher);
     332                 :             : 
     333                 :             :     assert(program);
     334                 :             :     assert(matcher);
     335                 :             : 
     336         [ -  + ]:         100 :     if (op != BF_MATCHER_ANY && op != BF_MATCHER_ALL)
     337         [ #  # ]:           0 :         return bf_err_r(-EINVAL, "unsupported operator %d", op);
     338                 :             : 
     339         [ +  - ]:         100 :     EMIT(program, BPF_ALU32_IMM(BPF_AND, reg, flags));
     340   [ +  +  -  + ]:         150 :     EMIT_FIXUP_JMP_NEXT_RULE(
     341                 :             :         program, BPF_JMP32_IMM(bf_cmp_get_jmp_ins(matcher), reg,
     342                 :             :                                op == BF_MATCHER_ANY ? 0 : flags, 0));
     343                 :             : 
     344                 :         100 :     return 0;
     345                 :             : }
        

Generated by: LCOV version 2.0-1