BPF List
 help / color / mirror / Atom feed
From: Anton Protopopov <a.s.protopopov@gmail.com>
To: Alexei Starovoitov <alexei.starovoitov@gmail.com>
Cc: bpf <bpf@vger.kernel.org>, Alexei Starovoitov <ast@kernel.org>,
	Andrii Nakryiko <andrii@kernel.org>,
	Anton Protopopov <aspsk@isovalent.com>,
	Daniel Borkmann <daniel@iogearbox.net>,
	Eduard Zingerman <eddyz87@gmail.com>,
	Quentin Monnet <qmo@kernel.org>,
	Yonghong Song <yonghong.song@linux.dev>
Subject: Re: [PATCH v11 bpf-next 08/12] bpf, x86: add support for indirect jumps
Date: Thu, 6 Nov 2025 10:03:53 +0000	[thread overview]
Message-ID: <aQxyiWANixOfg+Eg@mail.gmail.com> (raw)
In-Reply-To: <CAADnVQK3piReoo1ja=9hgz7aJ60Y_Jjur_JMOaYV8-Mn_VyE4A@mail.gmail.com>

On 25/11/05 02:42PM, Alexei Starovoitov wrote:
> On Wed, Nov 5, 2025 at 12:58 AM Anton Protopopov
> <a.s.protopopov@gmail.com> wrote:
> >
> > Add support for a new instruction
> >
> >     BPF_JMP|BPF_X|BPF_JA, SRC=0, DST=Rx, off=0, imm=0
> >
> > which does an indirect jump to a location stored in Rx.  The register
> > Rx should have type PTR_TO_INSN. This new type assures that the Rx
> > register contains a value (or a range of values) loaded from a
> > correct jump table – map of type instruction array.
> >
> > For example, for a C switch LLVM will generate the following code:
> >
> >     0:   r3 = r1                    # "switch (r3)"
> >     1:   if r3 > 0x13 goto +0x666   # check r3 boundaries
> >     2:   r3 <<= 0x3                 # adjust to an index in array of addresses
> >     3:   r1 = 0xbeef ll             # r1 is PTR_TO_MAP_VALUE, r1->map_ptr=M
> >     5:   r1 += r3                   # r1 inherits boundaries from r3
> >     6:   r1 = *(u64 *)(r1 + 0x0)    # r1 now has type INSN_TO_PTR
> >     7:   gotox r1                   # jit will generate proper code
> >
> > Here the gotox instruction corresponds to one particular map. This is
> > possible however to have a gotox instruction which can be loaded from
> > different maps, e.g.
> >
> >     0:   r1 &= 0x1
> >     1:   r2 <<= 0x3
> >     2:   r3 = 0x0 ll                # load from map M_1
> >     4:   r3 += r2
> >     5:   if r1 == 0x0 goto +0x4
> >     6:   r1 <<= 0x3
> >     7:   r3 = 0x0 ll                # load from map M_2
> >     9:   r3 += r1
> >     A:   r1 = *(u64 *)(r3 + 0x0)
> >     B:   gotox r1                   # jump to target loaded from M_1 or M_2
> >
> > During check_cfg stage the verifier will collect all the maps which
> > point to inside the subprog being verified. When building the config,
> > the high 16 bytes of the insn_state are used, so this patch
> > (theoretically) supports jump tables of up to 2^16 slots.
> >
> > During the later stage, in check_indirect_jump, it is checked that
> > the register Rx was loaded from a particular instruction array.
> >
> > Signed-off-by: Anton Protopopov <a.s.protopopov@gmail.com>
> > Acked-by: Eduard Zingerman <eddyz87@gmail.com>
> > ---
> >  arch/x86/net/bpf_jit_comp.c  |   3 +
> >  include/linux/bpf.h          |   1 +
> >  include/linux/bpf_verifier.h |   9 +
> >  kernel/bpf/bpf_insn_array.c  |  15 ++
> >  kernel/bpf/core.c            |   1 +
> >  kernel/bpf/liveness.c        |   3 +
> >  kernel/bpf/log.c             |   1 +
> >  kernel/bpf/verifier.c        | 373 ++++++++++++++++++++++++++++++++++-
> >  8 files changed, 400 insertions(+), 6 deletions(-)
> >
> > diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
> > index bbd2b03d2b74..36a0d4db9f68 100644
> > --- a/arch/x86/net/bpf_jit_comp.c
> > +++ b/arch/x86/net/bpf_jit_comp.c
> > @@ -2628,6 +2628,9 @@ st:                       if (is_imm8(insn->off))
> >
> >                         break;
> >
> > +               case BPF_JMP | BPF_JA | BPF_X:
> > +                       emit_indirect_jump(&prog, insn->dst_reg, image + addrs[i - 1]);
> > +                       break;
> >                 case BPF_JMP | BPF_JA:
> >                 case BPF_JMP32 | BPF_JA:
> >                         if (BPF_CLASS(insn->code) == BPF_JMP) {
> > diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> > index 9d41a6affcef..09d5dc541d1c 100644
> > --- a/include/linux/bpf.h
> > +++ b/include/linux/bpf.h
> > @@ -1001,6 +1001,7 @@ enum bpf_reg_type {
> >         PTR_TO_ARENA,
> >         PTR_TO_BUF,              /* reg points to a read/write buffer */
> >         PTR_TO_FUNC,             /* reg points to a bpf program function */
> > +       PTR_TO_INSN,             /* reg points to a bpf program instruction */
> >         CONST_PTR_TO_DYNPTR,     /* reg points to a const struct bpf_dynptr */
> >         __BPF_REG_TYPE_MAX,
> >
> > diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
> > index 6b820d8d77af..5441341f1ab9 100644
> > --- a/include/linux/bpf_verifier.h
> > +++ b/include/linux/bpf_verifier.h
> > @@ -527,6 +527,7 @@ struct bpf_insn_aux_data {
> >                 struct {
> >                         u32 map_index;          /* index into used_maps[] */
> >                         u32 map_off;            /* offset from value base address */
> > +                       struct bpf_iarray *jt;  /* jump table for gotox instruction */
> >                 };
> >                 struct {
> >                         enum bpf_reg_type reg_type;     /* type of pseudo_btf_id */
> > @@ -840,6 +841,7 @@ struct bpf_verifier_env {
> >         struct bpf_scc_info **scc_info;
> >         u32 scc_cnt;
> >         struct bpf_iarray *succ;
> > +       struct bpf_iarray *gotox_tmp_buf;
> >  };
> >
> >  static inline struct bpf_func_info_aux *subprog_aux(struct bpf_verifier_env *env, int subprog)
> > @@ -1050,6 +1052,13 @@ static inline bool bpf_stack_narrow_access_ok(int off, int fill_size, int spill_
> >         return !(off % BPF_REG_SIZE);
> >  }
> >
> > +static inline bool insn_is_gotox(struct bpf_insn *insn)
> > +{
> > +       return BPF_CLASS(insn->code) == BPF_JMP &&
> > +              BPF_OP(insn->code) == BPF_JA &&
> > +              BPF_SRC(insn->code) == BPF_X;
> > +}
> > +
> >  const char *reg_type_str(struct bpf_verifier_env *env, enum bpf_reg_type type);
> >  const char *dynptr_type_str(enum bpf_dynptr_type type);
> >  const char *iter_type_str(const struct btf *btf, u32 btf_id);
> > diff --git a/kernel/bpf/bpf_insn_array.c b/kernel/bpf/bpf_insn_array.c
> > index 2053fda377bb..61ce52882632 100644
> > --- a/kernel/bpf/bpf_insn_array.c
> > +++ b/kernel/bpf/bpf_insn_array.c
> > @@ -114,6 +114,20 @@ static u64 insn_array_mem_usage(const struct bpf_map *map)
> >         return insn_array_alloc_size(map->max_entries);
> >  }
> >
> > +static int insn_array_map_direct_value_addr(const struct bpf_map *map, u64 *imm, u32 off)
> > +{
> > +       struct bpf_insn_array *insn_array = cast_insn_array(map);
> > +
> > +       if ((off % sizeof(long)) != 0 ||
> > +           (off / sizeof(long)) >= map->max_entries)
> > +               return -EINVAL;
> > +
> > +       /* from BPF's point of view, this map is a jump table */
> > +       *imm = (unsigned long)insn_array->ips + off;
> > +
> > +       return 0;
> > +}
> > +
> >  BTF_ID_LIST_SINGLE(insn_array_btf_ids, struct, bpf_insn_array)
> >
> >  const struct bpf_map_ops insn_array_map_ops = {
> > @@ -126,6 +140,7 @@ const struct bpf_map_ops insn_array_map_ops = {
> >         .map_delete_elem = insn_array_delete_elem,
> >         .map_check_btf = insn_array_check_btf,
> >         .map_mem_usage = insn_array_mem_usage,
> > +       .map_direct_value_addr = insn_array_map_direct_value_addr,
> >         .map_btf_id = &insn_array_btf_ids[0],
> >  };
> >
> > diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
> > index 4b62a03d6df5..ef4448f18aad 100644
> > --- a/kernel/bpf/core.c
> > +++ b/kernel/bpf/core.c
> > @@ -1708,6 +1708,7 @@ bool bpf_opcode_in_insntable(u8 code)
> >                 [BPF_LD | BPF_IND | BPF_B] = true,
> >                 [BPF_LD | BPF_IND | BPF_H] = true,
> >                 [BPF_LD | BPF_IND | BPF_W] = true,
> > +               [BPF_JMP | BPF_JA | BPF_X] = true,
> >                 [BPF_JMP | BPF_JCOND] = true,
> >         };
> >  #undef BPF_INSN_3_TBL
> > diff --git a/kernel/bpf/liveness.c b/kernel/bpf/liveness.c
> > index bffb495bc933..a7240013fd9d 100644
> > --- a/kernel/bpf/liveness.c
> > +++ b/kernel/bpf/liveness.c
> > @@ -485,6 +485,9 @@ bpf_insn_successors(struct bpf_verifier_env *env, u32 idx)
> >         struct bpf_iarray *succ;
> >         int insn_sz;
> >
> > +       if (unlikely(insn_is_gotox(insn)))
> > +               return env->insn_aux_data[idx].jt;
> > +
> >         /* pre-allocated array of size up to 2; reset cnt, as it may have been used already */
> >         succ = env->succ;
> >         succ->cnt = 0;
> > diff --git a/kernel/bpf/log.c b/kernel/bpf/log.c
> > index 70221aafc35c..a0c3b35de2ce 100644
> > --- a/kernel/bpf/log.c
> > +++ b/kernel/bpf/log.c
> > @@ -461,6 +461,7 @@ const char *reg_type_str(struct bpf_verifier_env *env, enum bpf_reg_type type)
> >                 [PTR_TO_ARENA]          = "arena",
> >                 [PTR_TO_BUF]            = "buf",
> >                 [PTR_TO_FUNC]           = "func",
> > +               [PTR_TO_INSN]           = "insn",
> >                 [PTR_TO_MAP_KEY]        = "map_key",
> >                 [CONST_PTR_TO_DYNPTR]   = "dynptr_ptr",
> >         };
> > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> > index 781669f649f2..1268fa075d4c 100644
> > --- a/kernel/bpf/verifier.c
> > +++ b/kernel/bpf/verifier.c
> > @@ -6006,6 +6006,18 @@ static int check_map_kptr_access(struct bpf_verifier_env *env, u32 regno,
> >         return 0;
> >  }
> >
> > +/*
> > + * Return the size of the memory region accessible from a pointer to map value.
> > + * For INSN_ARRAY maps whole bpf_insn_array->ips array is accessible.
> > + */
> > +static u32 map_mem_size(const struct bpf_map *map)
> > +{
> > +       if (map->map_type == BPF_MAP_TYPE_INSN_ARRAY)
> > +               return map->max_entries * sizeof(long);
> > +
> > +       return map->value_size;
> > +}
> > +
> >  /* check read/write into a map element with possible variable offset */
> >  static int check_map_access(struct bpf_verifier_env *env, u32 regno,
> >                             int off, int size, bool zero_size_allowed,
> > @@ -6015,11 +6027,11 @@ static int check_map_access(struct bpf_verifier_env *env, u32 regno,
> >         struct bpf_func_state *state = vstate->frame[vstate->curframe];
> >         struct bpf_reg_state *reg = &state->regs[regno];
> >         struct bpf_map *map = reg->map_ptr;
> > +       u32 mem_size = map_mem_size(map);
> >         struct btf_record *rec;
> >         int err, i;
> >
> > -       err = check_mem_region_access(env, regno, off, size, map->value_size,
> > -                                     zero_size_allowed);
> > +       err = check_mem_region_access(env, regno, off, size, mem_size, zero_size_allowed);
> >         if (err)
> >                 return err;
> >
> > @@ -7481,6 +7493,8 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
> >  {
> >         struct bpf_reg_state *regs = cur_regs(env);
> >         struct bpf_reg_state *reg = regs + regno;
> > +       bool insn_array = reg->type == PTR_TO_MAP_VALUE &&
> > +                         reg->map_ptr->map_type == BPF_MAP_TYPE_INSN_ARRAY;
> >         int size, err = 0;
> >
> >         size = bpf_size_to_bytes(bpf_size);
> > @@ -7488,7 +7502,7 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
> >                 return size;
> >
> >         /* alignment checks will add in reg->off themselves */
> > -       err = check_ptr_alignment(env, reg, off, size, strict_alignment_once);
> > +       err = check_ptr_alignment(env, reg, off, size, strict_alignment_once || insn_array);
> >         if (err)
> >                 return err;
> >
> > @@ -7515,6 +7529,11 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
> >                         verbose(env, "R%d leaks addr into map\n", value_regno);
> >                         return -EACCES;
> >                 }
> > +               if (t == BPF_WRITE && insn_array) {
> > +                       verbose(env, "writes into insn_array not allowed\n");
> > +                       return -EACCES;
> > +               }
> > +
> >                 err = check_map_access_type(env, regno, off, size, t);
> 
> This is a bit ugly.
> Just set map->map_flags |= BPF_F_RDONLY_PROG;
> at map creation time or check that it's created this way from libbpf.
> And remove the above check.
> check_map_access_type() will do it generically.
> 
> and with that reg->map_ptr->map_type == BPF_MAP_TYPE_INSN_ARRAY ->strict
> can move into check_ptr_alignment().
> Abusing strict_alignment_once for this is wrong.
> 
> Both can be a follow up.

Ok, thanks, will follow up on both issues.

  reply	other threads:[~2025-11-06  9:57 UTC|newest]

Thread overview: 34+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-11-05  9:03 [PATCH v11 bpf-next 00/12] BPF indirect jumps Anton Protopopov
2025-11-05  9:03 ` [PATCH v11 bpf-next 01/12] bpf, x86: add new map type: instructions array Anton Protopopov
2025-11-06  2:03   ` Alexei Starovoitov
2025-11-06 10:01     ` Anton Protopopov
2025-11-06 17:08       ` Alexei Starovoitov
2025-11-16 12:58         ` Anton Protopopov
2025-11-22  2:40           ` Alexei Starovoitov
2025-11-24 15:17             ` Anton Protopopov
2025-11-05  9:04 ` [PATCH v11 bpf-next 02/12] bpftool: Recognize insn_array map type Anton Protopopov
2025-11-05  9:21   ` bot+bpf-ci
2025-11-05  9:29     ` Anton Protopopov
2025-11-05  9:04 ` [PATCH v11 bpf-next 03/12] libbpf: " Anton Protopopov
2025-11-05  9:04 ` [PATCH v11 bpf-next 04/12] selftests/bpf: add selftests for new insn_array map Anton Protopopov
2025-11-05  9:28   ` bot+bpf-ci
2025-11-05  9:52     ` Anton Protopopov
2025-11-05  9:04 ` [PATCH v11 bpf-next 05/12] bpf: support instructions arrays with constants blinding Anton Protopopov
2025-11-05  9:04 ` [PATCH v11 bpf-next 06/12] selftests/bpf: test instructions arrays with blinding Anton Protopopov
2025-11-05  9:04 ` [PATCH v11 bpf-next 07/12] bpf, x86: allow indirect jumps to r8...r15 Anton Protopopov
2025-11-05  9:04 ` [PATCH v11 bpf-next 08/12] bpf, x86: add support for indirect jumps Anton Protopopov
2025-11-05 11:23   ` Anton Protopopov
2025-11-05 17:45     ` Ihor Solodrai
2025-11-05 20:16       ` Anton Protopopov
2025-11-05 22:42   ` Alexei Starovoitov
2025-11-06 10:03     ` Anton Protopopov [this message]
2025-11-05  9:04 ` [PATCH v11 bpf-next 09/12] bpf: disasm: add support for BPF_JMP|BPF_JA|BPF_X Anton Protopopov
2025-11-05  9:04 ` [PATCH v11 bpf-next 10/12] libbpf: support llvm-generated indirect jumps Anton Protopopov
2025-11-05  9:04 ` [PATCH v11 bpf-next 11/12] selftests/bpf: add new verifier_gotox test Anton Protopopov
2025-11-05  9:04 ` [PATCH v11 bpf-next 12/12] selftests/bpf: add C-level selftests for indirect jumps Anton Protopopov
2025-11-05  9:28   ` bot+bpf-ci
2025-11-05  9:37     ` Anton Protopopov
2025-11-05 20:51 ` [PATCH v11 bpf-next 00/12] BPF " Eduard Zingerman
2025-11-05 21:54   ` Anton Protopopov
2025-11-06  1:56     ` Alexei Starovoitov
2025-11-06  2:00 ` patchwork-bot+netdevbpf

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=aQxyiWANixOfg+Eg@mail.gmail.com \
    --to=a.s.protopopov@gmail.com \
    --cc=alexei.starovoitov@gmail.com \
    --cc=andrii@kernel.org \
    --cc=aspsk@isovalent.com \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=eddyz87@gmail.com \
    --cc=qmo@kernel.org \
    --cc=yonghong.song@linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox