All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 bpf-next 00/13] bpf: fmod_ret for each function that returns errors
@ 2026-11-13  5:08 Alexei Starovoitov
  2026-11-13  5:08 ` [PATCH v2 bpf-next 01/13] selftests/bpf: Don't depend on the width of a BTF id in verifier_live_stack Alexei Starovoitov
                   ` (12 more replies)
  0 siblings, 13 replies; 24+ messages in thread
From: Alexei Starovoitov @ 2026-11-13  5:08 UTC (permalink / raw)
  To: bpf; +Cc: daniel, andrii, eddyz87, memxor, a.s.protopopov

From: Alexei Starovoitov <ast@kernel.org>

In short: with CONFIG_BPF_FMODRET_SCAN=y the build tags the functions that
return errors, 12633 of them. A program with CAP_BPF and CAP_PERFMON can
attach to one, skip a call and return one of its errors. It reads the
arguments as untrusted pointers. No file of net, fs or mm changes. Test:
one skipped call per function, 4401 functions of net, fs, mm. With this set
1 function in 180 then damages the kernel. A deny list by name can take
those out.

1. What for?
BPF can keep user space away from a security bug in the rest of the kernel
until the fixed kernel runs. An fmod_ret program attaches to a function on
the way to the bug and fails the bad call:
  SEC("fmod_ret/vfs_fallocate")    return len > MAX_LEN ? -EFBIG : 0;
  err = vfs_fallocate(..) --> program
                               returns 0      --> call it, err = its result
                               returns -EFBIG --> skip it, err = -EFBIG
Such a program needs targets in net, fs and mm. Today it has 1000 syscall
entries, the security hooks and about 20 other functions. Of 417 fixes of
one year that add a failing check to a function, the fixed function is a
target: today 1 %, Anton's v3 (4) 17 %, this set 50 %.

2. What is a target?
  THE RULE  A function f() that returns errors is a target.
            A program returns 0, or an error that f() returns.
  EXCEPTION An error is not allowed if each path to its "return" calls a
            void function with a pointer: an argument of f() or
            &arg->member, not a loaded arg->ptr.
            If no error is left, f() is no target.
A caller can get each of these errors today, without a program. The rule
is simple on purpose. It does not aim at 100 % safety (5).
Why the exception? "spin_unlock(&arg->lock); return -EBUSY;". If f() fails,
the lock is free. A skip of f() leaves the lock held. So a program cannot
return this -EBUSY (5).
A program can only fail a call. Loss of function is the purpose: a program
that switches off a part of netfilter to keep a bug out of reach works as
designed. Who may load does not change: CAP_BPF and CAP_PERFMON, or a BPF
token. An attach does not taint the kernel, as today.

3. How are targets found?
  vmlinux, *.ko      machine code of GCC or clang, inlined and linked
  resolve_btfids     "the tool". It runs at the link, after pahole
    | 1 decode       LLVM's MC (machine code) layer gives the length, the
    |                branches, the written and the read registers
    | 2 analyse      data flow, backward, may, like liveness: the constants
    |                -4095..-1 that reach the return value register.
    |                Forward, may: is a path free of the exception's call?
    v 3 tag
  DECL_TAG 'bpf_fmodret_retval:-95,-29,-27,-26,-22,-21,-19,-9,-1'
           on FUNC 'vfs_fallocate'. This is "the list" of vfs_fallocate()
    v verifier       at load
  The program attaches. At each exit R0 is 0 or an error of the list.

4. What do I get, and what is the price?
v3 is "[PATCH v3 bpf-next 00/18] Auto-detect BPF fmodret targets" by Anton
Protopopov. This set is a rework of v3. The idea, resolve_btfids as the
place, the tag, the check, the tests, and that Kconfig asks llvm-config and
a host C++ compiler are his. So is the question behind the exception: what
has f() done before the error?

                     targets   kernel damage   the build reads
  allowed today         1290    not measured   -
  v3                    4457           0.2 %   LLVM IR, in a clang plugin
  this set             12633     0.6 - 0.7 %   machine code, GCC or clang
  targets: tags in one x86-64 clang kernel. GCC: 11985. arm64: 11543.
  A program can attach to 12096 of the 12633, and to 4454 of v3's 4457.
  kernel damage: the share of the functions in net, fs, mm where one
  injected error ends in a panic, a stall or a lockdep report (5). 0.7 %:
  a second run, and a blind run on new functions.
  allowed today: 957 syscall entries, 312 security_*(), 21 others. They are
  no safe baseline. security_*() is a match by name: 47 are internals of
  SELinux, 59 return void, 12 are hooks that BPF LSM refuses. The value
  that a program returns is not checked. This set does not change that.
v3's rule: a "clean path", one without side effects, leads from the entry
to "return -E..". A call, a store or a shared "goto err" label ends it.
The exception (patch 8) takes v3's clean path and narrows what ends it:
only a call of a void function on an argument.

5. Does the kernel survive?
An injection: one program attaches to one tagged function. At one call it
returns one error of the list, whoever calls. The control attaches and
always returns 0. The kernel is x86-64 GCC with inline KASAN, lockdep,
kmemleak and a wide config. The workload is 530 tests and scripts, mostly
selftests. It calls 8027 tagged functions, 4401 of them in net, fs, mm.
Each gets one injection:

                                         panic
                                         oops
                               kernel    BUG()   hang   lock
                  functions    damage    KASAN   stall  dep    WARN   leak
  net                  2716   25  0.9 %     20      4     1      14     34
  fs                   1307   18  1.4 %      5     13     0       4      2
  mm                    378    7  1.9 %      6      1     0       2      0
  net + fs + mm        4401   50  1.1 %     31     18     1      20     36
    error is dropped    290   27  9.3 %     16     10     1       1     16
    error stays        4111   23  0.6 %     15      8     0      19     20
      v3 has it too     903    2  0.2 %      2      0     0       3      1
      only this set    3208   21  0.7 %     13      8     0      16     19
  kernel damage = the three columns right of it. WARN stands apart.
  A leak is the lowest concern: it is not in the rate.
  The run has the lists without the exception. "error is dropped": the
  exception takes the injected error out of the list.

 1 With the exception about 1 function in 180 damages the kernel after one
   injection. It is not 1 in 2, not 1 in 10.
 2 The exception takes out 27 of the 50 cases, for 7 % of the functions.
   These 27 functions have no error left: no tag, no attach. The exception
   comes from this run. In a blind run on 632 new functions: 3 of 9 cases.
 3 v3's rule is safer per function, 0.2 % against 0.7 %. It has far fewer
   functions: 903 of the 4111.
 - Of all 8027 injections 48 % only give user space an error, 50 % nothing.
 - 354 of 355 runs of the control are clean. A second harness with its own
   program sees all 50 cases again.
 - Are the 50 cases bugs in the kernel? By the source of each case: at most
   2 are, on an error path. In the other 48 a real failure of f() at that
   call does not do that damage, or cannot happen.

  A skip of f() is not a failure of f(). 38 of the 50 cases come from that.
  The exception of (2) is made for them. It closes 26 of the 38. An
  example, fs/file.c. ksys_dup3() takes the lock and ends with
  "return do_dup2(..)":
      static int do_dup2(..) __releases(&files->file_lock)
      ..
      Ebusy:
              spin_unlock(&files->file_lock);
              return -EBUSY;
      do_dup2() fails        dup2() = -EBUSY, the lock is free
      do_dup2() is skipped   dup2() = -EBUSY, the lock stays held
  With this set do_dup2() has no tag: a program cannot attach to it. The
  tool sees that in the machine code. It needs no name and no __releases():
      mov  %rdi,%r13           r13 = files: a pointer argument
      lea  0xc0(%r13),%rdi     rdi = &files->file_lock: no load
      call _raw_spin_unlock    BTF: it returns void and takes a pointer
      mov  $0xfffffff0,%eax    -EBUSY: each path to it has this call
  A void function cannot fail: it has done its work when f() returns the
  error. The caller relies on that work, and a skip leaves it out. Most
  unlock, put, free and init functions return void. A function that returns
  a value can be the one that failed: it does not count.
  v3 has no do_dup2() either: the unlock ends its clean path.
  Functions with __releases() and no __acquires() that have a tag, without
  and with the exception: 7 and 0 in this kernel, 4 and 0 in the GCC kernel
  of (4), 3 and 1 with clang and on arm64.

  What stays? In four runs, all directories, 169 functions cause kernel
  damage in this kernel. The exception takes out 55. A deny list by name,
  not in this set, can take out the other 114, 55 of them in net, fs, mm.

6. What can a program read?
Nobody looked at what these functions get: NULL, an ERR_PTR(), a stale
pointer, an object that is not set up yet. So the arguments of each
fmod_ret program are PTR_UNTRUSTED, also on a target allowed today
(patch 11). Loads are BPF_PROBE_MEM and stores are refused, as before.
New: a helper that expects a kernel object refuses the pointer:
bpf_get_socket_cookie(sk). bpf_probe_read_kernel() takes it, as before.
The type of an untrusted pointer is a hint (patch 12). These load:
  tcp_sk(sk)->snd_una
  container_of(p, struct net_device, dev_list)->ifindex
Where the type does not describe the bytes, the load gives a number. It can
be tested, not followed.

7. What does it cost me?
  CONFIG_BPF_FMODRET_SCAN    option off   option on
  each Kconfig run           +3 ms        +3 ms: one run of llvm-config
  build host                 as today     C++17, llvm-config, headers and
                                          libraries of LLVM 17 or later
  the tool on vmlinux        0.31 s       0.9 s, after 4.3 s of pahole
  BTF of vmlinux             8.02 MB      8.25 MB = +2.9 %
  kernel memory              -            +0.33 MB: BTF and its index
 - Nobody gets the option without a question: no default, not in defconfig.
   allmodconfig and allyesconfig have no BTF, so they do not have it.
 - A GCC build needs LLVM too. It need not be the LLVM of the compiler.
 - LLVM's C++ API and opcode names change between releases. The build then
   stops and says why. So a new LLVM can stop the build of an old kernel
   with the option on.
 - x86-64 and arm64 only.
 - No file outside of BPF's entries in MAINTAINERS changes.

8. Why this rule? Why machine code? Why LLVM?
  the rule (* = taken)                 targets  against it
  v3: an error on a clean path            4457  17 % of the fixed f() (1)
  an error reaches a return              13549  0.9 - 1.3 % kernel damage
* the same with the exception            12633  0.6 - 0.7 % (5, patch 8)
  + the errors of callees               + 40 %  3 - 6 % are never returned
v3's promise holds in the machine code for each of its 5249 errors. The
exception drops none of them.

  who reads what               lines  against it
  v3: clang plugin, LLVM IR    +1913  no GCC, no LTO yet
  objtool, v3's rule           +1272  no arm64
* resolve_btfids at the link   +2124  LLVM on the host
  lines: the part that finds and tags.
The rule makes the targets, not the reader. A plugin with "an error reaches
a return" finds 14229. But a plugin serves one compiler, before the link.
Why LLVM? A decoder by hand is 300 - 800 lines per arch, for ever. The MC
layer takes 538 lines, and a table of 139 (x86-64) or 124 (arm64) lines:
what an opcode computes, by opcode name. The tool checks the table against
LLVM at each start. LLVM's C API gives only text, so it is C++ (patch 3).

v1 -> v2:
- New patches 11 - 13: the arguments are untrusted pointers (6).
- BPF CI: the test modules build with a kernel build directory that has no
  resolve_btfids (patch 10).
- arm64: no return value passes a brk, like ud2 on x86-64: the brk of BUG()
  has no next instruction. WARN() has the same brk: 58 functions that load
  the error before it lose the tag. x18 is caller saved. The Makefile names
  the interpreter of llvm-mc.sh (patch 3).
- x86-64: the stack pointer is never based on an argument (patch 8).
- -EAGAIN, -EMSGSIZE, -EINPROGRESS have the numbers of the kernel, not of
  the build host (patch 5).
- Applies on top of bpf-next.
v1: https://lore.kernel.org/bpf/20261112164610.1917256-1-alexei.starovoitov@gmail.com/

Signed-off-by: Alexei Starovoitov <ast@kernel.org>

Alexei Starovoitov (8):
  selftests/bpf: Don't depend on the width of a BTF id in
    verifier_live_stack
  resolve_btfids: Describe machine instructions with LLVM's MC layer
  resolve_btfids: Fold the constants of a select without a branch
  resolve_btfids: Say which registers an instruction reads
  resolve_btfids: Don't list an error that follows work on an argument
  bpf: Mark the arguments of fmod_ret progs as PTR_UNTRUSTED
  bpf: Treat the type of an untrusted pointer as a hint
  selftests/bpf: Test untrusted pointers in fmod_ret progs

Anton Protopopov (5):
  resolve_btfids: Read the code of an ELF file by address
  resolve_btfids: Find the definitions that reach the return value
  resolve_btfids: Tag the functions that return errors
  bpf: Allow fmod_ret on tagged functions and check the return value
  selftests/bpf: Test fmod_ret on tagged functions

 include/linux/bpf.h                           |   2 +
 include/linux/bpf_verifier.h                  |   2 +
 kernel/bpf/Kconfig                            |  19 +
 kernel/bpf/btf.c                              |   2 +
 kernel/bpf/verifier.c                         | 102 +++++
 scripts/Makefile.btf                          |   1 +
 tools/bpf/resolve_btfids/Build                |  13 +
 tools/bpf/resolve_btfids/Makefile             |  23 +-
 tools/bpf/resolve_btfids/fmodret.c            | 186 ++++++++
 tools/bpf/resolve_btfids/fmodret.h            |  12 +
 tools/bpf/resolve_btfids/fmodret_scan.c       | 259 +++++++++++
 tools/bpf/resolve_btfids/fmodret_scan.h       |  29 ++
 tools/bpf/resolve_btfids/image.c              | 222 +++++++++
 tools/bpf/resolve_btfids/image.h              |  62 +++
 tools/bpf/resolve_btfids/llvm-mc.sh           |  59 +++
 tools/bpf/resolve_btfids/main.c               |  11 +
 tools/bpf/resolve_btfids/text_flow.c          | 263 +++++++++++
 tools/bpf/resolve_btfids/text_flow.h          |  77 ++++
 tools/bpf/resolve_btfids/text_insn.h          |  88 ++++
 tools/bpf/resolve_btfids/text_mc.cpp          | 431 ++++++++++++++++++
 tools/bpf/resolve_btfids/text_mc.h            | 107 +++++
 tools/bpf/resolve_btfids/text_mc_arm64.c      | 124 +++++
 tools/bpf/resolve_btfids/text_mc_x86.c        | 139 ++++++
 tools/testing/selftests/bpf/config            |   1 +
 .../selftests/bpf/prog_tests/fmodret_retval.c | 152 ++++++
 .../selftests/bpf/prog_tests/verifier.c       |   2 +
 .../selftests/bpf/progs/fmodret_retval.c      | 168 +++++++
 .../selftests/bpf/progs/fmodret_retval_ext.c  |  18 +
 .../selftests/bpf/progs/map_kptr_fail.c       |   2 +-
 .../bpf/progs/verifier_btf_array_access.c     |   6 +-
 .../bpf/progs/verifier_btf_flex_array.c       |   2 +-
 .../bpf/progs/verifier_btf_untrusted.c        | 112 +++++
 .../selftests/bpf/progs/verifier_live_stack.c |   6 +-
 .../bpf/progs/verifier_store_release.c        |  23 +
 .../testing/selftests/bpf/test_kmods/Makefile |  16 +
 .../selftests/bpf/test_kmods/bpf_testmod.c    |   4 +-
 .../bpf/test_kmods/bpf_testmod_retval.c       | 131 ++++++
 .../testing/selftests/bpf/verifier/map_kptr.c |   3 +-
 38 files changed, 2866 insertions(+), 13 deletions(-)
 create mode 100644 tools/bpf/resolve_btfids/fmodret.c
 create mode 100644 tools/bpf/resolve_btfids/fmodret.h
 create mode 100644 tools/bpf/resolve_btfids/fmodret_scan.c
 create mode 100644 tools/bpf/resolve_btfids/fmodret_scan.h
 create mode 100644 tools/bpf/resolve_btfids/image.c
 create mode 100644 tools/bpf/resolve_btfids/image.h
 create mode 100755 tools/bpf/resolve_btfids/llvm-mc.sh
 create mode 100644 tools/bpf/resolve_btfids/text_flow.c
 create mode 100644 tools/bpf/resolve_btfids/text_flow.h
 create mode 100644 tools/bpf/resolve_btfids/text_insn.h
 create mode 100644 tools/bpf/resolve_btfids/text_mc.cpp
 create mode 100644 tools/bpf/resolve_btfids/text_mc.h
 create mode 100644 tools/bpf/resolve_btfids/text_mc_arm64.c
 create mode 100644 tools/bpf/resolve_btfids/text_mc_x86.c
 create mode 100644 tools/testing/selftests/bpf/prog_tests/fmodret_retval.c
 create mode 100644 tools/testing/selftests/bpf/progs/fmodret_retval.c
 create mode 100644 tools/testing/selftests/bpf/progs/fmodret_retval_ext.c
 create mode 100644 tools/testing/selftests/bpf/progs/verifier_btf_untrusted.c
 create mode 100644 tools/testing/selftests/bpf/test_kmods/bpf_testmod_retval.c


base-commit: 0021f17128de5dc0b19f1a523f448df2440d8088
-- 
2.55.0


^ permalink raw reply	[flat|nested] 24+ messages in thread

end of thread, other threads:[~2026-11-13 12:09 UTC | newest]

Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-13  5:08 [PATCH v2 bpf-next 00/13] bpf: fmod_ret for each function that returns errors Alexei Starovoitov
2026-11-13  5:08 ` [PATCH v2 bpf-next 01/13] selftests/bpf: Don't depend on the width of a BTF id in verifier_live_stack Alexei Starovoitov
2026-11-13 11:13   ` Anton Protopopov
2026-11-13  5:08 ` [PATCH v2 bpf-next 02/13] resolve_btfids: Read the code of an ELF file by address Alexei Starovoitov
2026-11-13  5:08 ` [PATCH v2 bpf-next 03/13] resolve_btfids: Describe machine instructions with LLVM's MC layer Alexei Starovoitov
2026-11-13 11:45   ` Anton Protopopov
2026-11-13  5:08 ` [PATCH v2 bpf-next 04/13] resolve_btfids: Find the definitions that reach the return value Alexei Starovoitov
2026-11-13  5:08 ` [PATCH v2 bpf-next 05/13] resolve_btfids: Tag the functions that return errors Alexei Starovoitov
2026-11-13  5:08 ` [PATCH v2 bpf-next 06/13] resolve_btfids: Fold the constants of a select without a branch Alexei Starovoitov
2026-11-13 11:59   ` Anton Protopopov
2026-11-13  5:08 ` [PATCH v2 bpf-next 07/13] resolve_btfids: Say which registers an instruction reads Alexei Starovoitov
2026-11-13 12:03   ` Anton Protopopov
2026-11-13  5:08 ` [PATCH v2 bpf-next 08/13] resolve_btfids: Don't list an error that follows work on an argument Alexei Starovoitov
2026-11-13  5:52   ` bot+bpf-ci
2026-11-13 12:14   ` Anton Protopopov
2026-11-13  5:08 ` [PATCH v2 bpf-next 09/13] bpf: Allow fmod_ret on tagged functions and check the return value Alexei Starovoitov
2026-11-13  5:52   ` bot+bpf-ci
2026-11-13 12:20   ` Anton Protopopov
2026-11-13  5:08 ` [PATCH v2 bpf-next 10/13] selftests/bpf: Test fmod_ret on tagged functions Alexei Starovoitov
2026-11-13  5:08 ` [PATCH v2 bpf-next 11/13] bpf: Mark the arguments of fmod_ret progs as PTR_UNTRUSTED Alexei Starovoitov
2026-11-13 11:09   ` Anton Protopopov
2026-11-13  5:09 ` [PATCH v2 bpf-next 12/13] bpf: Treat the type of an untrusted pointer as a hint Alexei Starovoitov
2026-11-13  5:52   ` bot+bpf-ci
2026-11-13  5:09 ` [PATCH v2 bpf-next 13/13] selftests/bpf: Test untrusted pointers in fmod_ret progs Alexei Starovoitov

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.