BPF List
 help / color / mirror / Atom feed
* [PATCH bpf-next v2 00/17] Redesign Verification Errors
@ 2026-06-19 20:59 Kumar Kartikeya Dwivedi
  2026-06-19 20:59 ` [PATCH bpf-next v2 01/17] bpf: Add verifier diagnostics report helpers Kumar Kartikeya Dwivedi
                   ` (16 more replies)
  0 siblings, 17 replies; 34+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-06-19 20:59 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team

TL;DR: This set reworks verifier error messages to include source and
instruction annotations, together with more causal context, making
failures easier to understand and more actionable when debugging and
repairing BPF programs.

Changelog:
----------
v1 -> v2
v1: https://lore.kernel.org/bpf/20260605063412.974640-1-memxor@gmail.com

 * Reworked diagnostic history from per-verifier-state log to active
   path log with positions saved and reset when verifier search
   backtracks. (Eduard)
 * Moved reusable diagnostic formatting storage into struct bpf_diag
   under struct bpf_verifier_env, and removed large per-report scratch
   buffers from verifier stack frames. (Eduard)
 * Added stack-slot events so diagnostics follow ordinary stack
   spill/fill value flow and invalidations in register-scoped histories. (Eduard)
 * Reused existing source and BTF formatting helpers for diagnostics,
   including bpf_get_linfo_file_line() and btf_type_snprintf_show_name(). (Eduard)
 * Fixed diagnostic edge cases around signed offset text,
   BPF_MAX_VAR_OFF reporting, negative-offset clamping, poisoned
   stack reads, and borrowed-reference invalidations. (Eduard)
 * Fixed various miscellaneous diagnostic bugs. (Sashiko)
 * Misc improvements and refinements.

--

Motivation
~~~~~~~~~~

The verifier log is the primary interface through which the verifier
communicates to the user its verdict on whether a program was accepted
or rejected.

To aid the debugging of rejection decisions, verifier also reports the
symbolic state of the program at each instruction, across every explored
path of the BPF program. Such detailed information is critical to
introspect the correctness of verification decisions, and provide
insight into why a given program may have failed to load in the kernel.

A constant pain point in BPF ecosystem throughout the years has been the
difficulty of debugging verification errors. The human-readable error
messages produced in response to a failure in satisfying safety-related
constraints are often terse, context-dependent, or insufficient for
understanding why a given error may have happened. Users must fall back
to the verbose instruction-by-instruction breakdown of how the symbolic
state evolved to surface the root cause. For programs with a huge log
volume due to high verification complexity, such logs quickly become
inscrutable.

All of this has made life difficult for users lacking an understanding
of how the verifier works, and the various heuristics and idiosyncracies
used by it. In some cases, even seasoned BPF experts spend significant
time reverse engineering why a program may have failed, and have to
reach into the verifier's source code to form a complete picture of the
verification process.

Such a steep learning curve and cognitive burden also hurts the speed of
BPF development, as the verifier sits right in the middle of the user's
iteration loop while they make use of BPF to solve any given problem.
Expertise in debugging verifier errors does not scale in terms of teams
deploying these programs in production across a diverse set of kernels.

Overall, this leads to a poorer developer experience, causes visible
user dissatisfaction, and remains a drag on wider BPF adoption. With
some of the more recent developments where users increasingly leverage
AI tooling [0] to author their code, this bottleneck becomes even more
critical to address, since it throttles the much faster iteration loop
of AI agents.

  [0]: https://lwn.net/Articles/1075067

Approach
~~~~~~~~

This series starts moving selected failures from terse terminal messages
toward diagnostics that carry the relevant context for a verification
failure. The existing verbose log remains the low-level trace. The new
report is emitted after this trace with selected failures and answers
the immediate debugging questions:

  - what verifier rule failed,
  - why the current state does not satisfy it,
  - where the failing instruction maps to source,
  - which earlier branch or state event made this path fail,
  - what kind of source change would satisfy the verifier.

The series adds a text-only diagnostics framework under kernel/bpf and
uses it to augment selected verifier errors. Existing verbose(env, ...)
messages are kept, so current selftest expectations and existing log
consumers continue to see the legacy text. The new report has a uniform
outer shape:

  Verification failed: <category>: <problem>

  Reason:
    exact reason for the verification failure, with details

  At:
    source and instruction annotation

  Causal path:
    compressed branch and verifier-state events relevant for debugging

  Suggestion:
    speculation on potential fixes to repair the program

The outer shape is shared, but report construction is category-specific.

The categories are intentionally broad and reviewable. This revision
covers representative cases in Register Type Safety, Memory Safety,
Resource Lifetime Safety, Call Type Safety, Execution Context Safety,
Program Structure, Policy, Verifier Limit, and Verifier Internal errors.
It does not attempt to convert every verbose(env, ...) site for now.
Additional verbose-only errors can be moved into the same framework
incrementally.

The following excerpts are copied from this current run on this branch:

  ./test_progs -j1 \
    -a cpumask/test_populate_invalid_destination,\
    cpumask/test_alloc_no_release,\
    verifier_helper_value_access/via_variable_no_max_check_1,\
    verifier_sock/invalidate_pkt_pointers_from_global_func \
    -vv

They show the old terminal error and the exact new diagnostic report,
including the source/instruction annotation.

Call Type Safety, cpumask/test_populate_invalid_destination:

  Legacy:
    R1 type=scalar expected=fp

  Diagnostic:
    Verification failed: Call Type Safety: Invalid call argument

    Reason:
      The first argument (R1) to bpf_cpumask_populate does not satisfy the verifier contract: the kfunc
      expects 16 bytes of memory for (struct cpumask), but it is an integer scalar and not
      verifier-known memory.

    At:
      test_populate_invalid_destination @ cpumask_failure.c:234:8
          232 | ...                                                                                   2 | (b7) r1 = 1193046
          233 | ...                                                                                   3 | (b7) r3 = 8
      >>> 234 |         ret = bpf_cpumask_populate((struct cpumask *)invalid, &bits, sizeof...   >>>  4 | (85) call bpf_cpumask_populate#62115
              |         ^-- error: invalid first argument (R1) for bpf_cpumask_populate
          235 |         if (!ret)                                                                     5 | (56) if w0 != 0x0 goto pc+4
          236 |                 err = 2;                                                              6 | (18) r1 = 0xffffc9000028e000

    Causal path:
      test_populate_invalid_destination @ cpumask_failure.c:234:8
          232 | ...                                                                                   0 | (bf) r2 = r10
          233 | ...                                                                                   1 | (07) r2 += -8
      >>> 234 |         ret = bpf_cpumask_populate((struct cpumask *)invalid, &bits, sizeof...   >>>  2 | (b7) r1 = 1193046
              |         ^-- update: R1 changed from context pointer at offset 0 to integer scalar value
              |             1193046
          235 |         if (!ret)                                                                     3 | (b7) r3 = 8
          236 |                 err = 2;                                                              4 | (85) call bpf_cpumask_populate#62115

    Suggestion:
      Pass stack, map, context, or other verifier-known memory of the expected type and size, not an
      integer cast to a pointer.

Register Type Safety, verifier_sock/invalidate_pkt_pointers_from_global_func:

  Legacy:
    R7 invalid mem access 'scalar'

  Diagnostic:
    Verification failed: Register Type Safety: Invalid dereference

    Reason:
      R7 is an integer scalar here, not a pointer to memory.

    At:
      invalidate_pkt_pointers_from_global_func @ verifier_sock.c:1067:5
          1065 | ...                                                                                  8 | (85) call pc+4
          1066 |         skb_pull_data1(sk, 0);                                                       9 | (b4) w1 = 42
      >>> 1067 |         *p = 42; /* this is unsafe */                                           >>> 10 | (63) *(u32 *)(r7 +0) = r1
               |         ^-- error: invalid dereference of R7 (scalar)
          1068 | ...                                                                                 11 | (bc) w0 = w6
          1069 | }                                                                                   12 | (95) exit

    Causal path:
      invalidate_pkt_pointers_from_global_func @ verifier_sock.c:1066:2
          1064 |         if ((void *)(p + 1) > (void *)(long)sk->data_end)                            6 | (b4) w6 = 0
          1065 | ...                                                                                  7 | (b4) w2 = 0
      >>> 1066 |         skb_pull_data1(sk, 0);                                                  >>>  8 | (85) call pc+4
               |         ^-- invalidated: R7: packet data may have moved; previous value was pkt at
               |             offset 0
          1067 |         *p = 42; /* this is unsafe */                                                9 | (b4) w1 = 42
          1068 | ...                                                                                 10 | (63) *(u32 *)(r7 +0) = r1

    Suggestion:
      Preserve a pointer-valued register where needed, or reload and revalidate the pointer after scalar
      arithmetic, helper calls, or other operations that can invalidate it.

Memory Safety, verifier_helper_value_access/via_variable_no_max_check_1:

  Legacy:
    R1 unbounded memory access, make sure to bounds check any such access

  Diagnostic:
    Verification failed: Memory Safety: Access outside bounds

    Reason:
      The verifier cannot prove offset + access_size <= object_size. Here, R1 has unsigned maximum
      4294967295, which exceeds BPF_MAX_VAR_OFF 536870912. R1 is map_value; offset is variable: known
      bits 0x0, unknown mask 0xffffffff; signed range [0, 4294967295], unsigned range [0, 4294967295];
      access_size is 1; object_size is 48.

    At:
      via_variable_no_max_check_1 @ verifier_helper_value_access.c:627:2
          625 | ...                                                                                  11 | (b7) r2 = 1
          626 | ...                                                                                  12 | (b7) r3 = 0
      >>> 627 |         asm volatile ("                                 \                        >>> 13 | (85) call bpf_probe_read_kernel#113
              |         ^-- error: access may be outside object bounds
          628 | ...                                                                                  14 | (95) exit
          629 | ...

    Causal path:
      via_variable_no_max_check_1 @ verifier_helper_value_access.c:627:2
          625 | ...                                                                                   8 | (bf) r1 = r0
          626 | ...                                                                                   9 | (61) r3 = *(u32 *)(r0 +0)
      >>> 627 |         asm volatile ("                                 \                        >>> 10 | (0f) r1 += r3
              |         ^-- update: R1 changed from map value from map_hash_48b at offset 0 to map value
              |             from map_hash_48b with variable offset: known bits 0x0, unknown mask
              |             0xffffffff, signed range [0, 4294967295], unsigned range [0, 4294967295]
          628 | ...                                                                                  11 | (b7) r2 = 1
          629 | ...                                                                                  12 | (b7) r3 = 0

    Suggestion:
      Add or adjust a bounds check that proves offset + access_size stays within the object.

Resource Lifetime Safety, cpumask/test_alloc_no_release:

  Legacy:
    Unreleased reference id=2 alloc_insn=0
    BPF_EXIT instruction in main prog would lead to reference leak

  Diagnostic:
    Verification failed: Resource Lifetime Safety: Unreleased resource

    Reason:
      Owned resource (id=2) was acquired at instruction 0 and still needs to be released before this
      exit path.

    At:
      test_alloc_no_release @ cpumask_failure.c:36:5
          34 | ...                                                                                   19 | (7b) *(u64 *)(r10 -8) = r6
          35 | ...                                                                                   20 | (b4) w0 = 0
      >>> 36 | int BPF_PROG(test_alloc_no_release, struct task_struct *task, u64 clone_flags)    >>> 21 | (95) exit
             | ^-- error: owned resource (id=2) still needs release
          37 | ...
          38 | ...

    Causal path:
      test_alloc_no_release @ cpumask_common.h:78:12
          76 | ...
          77 | ...
      >>> 78 |         cpumask = bpf_cpumask_create();                                           >>>  0 | (85) call bpf_cpumask_create#62106
             |         ^-- acquired: owned resource (id=2)
          79 |         if (!cpumask) {                                                                1 | (bf) r6 = r0
          80 |                 err = 1;                                                               2 | (55) if r6 != 0x0 goto pc+5
      test_alloc_no_release @ cpumask_common.h:79:6
          77 | ...                                                                                    0 | (85) call bpf_cpumask_create#62106
          78 |         cpumask = bpf_cpumask_create();                                                1 | (bf) r6 = r0
      >>> 79 |         if (!cpumask) {                                                           >>>  2 | (55) if r6 != 0x0 goto pc+5
             |         ^-- branch: explored as true, goto followed
          80 |                 err = 1;                                                               3 | (18) r1 = 0xffffc90000252000
          81 | ...
      test_alloc_no_release @ cpumask_common.h:84:6
          82 | ...                                                                                    9 | (85) call bpf_cpumask_empty#62107
          83 | ...                                                                                   10 | (54) w0 &= 1
      >>> 84 |         if (!bpf_cpumask_empty(cast(cpumask))) {                                  >>> 11 | (56) if w0 != 0x0 goto pc+7
             |         ^-- branch: explored as true, goto followed
          85 |                 err = 2;                                                              12 | (18) r1 = 0xffffc90000252000
          86 |                 bpf_cpumask_release(cpumask);

    Suggestion:
      Release or transfer ownership of the acquired resource on every path before the program exits.

Patch layout:

  - Patches 1-3 add the common infrastructure: report sections, diagnostic
    categories, source-line lookup, and side-by-side source/instruction
    annotations.
  - Patches 4-7 add growable environment-owned diagnostic history. The
    history follows the active verifier path and is pruned when
    backtracking; it records branch outcomes, material register changes,
    reference lifetime events, and execution-context events so reports can
    explain the path and causal state transitions that led to the failure.
  - Patches 8-16 add the first category-specific reports. These patches
    hook selected verifier failure sites and choose the evidence that is
    useful for that error class.
  - Patch 17 gates diagnostic collection and rendering on verifier log
    level, so stats-only loads do not collect the extra path history.
    The overhead is limited to verbose log mode through this change.

Evaluation
~~~~~~~~~~

To quantitatively measure and goal on metrics that help assess the
quality of diagnostics (apart from subjective human feedback), we use AI
models (called over APIs) and veristat metrics to compare results.

Models are used as a way to measure repair utility of the extra
diagnostics over a fixed test set. Each prompt contains only a sanitized
source snippet and either the legacy verifier log or the new diagnostic
log. To avoid leaking the answer through the test itself, comments,
annotations, and other source hints that describe the intended failure
were removed. The model is not given internet access, repository access,
test execution, verifier access, or the expected fix. The expected
causes and intended repairs are kept outside the prompt. Under those
constraints, correctness, exact repair rate, output size, reasoning
tokens, cost, and wall time provide a proxy for whether the additional
verifier context makes the failure easier to understand and turn into a
source-level fix.

Verifier cost is assessed by forcing the collection of diagnostics
information during normal verification. This information by default is
supposed to only be collected and processed when logs are enabled, but
forcing it even without a verbose log levels helps us understand the
cost in CPU time and memory that is incurred due to this extra data.

Both evaluations are covered in the sections below.

Repair Quality
--------------

Repair quality is measured by asking API-only models to propose source
fixes from a sanitized source snippet and verifier log. The criterion is
score >= 3 on a 0-4 local grading scale, where 3 means a likely fix with
incomplete detail and 4 means an actionable source-level fix. Score 4 is
reported separately as the exact repair rate. The reported model set
contains 596 successful API responses: 298 diagnostic and 298 legacy.

Main results (details available in Appendix):

  Metric                              Diagnostic   Legacy       Delta
  ----------------------------------  -----------  -----------  --------
  Answers                             298          298
  Success rate                        97.0%        97.3%        -0.3 pp
  Exact repair rate                   82.2%        72.1%        +10.1 pp
  Mean score                          3.79         3.69         +0.10
  Solver cost                         $8.93        $10.37       -13.8%
  Mean output tokens per answer       1662         1975         -15.8%
  Mean reasoning tokens per answer    951          1080         -11.9%
  Mean wall time per answer           37.3s        44.1s        -15.4%

Diagnostic prompts carry more input context. The resulting answers are
still shorter and cheaper. In this run, diagnostics do not materially
change the coarse success rate, but they increase exact repairs by 10.1
percentage points while reducing cost, output tokens, reasoning tokens,
and wall time.

Verifier cost
-------------

Verifier cost is measured with veristat over the BPF selftest programs
selected by tools/testing/selftests/bpf/veristat.cfg, with five
repetitions per configuration. With diagnostics gated by log level, wall
time and verifier duration stay close to baseline. Forcing diagnostics
on for every verifier run adds modest overhead on this workload.

memory.peak is measured with cgroup v2 memory accounting for each
program load. The table reports the mean wall time, the mean summed
verifier duration, and the mean of the per-repetition maximum
memory.peak values.

  Configuration                 Wall time mean   Verifier duration    memory.peak
  ----------------------------  --------------   -----------------    -----------
  bpf-next baseline                 25.78s            9.86s              142 MiB
  diagnostics, gated                26.64s           10.16s              144 MiB
  diagnostics, forced on            28.01s           11.00s              148 MiB

TODO
~~~~

Known follow-up work:

  - Convert more verbose-only verifier errors into category-specific
    reports.
  - Integrate loop-convergence failure summarization from Eduard.
  - Report candidate kfuncs/helpers for releasing owned resources.
  - Explore association of source variables with verifier registers
    where debug info permits it.
  - Refine suggestions per category and, where useful, link diagnostics
    to maintained documentation.
  - Bring verifier warnings into the same reporting framework.

Appendix: AI repair details
~~~~~~~~~~~~~~~~~~~~~~~~~~~

The 20 verifier-failing selftest cases are:

  Case     Diff    Category                    Selftest selector
  -------  ------  --------------------------  ---------------------------------------------
  case-001 easy    Call Type Safety            cpumask/test_populate_invalid_destination
  case-002 easy    Resource Lifetime Safety    cpumask/test_alloc_no_release
  case-003 easy    Register Type Safety        verifier_spill_fill/check_corrupted_spill_fill
  case-004 easy    Register Type Safety        test_global_funcs/global_func12
  case-005 easy    Execution Context Safety    preempt_lock/preempt_sleepable_helper
  case-006 easy    Policy                      verifier_helper_restricted/in_bpf_prog_type_kprobe_1
  case-007 medium  Memory Safety               dynptr/dynptr_slice_var_len1
  case-008 medium  Call Type Safety            dynptr/test_dynptr_skb_small_buff
  case-009 medium  Call Type Safety            task_kfunc/task_kfunc_acquire_untrusted
  case-010 medium  Register Type Safety        test_global_funcs/global_func6
  case-011 medium  Resource Lifetime Safety    dynptr/ringbuf_missing_release2
  case-012 medium  Execution Context Safety    irq/irq_sleepable_helper_global_subprog
  case-013 medium  Verifier Limit              test_global_funcs/global_func1
  case-014 hard    Memory Safety               verifier_helper_value_access/via_variable_no_max_check_1
  case-015 hard    Register Type Safety        verifier_sock/invalidate_pkt_pointers_from_global_func
  case-016 hard    Resource Lifetime Safety    verifier_ref_tracking/check_free_in_one_subbranch
  case-017 hard    Resource Lifetime Safety    irq/irq_restore_ooo
  case-018 hard    Resource Lifetime Safety    res_spin_lock_failure/res_spin_lock_ooo_unlock
  case-019 hard    Program Structure           verifier_loops1/bounded_recursion
  case-020 hard    Verifier Limit              verifier_liveness_exp/liveness_exponential_complexity

The grading scale is:

  - 4: identifies the verifier cause and gives an actionable source-level fix.
  - 3: gives a likely fix, but with incomplete explanation or detail.
  - 2: identifies part of the issue, but not enough to fix confidently.
  - 1: gives only a broad verifier-area answer, or a wrong/insufficient fix.
  - 0: does not identify the intended verifier failure.

Detailed effort metrics for the model set:

  Metric                   Variant      Mean      Median       P99
  -----------------------  ----------  --------  --------  --------
  Cost per answer          diagnostic   $0.030    $0.019    $0.203
  Cost per answer          legacy       $0.035    $0.018    $0.223
  Input tokens             diagnostic     1391      1220      4048
  Input tokens             legacy         1052       805      3655
  Output tokens            diagnostic     1662       954      8680
  Output tokens            legacy         1975      1034      9912
  Reasoning tokens         diagnostic      951       208      8108
  Reasoning tokens         legacy         1080       228      6322
  Wall time                diagnostic    37.3s     18.3s    222.7s
  Wall time                legacy        44.1s     19.8s    255.5s

Per-model results for diagnostic prompts:

  Model profile                              Ans  Succ   Exact  Mean  Cost     OutK  ReasK  Wall
  -----------------------------------------  ---  -----  -----  ----  -------  ----  -----  -----
  anthropic-haiku-4.5-default                 20   90.0   80.0  3.70  $0.087   11.4    0.0   5.0s
  anthropic-opus-4.8-high                     20  100.0   90.0  3.90  $0.819   25.5    0.0  15.5s
  anthropic-opus-4.8-medium                   20   95.0   90.0  3.85  $0.870   27.5    0.0  12.7s
  anthropic-sonnet-4.6-high                   20   95.0   80.0  3.75  $0.824   48.9    0.0  21.6s
  anthropic-sonnet-4.6-medium                 20  100.0   65.0  3.65  $0.278   12.4    0.0   6.6s
  openai-gpt-5.3-codex-high                   20  100.0   80.0  3.80  $0.601   39.8   33.9  25.0s
  openai-gpt-5.3-codex-medium                 20   95.0   85.0  3.80  $0.287   17.5   11.4  13.5s
  openai-gpt-5.5-high                         20  100.0   90.0  3.90  $2.356   74.4   65.2  56.8s
  openai-gpt-5.5-low                          20  100.0   90.0  3.90  $0.686   18.7    8.5  21.3s
  openai-gpt-5.5-medium                       19  100.0   84.2  3.84  $1.353   41.1   31.8  37.4s
  openai-gpt-5.5-none                         20   95.0   90.0  3.85  $0.457   11.1    0.0  10.4s
  openrouter-deepseek-r1-0528                 20  100.0   75.0  3.75  $0.145   61.5   53.8  98.3s
  openrouter-deepseek-v3.2                    19  100.0   78.9  3.79  $0.028   64.2   58.1  87.3s
  openrouter-glm-5.1-high                     20   95.0   80.0  3.75  $0.113   28.8   20.7  19.3s
  openrouter-qwen3-coder                      20   90.0   75.0  3.65  $0.028   12.4    0.0   7.1s

Per-model results for legacy prompts:

  Model profile                              Ans  Succ   Exact  Mean  Cost     OutK  ReasK  Wall
  -----------------------------------------  ---  -----  -----  ----  -------  ----  -----  -----
  anthropic-haiku-4.5-default                 20   90.0   45.0  3.35  $0.081   11.6    0.0   5.0s
  anthropic-opus-4.8-high                     20   90.0   70.0  3.60  $1.192   42.2    0.0  17.5s
  anthropic-opus-4.8-medium                   20   95.0   85.0  3.80  $1.001   34.5    0.0  13.4s
  anthropic-sonnet-4.6-high                   20  100.0   75.0  3.75  $1.181   74.1    0.0  24.4s
  anthropic-sonnet-4.6-medium                 20   95.0   65.0  3.60  $0.420   23.4    0.0  12.3s
  openai-gpt-5.3-codex-high                   20  100.0   85.0  3.85  $0.562   37.8   31.6  27.1s
  openai-gpt-5.3-codex-medium                 20  100.0   75.0  3.75  $0.318   20.3   13.7  13.6s
  openai-gpt-5.5-high                         19  100.0   78.9  3.79  $2.613   84.0   75.4  98.1s
  openai-gpt-5.5-low                          20  100.0   75.0  3.75  $0.664   19.0    9.7  21.7s
  openai-gpt-5.5-medium                       20  100.0   75.0  3.75  $1.602   50.2   41.0  56.1s
  openai-gpt-5.5-none                         20   95.0   85.0  3.80  $0.416   10.7    0.0  10.9s
  openrouter-deepseek-r1-0528                 20   95.0   70.0  3.65  $0.149   64.6   57.5  92.5s
  openrouter-deepseek-v3.2                    20  100.0   60.0  3.60  $0.030   74.3   67.8  98.3s
  openrouter-glm-5.1-high                     19  100.0   63.2  3.63  $0.115   32.1   24.9  30.4s
  openrouter-qwen3-coder                      20  100.0   75.0  3.75  $0.022    9.5    0.0   5.4s

Kumar Kartikeya Dwivedi (17):
  bpf: Add verifier diagnostics report helpers
  bpf: Add source and instruction diagnostic context
  bpf: Add verifier diagnostic event log
  bpf: Prune verifier diagnostics on backtracking
  bpf: Track verifier register diagnostic events
  bpf: Track verifier reference diagnostic events
  bpf: Track verifier context diagnostic events
  bpf: Report Register Type Safety errors
  bpf: Report Memory Safety bounds errors
  bpf: Report Resource Lifetime reference leaks
  bpf: Report Call Type Safety argument errors
  bpf: Report Execution Context Safety errors
  bpf: Report Program Structure CFG errors
  bpf: Report Policy helper and kfunc errors
  bpf: Report Verifier Limit errors
  bpf: Report Verifier Internal errors
  bpf: Gate verifier diagnostics on log level

 include/linux/bpf.h          |    4 +-
 include/linux/bpf_verifier.h |    3 +
 include/linux/btf.h          |    2 +
 kernel/bpf/Makefile          |    2 +-
 kernel/bpf/btf.c             |   11 +
 kernel/bpf/cfg.c             |   39 +
 kernel/bpf/core.c            |   10 +-
 kernel/bpf/diagnostics.c     | 2502 ++++++++++++++++++++++++++++++++++
 kernel/bpf/diagnostics.h     |  272 ++++
 kernel/bpf/liveness.c        |    6 +
 kernel/bpf/verifier.c        | 1253 ++++++++++++++++-
 11 files changed, 4052 insertions(+), 52 deletions(-)
 create mode 100644 kernel/bpf/diagnostics.c
 create mode 100644 kernel/bpf/diagnostics.h


base-commit: e771677c937da5808f7b6c1f0e4a97ec1a84f8a8
-- 
2.53.0


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

* [PATCH bpf-next v2 01/17] bpf: Add verifier diagnostics report helpers
  2026-06-19 20:59 [PATCH bpf-next v2 00/17] Redesign Verification Errors Kumar Kartikeya Dwivedi
@ 2026-06-19 20:59 ` Kumar Kartikeya Dwivedi
  2026-06-19 21:09   ` sashiko-bot
  2026-06-19 20:59 ` [PATCH bpf-next v2 02/17] bpf: Add source and instruction diagnostic context Kumar Kartikeya Dwivedi
                   ` (15 subsequent siblings)
  16 siblings, 1 reply; 34+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-06-19 20:59 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team

Add a small diagnostics renderer for verifier reports and wire it into the
BPF build. The initial helpers emit the common text structure: a failure
header plus reusable report sections.

Wrap report prose at 100 columns so Reason and Suggestion text stays readable
without changing source or instruction gutters. Keep reusable formatting
scratch storage in the environment-owned diagnostics object.

Gate the helpers on normal verifier log output from the start, so
BPF_LOG_STATS-only loads do not collect or render diagnostics.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 kernel/bpf/Makefile      |   2 +-
 kernel/bpf/diagnostics.c | 170 +++++++++++++++++++++++++++++++++++++++
 kernel/bpf/diagnostics.h |  16 ++++
 3 files changed, 187 insertions(+), 1 deletion(-)
 create mode 100644 kernel/bpf/diagnostics.c
 create mode 100644 kernel/bpf/diagnostics.h

diff --git a/kernel/bpf/Makefile b/kernel/bpf/Makefile
index 4dc41bf5780c..90255d80e5be 100644
--- a/kernel/bpf/Makefile
+++ b/kernel/bpf/Makefile
@@ -6,7 +6,7 @@ cflags-nogcse-$(CONFIG_X86)$(CONFIG_CC_IS_GCC) := -fno-gcse
 endif
 CFLAGS_core.o += -Wno-override-init $(cflags-nogcse-yy)
 
-obj-$(CONFIG_BPF_SYSCALL) += syscall.o verifier.o inode.o helpers.o tnum.o cnum.o log.o token.o liveness.o const_fold.o
+obj-$(CONFIG_BPF_SYSCALL) += syscall.o verifier.o inode.o helpers.o tnum.o cnum.o log.o token.o liveness.o const_fold.o diagnostics.o
 obj-$(CONFIG_BPF_SYSCALL) += bpf_iter.o map_iter.o task_iter.o prog_iter.o link_iter.o
 obj-$(CONFIG_BPF_SYSCALL) += hashtab.o arraymap.o percpu_freelist.o bpf_lru_list.o lpm_trie.o map_in_map.o bloom_filter.o
 obj-$(CONFIG_BPF_SYSCALL) += local_storage.o queue_stack_maps.o ringbuf.o bpf_insn_array.o
diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
new file mode 100644
index 000000000000..a18fd5aa395d
--- /dev/null
+++ b/kernel/bpf/diagnostics.c
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+
+#include <linux/bpf_verifier.h>
+#include <linux/ctype.h>
+#include <linux/slab.h>
+#include <linux/stdarg.h>
+#include <linux/string.h>
+
+#include "diagnostics.h"
+
+#define BPF_DIAG_CATEGORY_MEMORY_SAFETY "Memory Safety"
+#define BPF_DIAG_CATEGORY_REGISTER_TYPE_SAFETY "Register Type Safety"
+#define BPF_DIAG_CATEGORY_CALL_TYPE_SAFETY "Call Type Safety"
+#define BPF_DIAG_CATEGORY_RESOURCE_LIFETIME_SAFETY "Resource Lifetime Safety"
+#define BPF_DIAG_CATEGORY_EXECUTION_CONTEXT_SAFETY "Execution Context Safety"
+#define BPF_DIAG_CATEGORY_PROGRAM_STRUCTURE "Program Structure"
+#define BPF_DIAG_CATEGORY_POLICY "Policy"
+#define BPF_DIAG_CATEGORY_VERIFIER_LIMIT "Verifier Limit"
+#define BPF_DIAG_CATEGORY_VERIFIER_INTERNAL_ERROR "Verifier Internal Error"
+
+#define BPF_DIAG_TEXT_WIDTH 100
+#define BPF_DIAG_TEXT_INDENT "  "
+
+bool bpf_diag_enabled(const struct bpf_verifier_env *env)
+{
+	return env->log.level & BPF_LOG_LEVEL;
+}
+
+static void bpf_diag_log(struct bpf_verifier_env *env, const char *fmt, ...)
+{
+	va_list args;
+
+	if (!bpf_diag_enabled(env))
+		return;
+
+	va_start(args, fmt);
+	bpf_verifier_vlog(&env->log, fmt, args);
+	va_end(args);
+}
+
+static void bpf_diag_print_wrapped_prefixed(struct bpf_verifier_env *env,
+					    const char *first_prefix,
+					    const char *next_prefix,
+					    const char *text)
+{
+	const char *prefix = first_prefix;
+
+	while (*text) {
+		const char *line = text;
+		int prefix_len = strlen(prefix);
+		int text_width = BPF_DIAG_TEXT_WIDTH - prefix_len;
+		int len = 0, last_space = -1;
+
+		if (text_width < 1)
+			text_width = 1;
+
+		while (line[len] && line[len] != '\n' && len < text_width) {
+			if (line[len] == ' ')
+				last_space = len;
+			len++;
+		}
+
+		if (line[len] && line[len] != '\n' && line[len] != ' ' &&
+		    last_space > 0)
+			len = last_space;
+
+		bpf_diag_log(env, "%s%.*s\n", prefix, len, line);
+
+		text = line + len;
+		while (*text == ' ')
+			text++;
+		if (*text == '\n')
+			text++;
+
+		prefix = next_prefix;
+	}
+}
+
+static void bpf_diag_print_wrapped_text(struct bpf_verifier_env *env,
+					const char *text)
+{
+	bpf_diag_print_wrapped_prefixed(env, BPF_DIAG_TEXT_INDENT,
+					BPF_DIAG_TEXT_INDENT, text);
+}
+
+static void bpf_diag_vprint_indented(struct bpf_verifier_env *env,
+				     const char *fmt, va_list args)
+{
+	char *buf;
+
+	if (!bpf_diag_enabled(env))
+		return;
+
+	buf = kvasprintf(GFP_KERNEL_ACCOUNT, fmt, args);
+	if (!buf) {
+		bpf_diag_log(env, "%s<failed to allocate diagnostic text>\n",
+			     BPF_DIAG_TEXT_INDENT);
+		return;
+	}
+
+	bpf_diag_print_wrapped_text(env, buf);
+	kfree(buf);
+}
+
+void bpf_diag_report_header(struct bpf_verifier_env *env,
+			    const char *category, const char *problem)
+{
+	char first;
+
+	if (!bpf_diag_enabled(env))
+		return;
+
+	category = category ?: BPF_DIAG_CATEGORY_VERIFIER_INTERNAL_ERROR;
+	problem = problem ?: "";
+
+	if (!problem[0]) {
+		bpf_diag_log(env, "\nVerification failed: %s\n", category);
+		return;
+	}
+
+	first = toupper(problem[0]);
+	bpf_diag_log(env, "\nVerification failed: %s: %c%s\n", category,
+		     first, problem + 1);
+}
+
+static void bpf_diag_report_reason(struct bpf_verifier_env *env,
+				   const char *fmt, ...) __printf(2, 3);
+static void bpf_diag_report_suggestion(struct bpf_verifier_env *env,
+				       const char *fmt, ...) __printf(2, 3);
+
+static void bpf_diag_report_section(struct bpf_verifier_env *env,
+				    const char *title)
+{
+	if (!bpf_diag_enabled(env))
+		return;
+
+	bpf_diag_log(env, "\n%s:\n", title);
+}
+
+static void bpf_diag_report_reason(struct bpf_verifier_env *env,
+				   const char *fmt, ...)
+{
+	va_list args;
+
+	if (!bpf_diag_enabled(env))
+		return;
+
+	bpf_diag_report_section(env, "Reason");
+
+	va_start(args, fmt);
+	bpf_diag_vprint_indented(env, fmt, args);
+	va_end(args);
+}
+
+static void bpf_diag_report_suggestion(struct bpf_verifier_env *env,
+				       const char *fmt, ...)
+{
+	va_list args;
+
+	if (!bpf_diag_enabled(env))
+		return;
+
+	bpf_diag_report_section(env, "Suggestion");
+
+	va_start(args, fmt);
+	bpf_diag_vprint_indented(env, fmt, args);
+	va_end(args);
+	bpf_diag_log(env, "\n");
+}
diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
new file mode 100644
index 000000000000..5fb3271530a1
--- /dev/null
+++ b/kernel/bpf/diagnostics.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#ifndef __BPF_DIAGNOSTICS_H
+#define __BPF_DIAGNOSTICS_H
+
+#include <linux/compiler_attributes.h>
+#include <linux/types.h>
+
+struct bpf_verifier_env;
+
+bool bpf_diag_enabled(const struct bpf_verifier_env *env);
+void bpf_diag_report_header(struct bpf_verifier_env *env,
+			    const char *category, const char *problem);
+
+#endif /* __BPF_DIAGNOSTICS_H */
-- 
2.53.0


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

* [PATCH bpf-next v2 02/17] bpf: Add source and instruction diagnostic context
  2026-06-19 20:59 [PATCH bpf-next v2 00/17] Redesign Verification Errors Kumar Kartikeya Dwivedi
  2026-06-19 20:59 ` [PATCH bpf-next v2 01/17] bpf: Add verifier diagnostics report helpers Kumar Kartikeya Dwivedi
@ 2026-06-19 20:59 ` Kumar Kartikeya Dwivedi
  2026-06-19 21:46   ` bot+bpf-ci
  2026-06-19 20:59 ` [PATCH bpf-next v2 03/17] bpf: Add verifier diagnostic event log Kumar Kartikeya Dwivedi
                   ` (14 subsequent siblings)
  16 siblings, 1 reply; 34+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-06-19 20:59 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team

Teach verifier diagnostics to annotate an instruction with BTF source
line information and nearby BPF instructions. The renderer keeps source
text in a fixed-width lane and prints instructions in a stable right-hand
gutter.

Wrap annotation text under the source line so long error labels remain
readable while the source and instruction lanes keep their fixed layout.

Keeping source and instruction context in one commit preserves the visual
layout contract that later diagnostic reports rely on.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 include/linux/bpf.h          |   4 +-
 include/linux/bpf_verifier.h |   3 +
 include/linux/btf.h          |   2 +
 kernel/bpf/btf.c             |  11 +
 kernel/bpf/core.c            |  10 +-
 kernel/bpf/diagnostics.c     | 538 ++++++++++++++++++++++++++++++++++-
 kernel/bpf/diagnostics.h     |  13 +
 kernel/bpf/verifier.c        |   6 +-
 8 files changed, 579 insertions(+), 8 deletions(-)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 7719f6528445..b59ec4144cf0 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -4148,8 +4148,10 @@ static inline bool bpf_is_subprog(const struct bpf_prog *prog)
 }
 
 const struct bpf_line_info *bpf_find_linfo(const struct bpf_prog *prog, u32 insn_off);
+#define BPF_LINFO_LINE_TRIM 1
 void bpf_get_linfo_file_line(struct btf *btf, const struct bpf_line_info *linfo,
-			     const char **filep, const char **linep, int *nump);
+			     const char **filep, const char **linep, int *nump,
+			     u32 flags);
 int bpf_prog_get_file_line(struct bpf_prog *prog, unsigned long ip, const char **filep,
 			   const char **linep, int *nump);
 struct bpf_prog *bpf_prog_find_from_stack(void);
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 39a851e690ec..a5dc2568ceb9 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -835,6 +835,7 @@ static inline u16 bpf_in_stack_arg_cnt(const struct bpf_subprog_info *sub)
 	return 0;
 }
 
+struct bpf_diag;
 struct bpf_verifier_env;
 
 struct backtrack_state {
@@ -942,6 +943,7 @@ struct bpf_verifier_env {
 	struct bpf_insn_aux_data *insn_aux_data; /* array of per-insn state */
 	const struct bpf_line_info *prev_linfo;
 	struct bpf_verifier_log log;
+	struct bpf_diag *diag;
 	struct bpf_subprog_info subprog_info[BPF_MAX_SUBPROGS + 2]; /* max + 2 for the fake and exception subprogs */
 	/* subprog indices sorted in topological order: leaves first, callers last */
 	int subprog_topo_order[BPF_MAX_SUBPROGS + 2];
@@ -1399,6 +1401,7 @@ void print_verifier_state(struct bpf_verifier_env *env, const struct bpf_verifie
 void print_insn_state(struct bpf_verifier_env *env, const struct bpf_verifier_state *vstate,
 		      u32 frameno);
 u32 bpf_vlog_alignment(u32 pos);
+const char *bpf_disasm_kfunc_name(void *data, const struct bpf_insn *insn);
 
 struct bpf_subprog_info *bpf_find_containing_subprog(struct bpf_verifier_env *env, int off);
 int bpf_jmp_offset(struct bpf_insn *insn);
diff --git a/include/linux/btf.h b/include/linux/btf.h
index 240401d9b25b..ed01a369e6cd 100644
--- a/include/linux/btf.h
+++ b/include/linux/btf.h
@@ -213,6 +213,8 @@ int btf_type_seq_show_flags(const struct btf *btf, u32 type_id, void *obj,
  */
 int btf_type_snprintf_show(const struct btf *btf, u32 type_id, void *obj,
 			   char *buf, int len, u64 flags);
+int btf_type_snprintf_show_name(const struct btf *btf, u32 type_id,
+				char *buf, int len);
 
 int btf_get_fd_by_id(u32 id);
 u32 btf_obj_id(const struct btf *btf);
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 15ae7c43f594..6f08bf383c11 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -8255,6 +8255,17 @@ int btf_type_snprintf_show(const struct btf *btf, u32 type_id, void *obj,
 	return ssnprintf.len;
 }
 
+int btf_type_snprintf_show_name(const struct btf *btf, u32 type_id,
+				char *buf, int len)
+{
+	struct btf_show show = {
+		.btf = btf,
+		.state.type_id = type_id,
+	};
+
+	return snprintf(buf, len, "%s", btf_show_name(&show));
+}
+
 #ifdef CONFIG_PROC_FS
 static void bpf_btf_show_fdinfo(struct seq_file *m, struct file *filp)
 {
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 649cce41e13f..7883449ead89 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -3408,7 +3408,8 @@ EXPORT_TRACEPOINT_SYMBOL_GPL(xdp_bulk_tx);
 #ifdef CONFIG_BPF_SYSCALL
 
 void bpf_get_linfo_file_line(struct btf *btf, const struct bpf_line_info *linfo,
-			     const char **filep, const char **linep, int *nump)
+			     const char **filep, const char **linep, int *nump,
+			     u32 flags)
 {
 	/* Get base component of the file path. */
 	if (filep) {
@@ -3416,10 +3417,10 @@ void bpf_get_linfo_file_line(struct btf *btf, const struct bpf_line_info *linfo,
 		*filep = kbasename(*filep);
 	}
 
-	/* Obtain the source line, and strip whitespace in prefix. */
+	/* Obtain the source line, and optionally strip whitespace in prefix. */
 	if (linep) {
 		*linep = btf_name_by_offset(btf, linfo->line_off);
-		while (isspace(**linep))
+		while ((flags & BPF_LINFO_LINE_TRIM) && isspace(**linep))
 			*linep += 1;
 	}
 
@@ -3498,7 +3499,8 @@ int bpf_prog_get_file_line(struct bpf_prog *prog, unsigned long ip, const char *
 	if (idx == -1)
 		return -ENOENT;
 
-	bpf_get_linfo_file_line(btf, &linfo[idx], filep, linep, nump);
+	bpf_get_linfo_file_line(btf, &linfo[idx], filep, linep, nump,
+				BPF_LINFO_LINE_TRIM);
 	return 0;
 }
 
diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
index a18fd5aa395d..534a7bc58781 100644
--- a/kernel/bpf/diagnostics.c
+++ b/kernel/bpf/diagnostics.c
@@ -1,12 +1,16 @@
 // SPDX-License-Identifier: GPL-2.0-only
 // Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
 
+#include <linux/bpf.h>
 #include <linux/bpf_verifier.h>
+#include <linux/btf.h>
 #include <linux/ctype.h>
+#include <linux/kernel.h>
 #include <linux/slab.h>
 #include <linux/stdarg.h>
 #include <linux/string.h>
 
+#include "disasm.h"
 #include "diagnostics.h"
 
 #define BPF_DIAG_CATEGORY_MEMORY_SAFETY "Memory Safety"
@@ -21,12 +25,147 @@
 
 #define BPF_DIAG_TEXT_WIDTH 100
 #define BPF_DIAG_TEXT_INDENT "  "
+#define BPF_DIAG_MSG_LEN 512
+#define BPF_DIAG_SOURCE_CONTEXT 2
+#define BPF_DIAG_SOURCE_LINE_CNT (1 + BPF_DIAG_SOURCE_CONTEXT * 2)
+#define BPF_DIAG_INSN_CONTEXT 2
+#define BPF_DIAG_INSN_CNT (1 + BPF_DIAG_INSN_CONTEXT * 2)
+#define BPF_DIAG_COLUMN_GAP 3
+#define BPF_DIAG_SOURCE_LANE_WIDTH 88
+#define BPF_DIAG_TAB_WIDTH 8
+#define BPF_DIAG_REG_DESC_LEN 512
+#define BPF_DIAG_REG_TMP_LEN 192
+#define BPF_DIAG_SCRATCH_STR_CNT 3
+#define BPF_DIAG_SCRATCH_STR_LEN 256
+#define BPF_DIAG_TEXT_LEN 160
+
+struct bpf_diag_source {
+	const char *file;
+	const char *line;
+	u32 file_name_off;
+	int line_num;
+	int line_col;
+};
+
+struct bpf_diag_source_line {
+	const char *line;
+	int line_num;
+};
+
+struct bpf_diag_insn {
+	char text[BPF_DIAG_TEXT_LEN];
+	int idx;
+	bool valid;
+};
+
+struct bpf_diag_insn_buf {
+	char *buf;
+	size_t size;
+	size_t len;
+};
+
+struct bpf_diag_insn_ctx {
+	struct bpf_verifier_env *env;
+	struct bpf_diag_insn_buf buf;
+};
+
+struct bpf_diag_scratch {
+	char str[BPF_DIAG_SCRATCH_STR_CNT][BPF_DIAG_SCRATCH_STR_LEN];
+	struct bpf_diag_source source;
+	struct bpf_diag_source_line source_lines[BPF_DIAG_SOURCE_LINE_CNT];
+	struct bpf_diag_insn insns[BPF_DIAG_INSN_CNT];
+};
+
+struct bpf_diag {
+	struct bpf_diag_scratch scratch;
+};
 
 bool bpf_diag_enabled(const struct bpf_verifier_env *env)
 {
 	return env->log.level & BPF_LOG_LEVEL;
 }
 
+static struct bpf_diag *bpf_diag_env(struct bpf_verifier_env *env)
+{
+	if (!bpf_diag_enabled(env))
+		return NULL;
+	if (env->diag)
+		return env->diag;
+
+	env->diag = kzalloc_obj(struct bpf_diag, GFP_KERNEL_ACCOUNT);
+	return env->diag;
+}
+
+static struct bpf_diag_scratch *bpf_diag_scratch(struct bpf_verifier_env *env)
+{
+	struct bpf_diag *diag = bpf_diag_env(env);
+
+	return diag ? &diag->scratch : NULL;
+}
+
+char *bpf_diag_scratch_buf(struct bpf_verifier_env *env,
+			   unsigned int slot, size_t *size)
+{
+	struct bpf_diag_scratch *scratch = bpf_diag_scratch(env);
+	char *buf = NULL;
+	size_t buf_size = 0;
+
+	if (!scratch)
+		goto out;
+
+	if ((unsigned int)slot >= BPF_DIAG_SCRATCH_STR_CNT)
+		goto out;
+
+	buf = scratch->str[slot];
+	buf_size = sizeof(scratch->str[slot]);
+
+out:
+	if (size)
+		*size = buf_size;
+	return buf;
+}
+
+const char *bpf_diag_scratch_strcpy(struct bpf_verifier_env *env,
+				    unsigned int slot,
+				    const char *str)
+{
+	size_t size;
+	char *buf = bpf_diag_scratch_buf(env, slot, &size);
+
+	if (!buf)
+		return "";
+	strscpy(buf, str ?: "", size);
+	return buf;
+}
+
+const char *bpf_diag_scratch_printf(struct bpf_verifier_env *env,
+				    unsigned int slot,
+				    const char *fmt, ...)
+{
+	size_t size;
+	va_list args;
+	char *buf = bpf_diag_scratch_buf(env, slot, &size);
+
+	if (!buf)
+		return "";
+
+	va_start(args, fmt);
+	vscnprintf(buf, size, fmt, args);
+	va_end(args);
+	return buf;
+}
+
+void bpf_diag_free(struct bpf_verifier_env *env)
+{
+	struct bpf_diag *diag = env->diag;
+
+	if (!diag)
+		return;
+
+	kfree(diag);
+	env->diag = NULL;
+}
+
 static void bpf_diag_log(struct bpf_verifier_env *env, const char *fmt, ...)
 {
 	va_list args;
@@ -38,7 +177,6 @@ static void bpf_diag_log(struct bpf_verifier_env *env, const char *fmt, ...)
 	bpf_verifier_vlog(&env->log, fmt, args);
 	va_end(args);
 }
-
 static void bpf_diag_print_wrapped_prefixed(struct bpf_verifier_env *env,
 					    const char *first_prefix,
 					    const char *next_prefix,
@@ -103,6 +241,287 @@ static void bpf_diag_vprint_indented(struct bpf_verifier_env *env,
 	kfree(buf);
 }
 
+static int bpf_diag_line_width(unsigned int line)
+{
+	int width = 1;
+
+	while (line >= 10) {
+		line /= 10;
+		width++;
+	}
+
+	return width;
+}
+
+static const char *bpf_diag_func_name(struct bpf_verifier_env *env, u32 insn_idx)
+{
+	const struct bpf_subprog_info *subprog;
+	const struct bpf_func_info *info;
+	const struct btf_type *type;
+	int subprogno;
+
+	subprog = bpf_find_containing_subprog(env, insn_idx);
+	if (!subprog)
+		return NULL;
+	if (subprog->name && *subprog->name)
+		return subprog->name;
+
+	if (!env->prog->aux->func_info || !env->prog->aux->btf)
+		return NULL;
+
+	subprogno = subprog - env->subprog_info;
+	if (subprogno < 0 || subprogno >= env->prog->aux->func_info_cnt)
+		return NULL;
+
+	info = &env->prog->aux->func_info[subprogno];
+	type = btf_type_by_id(env->prog->aux->btf, info->type_id);
+	if (!type)
+		return NULL;
+
+	return btf_name_by_offset(env->prog->aux->btf, type->name_off);
+}
+
+static bool bpf_diag_fill_source(struct bpf_verifier_env *env,
+				 const struct bpf_line_info *linfo,
+				 struct bpf_diag_source *src)
+{
+	const char *file, *line;
+	int line_num;
+
+	if (!env->prog->aux->btf)
+		return false;
+
+	bpf_get_linfo_file_line(env->prog->aux->btf, linfo, &file, &line,
+				&line_num, 0);
+	if (!file || !*file || !line || !*line)
+		return false;
+
+	src->file = file;
+	src->line = line;
+	src->file_name_off = linfo->file_name_off;
+	src->line_num = line_num;
+	src->line_col = BPF_LINE_INFO_LINE_COL(linfo->line_col);
+	return true;
+}
+
+static bool bpf_diag_get_source(struct bpf_verifier_env *env, u32 insn_idx,
+				struct bpf_diag_source *src)
+{
+	const struct bpf_line_info *linfo;
+
+	linfo = bpf_find_linfo(env->prog, insn_idx);
+	if (!linfo)
+		return false;
+
+	return bpf_diag_fill_source(env, linfo, src);
+}
+
+static void bpf_diag_fill_source_lines(struct bpf_verifier_env *env,
+				       const struct bpf_diag_source *src,
+				       int start_line, int end_line,
+				       struct bpf_diag_source_line *lines)
+{
+	const struct bpf_line_info *linfo = env->prog->aux->linfo;
+	struct btf *btf = env->prog->aux->btf;
+	u32 i;
+
+	for (i = 0; i < BPF_DIAG_SOURCE_LINE_CNT; i++)
+		lines[i].line_num = start_line + i;
+
+	if (!btf || !env->prog->aux->nr_linfo)
+		return;
+
+	for (i = 0; i < env->prog->aux->nr_linfo; i++) {
+		const char *file, *line;
+		int line_num, idx;
+
+		if (linfo[i].file_name_off != src->file_name_off)
+			continue;
+
+		bpf_get_linfo_file_line(btf, &linfo[i], &file, &line,
+					&line_num, 0);
+		if (line_num < start_line || line_num > end_line)
+			continue;
+
+		idx = line_num - start_line;
+		if (lines[idx].line)
+			continue;
+		if (line && *line)
+			lines[idx].line = line;
+	}
+}
+
+static int bpf_diag_line_indent(const char *line)
+{
+	int indent = 0;
+
+	while (*line == ' ' || *line == '\t') {
+		if (*line == '\t')
+			indent = round_up(indent + 1, BPF_DIAG_TAB_WIDTH);
+		else
+			indent++;
+		line++;
+	}
+
+	return indent;
+}
+
+static void bpf_diag_insn_print(void *private_data, const char *fmt, ...)
+{
+	struct bpf_diag_insn_ctx *ctx = private_data;
+	struct bpf_diag_insn_buf *buf = &ctx->buf;
+	va_list args;
+
+	if (buf->len >= buf->size)
+		return;
+
+	va_start(args, fmt);
+	buf->len += vscnprintf(buf->buf + buf->len, buf->size - buf->len,
+			       fmt, args);
+	va_end(args);
+}
+
+static const char *bpf_diag_disasm_kfunc_name(void *private_data,
+					      const struct bpf_insn *insn)
+{
+	struct bpf_diag_insn_ctx *ctx = private_data;
+
+	return bpf_disasm_kfunc_name(ctx->env, insn);
+}
+
+static void bpf_diag_format_insn(struct bpf_verifier_env *env, int insn_idx,
+				 struct bpf_diag_insn *diag_insn)
+{
+	struct bpf_insn *insn;
+	struct bpf_diag_insn_ctx ctx = {
+		.env = env,
+		.buf = {
+			.buf = diag_insn->text,
+			.size = sizeof(diag_insn->text),
+		},
+	};
+	const struct bpf_insn_cbs cbs = {
+		.cb_call = bpf_diag_disasm_kfunc_name,
+		.cb_print = bpf_diag_insn_print,
+		.private_data = &ctx,
+	};
+
+	diag_insn->idx = insn_idx;
+	diag_insn->valid = false;
+	diag_insn->text[0] = '\0';
+
+	if (insn_idx < 0 || insn_idx >= env->prog->len)
+		return;
+
+	if (insn_idx > 0 && bpf_is_ldimm64(&env->prog->insnsi[insn_idx - 1]))
+		return;
+
+	insn = &env->prog->insnsi[insn_idx];
+	if (bpf_is_ldimm64(insn) && insn_idx + 1 >= env->prog->len)
+		return;
+
+	print_bpf_insn(&cbs, insn, env->allow_ptr_leaks);
+	while (ctx.buf.len && diag_insn->text[ctx.buf.len - 1] == '\n')
+		diag_insn->text[--ctx.buf.len] = '\0';
+
+	diag_insn->valid = true;
+}
+
+static void bpf_diag_format_source_text(char *buf, size_t size,
+					const char *line, int width)
+{
+	int col = 0, len = 0;
+
+	if (!size)
+		return;
+	if (width <= 0) {
+		buf[0] = '\0';
+		return;
+	}
+
+	line = line ?: "...";
+	while (*line && col < width && len + 1 < size) {
+		if (*line == '\t') {
+			int next = round_up(col + 1, BPF_DIAG_TAB_WIDTH);
+
+			while (col < next && col < width && len + 1 < size) {
+				buf[len++] = ' ';
+				col++;
+			}
+			line++;
+			continue;
+		}
+
+		buf[len++] = *line++;
+		col++;
+	}
+
+	if (*line) {
+		int ellipsis_len = min(3, width);
+
+		while (len > 0 && col > width - ellipsis_len) {
+			len--;
+			col--;
+		}
+		while (ellipsis_len-- && len + 1 < size) {
+			buf[len++] = '.';
+			col++;
+		}
+	}
+
+	buf[len] = '\0';
+}
+
+static void bpf_diag_format_source_lane(char *buf, size_t size,
+					const char *source_prefix,
+					int source_line_width,
+					int line_num, const char *line)
+{
+	int len, text_width;
+
+	if (line_num <= 0) {
+		buf[0] = '\0';
+		return;
+	}
+
+	len = scnprintf(buf, size, "%s%*d | ",
+			source_prefix, source_line_width, line_num);
+	if (len >= (int)size)
+		return;
+
+	text_width = BPF_DIAG_SOURCE_LANE_WIDTH - len;
+	bpf_diag_format_source_text(buf + len, size - len, line, text_width);
+}
+
+static void bpf_diag_print_source_insn_line(struct bpf_verifier_env *env,
+					    const char *source_prefix,
+					    int source_line_width,
+					    const struct bpf_diag_source_line *line,
+					    const struct bpf_diag_insn *diag_insn,
+					    int focus_insn_idx,
+					    int insn_width)
+{
+	size_t source_lane_size;
+	char *source_lane;
+
+	source_lane = bpf_diag_scratch_buf(env, 0,
+					   &source_lane_size);
+	if (!source_lane)
+		return;
+
+	bpf_diag_format_source_lane(source_lane, source_lane_size,
+				    source_prefix, source_line_width,
+				    line->line_num, line->line);
+
+	bpf_diag_log(env, "  %-*s%*s", BPF_DIAG_SOURCE_LANE_WIDTH,
+		     source_lane, BPF_DIAG_COLUMN_GAP, "");
+	if (diag_insn->valid)
+		bpf_diag_log(env, "%s%*d | %s",
+			     diag_insn->idx == focus_insn_idx ? ">>> " : "    ",
+			     insn_width, diag_insn->idx, diag_insn->text);
+	bpf_diag_log(env, "\n");
+}
+
 void bpf_diag_report_header(struct bpf_verifier_env *env,
 			    const char *category, const char *problem)
 {
@@ -168,3 +587,120 @@ static void bpf_diag_report_suggestion(struct bpf_verifier_env *env,
 	va_end(args);
 	bpf_diag_log(env, "\n");
 }
+
+static void bpf_diag_print_source_annotation(struct bpf_verifier_env *env,
+					     int line_width, int indent,
+					     const char *label,
+					     const char *msg)
+{
+	size_t first_prefix_size, next_prefix_size;
+	char *first_prefix, *next_prefix;
+	char *text;
+
+	first_prefix = bpf_diag_scratch_buf(env, 0,
+					    &first_prefix_size);
+	next_prefix = bpf_diag_scratch_buf(env,
+					   1,
+					   &next_prefix_size);
+	if (!first_prefix || !next_prefix)
+		return;
+
+	indent = min_t(int, indent,
+		       max_t(int, 0, BPF_DIAG_SOURCE_LANE_WIDTH -
+			     line_width - 8));
+	text = kasprintf(GFP_KERNEL_ACCOUNT, "%s: %s", label, msg);
+	if (!text) {
+		bpf_diag_log(env, "  %*s | %*s^-- %s\n",
+			     line_width + 4, "", indent, "", label);
+		return;
+	}
+
+	scnprintf(first_prefix, first_prefix_size, "  %*s | %*s^-- ",
+		  line_width + 4, "", indent, "");
+	scnprintf(next_prefix, next_prefix_size, "  %*s | %*s    ",
+		  line_width + 4, "", indent, "");
+
+	bpf_diag_print_wrapped_prefixed(env, first_prefix, next_prefix, text);
+	kfree(text);
+}
+
+void bpf_diag_report_source(struct bpf_verifier_env *env, u32 insn_idx,
+			    const char *label, const char *fmt, ...)
+{
+	struct bpf_diag_scratch *scratch;
+	struct bpf_diag_source_line *source_lines;
+	struct bpf_diag_insn *diag_insn;
+	struct bpf_diag_source *src;
+	char *msg;
+	const char *func;
+	int start_line, end_line, width, indent, insn_width, i;
+	va_list args;
+
+	if (!bpf_diag_enabled(env))
+		return;
+
+	va_start(args, fmt);
+	msg = kvasprintf(GFP_KERNEL_ACCOUNT, fmt, args);
+	va_end(args);
+	if (!msg)
+		msg = kstrdup("<failed to allocate diagnostic text>",
+			      GFP_KERNEL_ACCOUNT);
+	if (!msg)
+		return;
+
+	label = label ?: "note";
+	scratch = bpf_diag_scratch(env);
+	if (!scratch)
+		goto out_free_msg;
+
+	src = &scratch->source;
+	source_lines = scratch->source_lines;
+	diag_insn = scratch->insns;
+	memset(source_lines, 0, sizeof(scratch->source_lines));
+	memset(diag_insn, 0, sizeof(scratch->insns));
+
+	if (!bpf_diag_get_source(env, insn_idx, src)) {
+		bpf_diag_log(env, "  insn %u\n", insn_idx);
+		bpf_diag_print_source_annotation(env, 0, 0, label, msg);
+		goto out_free_msg;
+	}
+
+	func = bpf_diag_func_name(env, insn_idx);
+	if (func && *func)
+		bpf_diag_log(env, "  %s @ %s:%d:%d\n", func, src->file,
+			     src->line_num, src->line_col);
+	else
+		bpf_diag_log(env, "  %s:%d:%d\n", src->file, src->line_num,
+			     src->line_col);
+
+	start_line = src->line_num - BPF_DIAG_SOURCE_CONTEXT;
+	end_line = src->line_num + BPF_DIAG_SOURCE_CONTEXT;
+	width = bpf_diag_line_width(end_line);
+	indent = bpf_diag_line_indent(src->line);
+	insn_width = bpf_diag_line_width(env->prog->len ? env->prog->len - 1 : 0);
+	bpf_diag_fill_source_lines(env, src, start_line, end_line,
+				   source_lines);
+
+	for (i = 0; i < BPF_DIAG_INSN_CNT; i++) {
+		int row = i - BPF_DIAG_INSN_CONTEXT;
+
+		bpf_diag_format_insn(env, insn_idx + row, &diag_insn[i]);
+	}
+
+	for (i = 0; i < BPF_DIAG_SOURCE_LINE_CNT; i++) {
+		const char *source_prefix;
+
+		source_prefix = source_lines[i].line_num == src->line_num ?
+				">>> " : "    ";
+		bpf_diag_print_source_insn_line(env, source_prefix, width,
+						&source_lines[i],
+						&diag_insn[i],
+						insn_idx, insn_width);
+		if (source_lines[i].line_num == src->line_num)
+			bpf_diag_print_source_annotation(env, width, indent,
+							 label, msg);
+	}
+
+out_free_msg:
+	kfree(msg);
+}
diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
index 5fb3271530a1..7f1380508789 100644
--- a/kernel/bpf/diagnostics.h
+++ b/kernel/bpf/diagnostics.h
@@ -10,7 +10,20 @@
 struct bpf_verifier_env;
 
 bool bpf_diag_enabled(const struct bpf_verifier_env *env);
+char *bpf_diag_scratch_buf(struct bpf_verifier_env *env,
+			   unsigned int slot, size_t *size);
+const char *bpf_diag_scratch_strcpy(struct bpf_verifier_env *env,
+				    unsigned int slot,
+				    const char *str);
+const char *bpf_diag_scratch_printf(struct bpf_verifier_env *env,
+				    unsigned int slot,
+				    const char *fmt, ...)
+	__printf(3, 4);
+void bpf_diag_free(struct bpf_verifier_env *env);
 void bpf_diag_report_header(struct bpf_verifier_env *env,
 			    const char *category, const char *problem);
+void bpf_diag_report_source(struct bpf_verifier_env *env, u32 insn_idx,
+			    const char *label, const char *fmt, ...)
+	__printf(4, 5);
 
 #endif /* __BPF_DIAGNOSTICS_H */
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 2abc79dbf281..e81fdb0e22ae 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -32,6 +32,7 @@
 #include <linux/trace_events.h>
 #include <linux/kallsyms.h>
 
+#include "diagnostics.h"
 #include "disasm.h"
 
 static const struct bpf_verifier_ops * const bpf_verifier_ops[] = {
@@ -3227,7 +3228,7 @@ static void linked_regs_unpack(u64 val, struct linked_regs *s)
 	}
 }
 
-static const char *disasm_kfunc_name(void *data, const struct bpf_insn *insn)
+const char *bpf_disasm_kfunc_name(void *data, const struct bpf_insn *insn)
 {
 	const struct btf_type *func;
 	struct btf *desc_btf;
@@ -3246,7 +3247,7 @@ static const char *disasm_kfunc_name(void *data, const struct bpf_insn *insn)
 void bpf_verbose_insn(struct bpf_verifier_env *env, struct bpf_insn *insn)
 {
 	const struct bpf_insn_cbs cbs = {
-		.cb_call	= disasm_kfunc_name,
+		.cb_call	= bpf_disasm_kfunc_name,
 		.cb_print	= verbose,
 		.private_data	= env,
 	};
@@ -19995,6 +19996,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
 	kvfree(env->scc_info);
 	kvfree(env->succ);
 	kvfree(env->gotox_tmp_buf);
+	bpf_diag_free(env);
 	kvfree(env);
 	return ret;
 }
-- 
2.53.0


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

* [PATCH bpf-next v2 03/17] bpf: Add verifier diagnostic event log
  2026-06-19 20:59 [PATCH bpf-next v2 00/17] Redesign Verification Errors Kumar Kartikeya Dwivedi
  2026-06-19 20:59 ` [PATCH bpf-next v2 01/17] bpf: Add verifier diagnostics report helpers Kumar Kartikeya Dwivedi
  2026-06-19 20:59 ` [PATCH bpf-next v2 02/17] bpf: Add source and instruction diagnostic context Kumar Kartikeya Dwivedi
@ 2026-06-19 20:59 ` Kumar Kartikeya Dwivedi
  2026-06-19 21:46   ` bot+bpf-ci
  2026-06-19 20:59 ` [PATCH bpf-next v2 04/17] bpf: Prune verifier diagnostics on backtracking Kumar Kartikeya Dwivedi
                   ` (13 subsequent siblings)
  16 siblings, 1 reply; 34+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-06-19 20:59 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team

Add an environment-owned diagnostic history for verifier reports. Event
payloads keep the user-facing branch history shape, but storage lives in
bpf_verifier_env instead of verifier states so explored states do not copy
diagnostic buffers.

Grow the event array as entries are appended and keep saved positions as array
indices. Later patches can truncate back to those positions when verifier
search backtracks. Add the branch event renderer now, while leaving branch
recording to the follow-up patch that wires active-path pruning.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 kernel/bpf/diagnostics.c | 182 +++++++++++++++++++++++++++++++++------
 kernel/bpf/diagnostics.h |  19 ++++
 2 files changed, 174 insertions(+), 27 deletions(-)

diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
index 534a7bc58781..fa5a25b314a0 100644
--- a/kernel/bpf/diagnostics.c
+++ b/kernel/bpf/diagnostics.c
@@ -69,6 +69,12 @@ struct bpf_diag_insn_ctx {
 	struct bpf_diag_insn_buf buf;
 };
 
+struct bpf_diag_log {
+	struct bpf_diag_history_event *events;
+	u32 cnt;
+	u32 cap;
+};
+
 struct bpf_diag_scratch {
 	char str[BPF_DIAG_SCRATCH_STR_CNT][BPF_DIAG_SCRATCH_STR_LEN];
 	struct bpf_diag_source source;
@@ -77,6 +83,7 @@ struct bpf_diag_scratch {
 };
 
 struct bpf_diag {
+	struct bpf_diag_log log;
 	struct bpf_diag_scratch scratch;
 };
 
@@ -155,6 +162,51 @@ const char *bpf_diag_scratch_printf(struct bpf_verifier_env *env,
 	return buf;
 }
 
+static void bpf_diag_write(struct bpf_verifier_env *env, const char *fmt, ...)
+{
+	va_list args;
+
+	if (!bpf_diag_enabled(env))
+		return;
+
+	va_start(args, fmt);
+	bpf_verifier_vlog(&env->log, fmt, args);
+	va_end(args);
+}
+
+static struct bpf_diag_log *bpf_diag_event_log(struct bpf_verifier_env *env)
+{
+	struct bpf_diag *diag = bpf_diag_env(env);
+
+	return diag ? &diag->log : NULL;
+}
+
+u64 bpf_diag_event_log_pos(struct bpf_verifier_env *env)
+{
+	struct bpf_diag *diag = bpf_diag_env(env);
+
+	if (!diag)
+		return 0;
+	return diag->log.cnt;
+}
+
+void bpf_diag_event_log_reset(struct bpf_verifier_env *env, u64 pos)
+{
+	struct bpf_diag *diag = env->diag;
+	struct bpf_diag_log *log;
+	u64 end;
+
+	if (!diag)
+		return;
+
+	log = &diag->log;
+	end = log->cnt;
+	if (WARN_ON_ONCE(pos > end))
+		pos = end;
+
+	log->cnt = pos;
+}
+
 void bpf_diag_free(struct bpf_verifier_env *env)
 {
 	struct bpf_diag *diag = env->diag;
@@ -162,21 +214,46 @@ void bpf_diag_free(struct bpf_verifier_env *env)
 	if (!diag)
 		return;
 
+	kfree(diag->log.events);
 	kfree(diag);
 	env->diag = NULL;
 }
 
-static void bpf_diag_log(struct bpf_verifier_env *env, const char *fmt, ...)
+static const struct bpf_diag_history_event *
+bpf_diag_history_event(const struct bpf_diag_log *log, u32 idx)
 {
-	va_list args;
+	return &log->events[idx];
+}
 
-	if (!bpf_diag_enabled(env))
+static void bpf_diag_append_history(struct bpf_verifier_env *env,
+				    const struct bpf_diag_history_event *event)
+{
+	struct bpf_diag_history_event *events;
+	struct bpf_diag_log *log;
+	u32 cap;
+
+	log = bpf_diag_event_log(env);
+	if (!log)
 		return;
 
-	va_start(args, fmt);
-	bpf_verifier_vlog(&env->log, fmt, args);
-	va_end(args);
+	if (log->cnt < log->cap) {
+		log->events[log->cnt++] = *event;
+		return;
+	}
+
+	cap = log->cap ? log->cap * 2 : 64;
+	if (cap < log->cap)
+		return;
+
+	events = krealloc_array(log->events, cap, sizeof(*events),
+				GFP_KERNEL_ACCOUNT);
+	if (!events)
+		return;
+	log->events = events;
+	log->cap = cap;
+	log->events[log->cnt++] = *event;
 }
+
 static void bpf_diag_print_wrapped_prefixed(struct bpf_verifier_env *env,
 					    const char *first_prefix,
 					    const char *next_prefix,
@@ -203,7 +280,7 @@ static void bpf_diag_print_wrapped_prefixed(struct bpf_verifier_env *env,
 		    last_space > 0)
 			len = last_space;
 
-		bpf_diag_log(env, "%s%.*s\n", prefix, len, line);
+		bpf_diag_write(env, "%s%.*s\n", prefix, len, line);
 
 		text = line + len;
 		while (*text == ' ')
@@ -232,8 +309,8 @@ static void bpf_diag_vprint_indented(struct bpf_verifier_env *env,
 
 	buf = kvasprintf(GFP_KERNEL_ACCOUNT, fmt, args);
 	if (!buf) {
-		bpf_diag_log(env, "%s<failed to allocate diagnostic text>\n",
-			     BPF_DIAG_TEXT_INDENT);
+		bpf_diag_write(env, "%s<failed to allocate diagnostic text>\n",
+			       BPF_DIAG_TEXT_INDENT);
 		return;
 	}
 
@@ -513,13 +590,14 @@ static void bpf_diag_print_source_insn_line(struct bpf_verifier_env *env,
 				    source_prefix, source_line_width,
 				    line->line_num, line->line);
 
-	bpf_diag_log(env, "  %-*s%*s", BPF_DIAG_SOURCE_LANE_WIDTH,
-		     source_lane, BPF_DIAG_COLUMN_GAP, "");
+	bpf_diag_write(env, "  %-*s%*s", BPF_DIAG_SOURCE_LANE_WIDTH,
+		       source_lane, BPF_DIAG_COLUMN_GAP, "");
 	if (diag_insn->valid)
-		bpf_diag_log(env, "%s%*d | %s",
-			     diag_insn->idx == focus_insn_idx ? ">>> " : "    ",
-			     insn_width, diag_insn->idx, diag_insn->text);
-	bpf_diag_log(env, "\n");
+		bpf_diag_write(env, "%s%*d | %s",
+			       diag_insn->idx == focus_insn_idx ?
+			       ">>> " : "    ", insn_width, diag_insn->idx,
+			       diag_insn->text);
+	bpf_diag_write(env, "\n");
 }
 
 void bpf_diag_report_header(struct bpf_verifier_env *env,
@@ -534,13 +612,13 @@ void bpf_diag_report_header(struct bpf_verifier_env *env,
 	problem = problem ?: "";
 
 	if (!problem[0]) {
-		bpf_diag_log(env, "\nVerification failed: %s\n", category);
+		bpf_diag_write(env, "\nVerification failed: %s\n", category);
 		return;
 	}
 
 	first = toupper(problem[0]);
-	bpf_diag_log(env, "\nVerification failed: %s: %c%s\n", category,
-		     first, problem + 1);
+	bpf_diag_write(env, "\nVerification failed: %s: %c%s\n", category,
+		       first, problem + 1);
 }
 
 static void bpf_diag_report_reason(struct bpf_verifier_env *env,
@@ -554,7 +632,7 @@ static void bpf_diag_report_section(struct bpf_verifier_env *env,
 	if (!bpf_diag_enabled(env))
 		return;
 
-	bpf_diag_log(env, "\n%s:\n", title);
+	bpf_diag_write(env, "\n%s:\n", title);
 }
 
 static void bpf_diag_report_reason(struct bpf_verifier_env *env,
@@ -585,7 +663,7 @@ static void bpf_diag_report_suggestion(struct bpf_verifier_env *env,
 	va_start(args, fmt);
 	bpf_diag_vprint_indented(env, fmt, args);
 	va_end(args);
-	bpf_diag_log(env, "\n");
+	bpf_diag_write(env, "\n");
 }
 
 static void bpf_diag_print_source_annotation(struct bpf_verifier_env *env,
@@ -610,8 +688,8 @@ static void bpf_diag_print_source_annotation(struct bpf_verifier_env *env,
 			     line_width - 8));
 	text = kasprintf(GFP_KERNEL_ACCOUNT, "%s: %s", label, msg);
 	if (!text) {
-		bpf_diag_log(env, "  %*s | %*s^-- %s\n",
-			     line_width + 4, "", indent, "", label);
+		bpf_diag_write(env, "  %*s | %*s^-- %s\n",
+			       line_width + 4, "", indent, "", label);
 		return;
 	}
 
@@ -660,18 +738,18 @@ void bpf_diag_report_source(struct bpf_verifier_env *env, u32 insn_idx,
 	memset(diag_insn, 0, sizeof(scratch->insns));
 
 	if (!bpf_diag_get_source(env, insn_idx, src)) {
-		bpf_diag_log(env, "  insn %u\n", insn_idx);
+		bpf_diag_write(env, "  insn %u\n", insn_idx);
 		bpf_diag_print_source_annotation(env, 0, 0, label, msg);
 		goto out_free_msg;
 	}
 
 	func = bpf_diag_func_name(env, insn_idx);
 	if (func && *func)
-		bpf_diag_log(env, "  %s @ %s:%d:%d\n", func, src->file,
-			     src->line_num, src->line_col);
+		bpf_diag_write(env, "  %s @ %s:%d:%d\n", func, src->file,
+			       src->line_num, src->line_col);
 	else
-		bpf_diag_log(env, "  %s:%d:%d\n", src->file, src->line_num,
-			     src->line_col);
+		bpf_diag_write(env, "  %s:%d:%d\n", src->file, src->line_num,
+			       src->line_col);
 
 	start_line = src->line_num - BPF_DIAG_SOURCE_CONTEXT;
 	end_line = src->line_num + BPF_DIAG_SOURCE_CONTEXT;
@@ -704,3 +782,53 @@ void bpf_diag_report_source(struct bpf_verifier_env *env, u32 insn_idx,
 out_free_msg:
 	kfree(msg);
 }
+
+void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx,
+			    bool cond_true)
+{
+	struct bpf_diag_history_event event = {
+		.insn_idx = insn_idx,
+		.kind = BPF_DIAG_HISTORY_BRANCH,
+		.branch.cond_true = cond_true,
+	};
+
+	bpf_diag_append_history(env, &event);
+}
+
+void bpf_diag_print_history(struct bpf_verifier_env *env)
+{
+	const struct bpf_diag_history_event *event;
+	const struct bpf_diag_log *log;
+	bool printed = false;
+	u32 i;
+
+	bpf_diag_report_section(env, "Causal path");
+
+	if (!env->diag) {
+		bpf_diag_write(env, "  no recorded diagnostic events on this path\n");
+		return;
+	}
+	log = &env->diag->log;
+
+	for (i = 0; i < log->cnt; i++) {
+		event = bpf_diag_history_event(log, i);
+
+		switch (event->kind) {
+		case BPF_DIAG_HISTORY_BRANCH:
+			bpf_diag_report_source(env, event->insn_idx, "branch",
+					       "explored as %s, goto %s",
+					       event->branch.cond_true ? "true" :
+					       "false",
+					       event->branch.cond_true ? "followed" :
+					       "not followed");
+			printed = true;
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (!printed)
+		bpf_diag_write(env,
+			       "  no retained diagnostic events on this path\n");
+}
diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
index 7f1380508789..4bc44be757c4 100644
--- a/kernel/bpf/diagnostics.h
+++ b/kernel/bpf/diagnostics.h
@@ -9,6 +9,20 @@
 
 struct bpf_verifier_env;
 
+struct bpf_diag_history_event {
+	u32 insn_idx;
+	u8 kind;
+	union {
+		struct {
+			bool cond_true;
+		} branch;
+	};
+};
+
+enum bpf_diag_history_kind {
+	BPF_DIAG_HISTORY_BRANCH,
+};
+
 bool bpf_diag_enabled(const struct bpf_verifier_env *env);
 char *bpf_diag_scratch_buf(struct bpf_verifier_env *env,
 			   unsigned int slot, size_t *size);
@@ -19,11 +33,16 @@ const char *bpf_diag_scratch_printf(struct bpf_verifier_env *env,
 				    unsigned int slot,
 				    const char *fmt, ...)
 	__printf(3, 4);
+u64 bpf_diag_event_log_pos(struct bpf_verifier_env *env);
+void bpf_diag_event_log_reset(struct bpf_verifier_env *env, u64 pos);
 void bpf_diag_free(struct bpf_verifier_env *env);
 void bpf_diag_report_header(struct bpf_verifier_env *env,
 			    const char *category, const char *problem);
 void bpf_diag_report_source(struct bpf_verifier_env *env, u32 insn_idx,
 			    const char *label, const char *fmt, ...)
 	__printf(4, 5);
+void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx,
+			    bool cond_true);
+void bpf_diag_print_history(struct bpf_verifier_env *env);
 
 #endif /* __BPF_DIAGNOSTICS_H */
-- 
2.53.0


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

* [PATCH bpf-next v2 04/17] bpf: Prune verifier diagnostics on backtracking
  2026-06-19 20:59 [PATCH bpf-next v2 00/17] Redesign Verification Errors Kumar Kartikeya Dwivedi
                   ` (2 preceding siblings ...)
  2026-06-19 20:59 ` [PATCH bpf-next v2 03/17] bpf: Add verifier diagnostic event log Kumar Kartikeya Dwivedi
@ 2026-06-19 20:59 ` Kumar Kartikeya Dwivedi
  2026-06-19 21:46   ` bot+bpf-ci
  2026-06-19 20:59 ` [PATCH bpf-next v2 05/17] bpf: Track verifier register diagnostic events Kumar Kartikeya Dwivedi
                   ` (12 subsequent siblings)
  16 siblings, 1 reply; 34+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-06-19 20:59 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team

Save the diagnostic event-log position with each verifier stack entry and
reset the environment-owned stream together with the normal verifier log
when a queued state is popped. Also reset the diagnostic stream after
successful subprogram verification even when level-2 logging preserves the
normal verifier log.

Record branch outcomes as true or false. Later reports use those
events to show the path that reached a verifier error. For queued branches,
append the queued outcome before push_stack() so the saved diagnostic
position includes it, then reset back to the active path before continuing.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 kernel/bpf/verifier.c | 44 +++++++++++++++++++++++++++++++++++++------
 1 file changed, 38 insertions(+), 6 deletions(-)

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index e81fdb0e22ae..ca4bba163418 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -192,6 +192,9 @@ struct bpf_verifier_stack_elem {
 	struct bpf_verifier_stack_elem *next;
 	/* length of verifier log at the time this state was pushed on stack */
 	u32 log_pos;
+	u64 diag_log_pos;
+	bool diag_branch_valid;
+	bool diag_branch_cond_true;
 };
 
 #define BPF_COMPLEXITY_LIMIT_JMP_SEQ	8192
@@ -1704,7 +1707,7 @@ void bpf_free_backedges(struct bpf_scc_visit *visit)
 }
 
 static int pop_stack(struct bpf_verifier_env *env, int *prev_insn_idx,
-		     int *insn_idx, bool pop_log)
+		     int *insn_idx, bool pop_log, bool activate_diag_branch)
 {
 	struct bpf_verifier_state *cur = env->cur_state;
 	struct bpf_verifier_stack_elem *elem, *head = env->head;
@@ -1720,6 +1723,10 @@ static int pop_stack(struct bpf_verifier_env *env, int *prev_insn_idx,
 	}
 	if (pop_log)
 		bpf_vlog_reset(&env->log, head->log_pos);
+	bpf_diag_event_log_reset(env, head->diag_log_pos);
+	if (activate_diag_branch && head->diag_branch_valid)
+		bpf_diag_record_branch(env, head->prev_insn_idx,
+				       head->diag_branch_cond_true);
 	if (insn_idx)
 		*insn_idx = head->insn_idx;
 	if (prev_insn_idx)
@@ -1760,6 +1767,7 @@ static struct bpf_verifier_state *push_stack(struct bpf_verifier_env *env,
 	elem->prev_insn_idx = prev_insn_idx;
 	elem->next = env->head;
 	elem->log_pos = env->log.end_pos;
+	elem->diag_log_pos = bpf_diag_event_log_pos(env);
 	env->head = elem;
 	env->stack_size++;
 	err = bpf_copy_verifier_state(&elem->st, cur);
@@ -1786,6 +1794,21 @@ static struct bpf_verifier_state *push_stack(struct bpf_verifier_env *env,
 	return &elem->st;
 }
 
+static struct bpf_verifier_state *
+push_stack_with_branch_diag(struct bpf_verifier_env *env, int insn_idx,
+			    int prev_insn_idx, bool speculative,
+			    bool cond_true)
+{
+	struct bpf_verifier_state *st;
+
+	st = push_stack(env, insn_idx, prev_insn_idx, speculative);
+	if (!IS_ERR(st)) {
+		env->head->diag_branch_valid = true;
+		env->head->diag_branch_cond_true = cond_true;
+	}
+	return st;
+}
+
 static const char *reg_arg_name(struct bpf_verifier_env *env, argno_t argno)
 {
 	char *buf = env->tmp_arg_name;
@@ -2284,6 +2307,7 @@ static struct bpf_verifier_state *push_async_cb(struct bpf_verifier_env *env,
 	elem->prev_insn_idx = prev_insn_idx;
 	elem->next = env->head;
 	elem->log_pos = env->log.end_pos;
+	elem->diag_log_pos = bpf_diag_event_log_pos(env);
 	env->head = elem;
 	env->stack_size++;
 	if (env->stack_size > BPF_COMPLEXITY_LIMIT_JMP_SEQ) {
@@ -16027,6 +16051,7 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
 		}
 		if (env->log.level & BPF_LOG_LEVEL)
 			print_insn_state(env, this_branch, this_branch->curframe);
+		bpf_diag_record_branch(env, *insn_idx, true);
 		*insn_idx += insn->off;
 		return 0;
 	} else if (pred == 0) {
@@ -16042,6 +16067,7 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
 		}
 		if (env->log.level & BPF_LOG_LEVEL)
 			print_insn_state(env, this_branch, this_branch->curframe);
+		bpf_diag_record_branch(env, *insn_idx, false);
 		return 0;
 	}
 
@@ -16060,7 +16086,8 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
 			return err;
 	}
 
-	other_branch = push_stack(env, *insn_idx + insn->off + 1, *insn_idx, false);
+	other_branch = push_stack_with_branch_diag(env, *insn_idx + insn->off + 1,
+						   *insn_idx, false, true);
 	if (IS_ERR(other_branch))
 		return PTR_ERR(other_branch);
 	other_branch_regs = other_branch->frame[other_branch->curframe]->regs;
@@ -16074,6 +16101,7 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
 	other_branch_regs[insn->dst_reg] = env->true_reg1;
 	if (BPF_SRC(insn->code) == BPF_X)
 		other_branch_regs[insn->src_reg] = env->true_reg2;
+	bpf_diag_record_branch(env, *insn_idx, false);
 
 	if (BPF_SRC(insn->code) == BPF_X &&
 	    src_reg->type == SCALAR_VALUE && src_reg->id &&
@@ -17484,7 +17512,7 @@ static int do_check(struct bpf_verifier_env *env)
 			if (err)
 				return err;
 			err = pop_stack(env, &prev_insn_idx, &env->insn_idx,
-					pop_log);
+					pop_log, true);
 			if (err < 0) {
 				if (err != -ENOENT)
 					return err;
@@ -18292,7 +18320,8 @@ static void free_states(struct bpf_verifier_env *env)
 
 	bpf_free_verifier_state(env->cur_state, true);
 	env->cur_state = NULL;
-	while (!pop_stack(env, NULL, NULL, false));
+	while (!pop_stack(env, NULL, NULL, false, false))
+		;
 
 	list_for_each_safe(pos, tmp, &env->free_list) {
 		sl = container_of(pos, struct bpf_verifier_state_list, node);
@@ -18471,8 +18500,11 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
 
 	ret = do_check(env);
 out:
-	if (!ret && pop_log)
-		bpf_vlog_reset(&env->log, 0);
+	if (!ret) {
+		if (pop_log)
+			bpf_vlog_reset(&env->log, 0);
+		bpf_diag_event_log_reset(env, 0);
+	}
 	free_states(env);
 	return ret;
 }
-- 
2.53.0


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

* [PATCH bpf-next v2 05/17] bpf: Track verifier register diagnostic events
  2026-06-19 20:59 [PATCH bpf-next v2 00/17] Redesign Verification Errors Kumar Kartikeya Dwivedi
                   ` (3 preceding siblings ...)
  2026-06-19 20:59 ` [PATCH bpf-next v2 04/17] bpf: Prune verifier diagnostics on backtracking Kumar Kartikeya Dwivedi
@ 2026-06-19 20:59 ` Kumar Kartikeya Dwivedi
  2026-06-19 21:18   ` sashiko-bot
  2026-06-19 23:35   ` Alexei Starovoitov
  2026-06-19 20:59 ` [PATCH bpf-next v2 06/17] bpf: Track verifier reference " Kumar Kartikeya Dwivedi
                   ` (11 subsequent siblings)
  16 siblings, 2 replies; 34+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-06-19 20:59 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team

Record material register and outgoing stack argument changes so diagnostics can
explain how a value reached its current type, bounds, or unreadable state.

Store old and new register types, scalar ranges, tnum value and mask, map and
BTF type identity, and basic operand metadata in the environment-owned
diagnostic event stream.

Record invalidations when packet data moves, references are released, or
borrowed references leave their protected region. Register-scoped history
starts at the latest matching modification and then shows later branch
outcomes.

Also record fixed stack spills and overwrites, and tag register fills from
stack so register-scoped history can follow value flow through spilled stack
slots.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 kernel/bpf/diagnostics.c | 766 ++++++++++++++++++++++++++++++++++++++-
 kernel/bpf/diagnostics.h | 113 +++++-
 kernel/bpf/verifier.c    | 269 +++++++++++++-
 3 files changed, 1137 insertions(+), 11 deletions(-)

diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
index fa5a25b314a0..f51b2860c11d 100644
--- a/kernel/bpf/diagnostics.c
+++ b/kernel/bpf/diagnostics.c
@@ -75,11 +75,24 @@ struct bpf_diag_log {
 	u32 cap;
 };
 
+struct bpf_diag_reg_fmt {
+	char old_buf[BPF_DIAG_REG_DESC_LEN];
+	char new_buf[BPF_DIAG_REG_DESC_LEN];
+	char offset_desc[BPF_DIAG_REG_DESC_LEN];
+	char btf_type[BPF_DIAG_REG_TMP_LEN];
+	char range[BPF_DIAG_REG_TMP_LEN];
+	char smin_buf[32];
+	char smax_buf[32];
+	char umin_buf[32];
+	char umax_buf[32];
+};
+
 struct bpf_diag_scratch {
 	char str[BPF_DIAG_SCRATCH_STR_CNT][BPF_DIAG_SCRATCH_STR_LEN];
 	struct bpf_diag_source source;
 	struct bpf_diag_source_line source_lines[BPF_DIAG_SOURCE_LINE_CNT];
 	struct bpf_diag_insn insns[BPF_DIAG_INSN_CNT];
+	struct bpf_diag_reg_fmt reg_fmt;
 };
 
 struct bpf_diag {
@@ -299,6 +312,47 @@ static void bpf_diag_print_wrapped_text(struct bpf_verifier_env *env,
 					BPF_DIAG_TEXT_INDENT, text);
 }
 
+static void bpf_diag_trim_btf_show_name(char *buf)
+{
+	size_t len = strlen(buf);
+
+	if (len && buf[len - 1] == '{')
+		buf[len - 1] = '\0';
+}
+
+void bpf_diag_format_btf_type(char *buf, size_t size, const struct btf *btf,
+			      u32 type_id)
+{
+	int ret;
+
+	if (!size)
+		return;
+
+	buf[0] = '\0';
+	ret = btf_type_snprintf_show_name(btf, type_id, buf, size);
+	if (ret < 0 || !buf[0]) {
+		scnprintf(buf, size, "BTF type ID %u", type_id);
+		return;
+	}
+
+	bpf_diag_trim_btf_show_name(buf);
+}
+
+const char *bpf_diag_format_btf_type_scratch(struct bpf_verifier_env *env,
+					     unsigned int slot,
+					     const struct btf *btf,
+					     u32 type_id)
+{
+	size_t size;
+	char *buf = bpf_diag_scratch_buf(env, slot, &size);
+
+	if (!buf)
+		return "";
+
+	bpf_diag_format_btf_type(buf, size, btf, type_id);
+	return buf;
+}
+
 static void bpf_diag_vprint_indented(struct bpf_verifier_env *env,
 				     const char *fmt, va_list args)
 {
@@ -795,11 +849,703 @@ void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx,
 	bpf_diag_append_history(env, &event);
 }
 
-void bpf_diag_print_history(struct bpf_verifier_env *env)
+static void bpf_diag_snapshot_one_reg(struct bpf_diag_reg_snapshot *snapshot,
+				      const struct bpf_reg_state *reg)
+{
+	snapshot->type = reg->type;
+	if (base_type(reg->type) == PTR_TO_MAP_VALUE ||
+	    base_type(reg->type) == CONST_PTR_TO_MAP ||
+	    base_type(reg->type) == PTR_TO_MAP_KEY)
+		snapshot->map_ptr = reg->map_ptr;
+	if (base_type(reg->type) == PTR_TO_BTF_ID && reg->btf && reg->btf_id) {
+		snapshot->btf = reg->btf;
+		snapshot->btf_id = reg->btf_id;
+	}
+	snapshot->var_off = reg->var_off;
+	snapshot->r64 = reg->r64;
+}
+
+static void bpf_diag_snapshot_reg(struct bpf_diag_history_event *event,
+				  enum bpf_diag_reg_mod_reason reason,
+				  const struct bpf_reg_state *old_reg,
+				  const struct bpf_reg_state *new_reg)
+{
+	event->reg.reason = reason;
+	bpf_diag_snapshot_one_reg(&event->reg.old, old_reg);
+	bpf_diag_snapshot_one_reg(&event->reg.new, new_reg);
+}
+
+static bool bpf_diag_snapshot_eq(const struct bpf_diag_reg_snapshot *old,
+				 const struct bpf_diag_reg_snapshot *new)
+{
+	return old->type == new->type &&
+	       old->map_ptr == new->map_ptr &&
+	       old->btf == new->btf &&
+	       old->btf_id == new->btf_id &&
+	       old->var_off.value == new->var_off.value &&
+	       old->var_off.mask == new->var_off.mask &&
+	       old->r64.base == new->r64.base &&
+	       old->r64.size == new->r64.size;
+}
+
+static bool bpf_diag_reg_snapshot_eq(const struct bpf_diag_history_event *event)
+{
+	return bpf_diag_snapshot_eq(&event->reg.old, &event->reg.new);
+}
+
+static void bpf_diag_record_reg_mod_reason(struct bpf_verifier_env *env,
+					   u32 insn_idx, u32 frameno,
+					   u8 dst_reg, bool src_valid,
+					   u8 src_reg, u8 opcode,
+					   bool stack_slot_valid,
+					   u32 stack_frameno,
+					   u16 stack_spi,
+					   enum bpf_diag_reg_mod_reason reason,
+					   const struct bpf_reg_state *old_reg,
+					   const struct bpf_reg_state *new_reg)
+{
+	struct bpf_diag_history_event event = {
+		.insn_idx = insn_idx,
+		.kind = BPF_DIAG_HISTORY_REG_MOD,
+		.reg.frameno = frameno,
+		.reg.dst_reg = dst_reg,
+		.reg.src_reg = src_reg,
+		.reg.opcode = opcode,
+		.reg.src_valid = src_valid,
+		.reg.stack_slot_valid = stack_slot_valid,
+		.reg.stack_frameno = stack_frameno,
+		.reg.stack_spi = stack_spi,
+	};
+
+	bpf_diag_snapshot_reg(&event, reason, old_reg, new_reg);
+	if (reason == BPF_DIAG_REG_MOD_WRITE &&
+	    bpf_diag_reg_snapshot_eq(&event))
+		return;
+
+	bpf_diag_append_history(env, &event);
+}
+
+void bpf_diag_record_reg_mod(struct bpf_verifier_env *env, u32 insn_idx,
+			     u32 frameno, u8 dst_reg, bool src_valid,
+			     u8 src_reg, u8 opcode,
+			     const struct bpf_reg_state *old_reg,
+			     const struct bpf_reg_state *new_reg)
+{
+	bpf_diag_record_reg_mod_reason(env, insn_idx, frameno, dst_reg,
+				       src_valid, src_reg, opcode, false, 0, 0,
+				       BPF_DIAG_REG_MOD_WRITE, old_reg,
+				       new_reg);
+}
+
+void bpf_diag_record_reg_stack_fill(struct bpf_verifier_env *env, u32 insn_idx,
+				    u32 frameno, u8 dst_reg, u32 stack_frameno,
+				    u16 stack_spi, bool src_valid, u8 src_reg,
+				    u8 opcode,
+				    const struct bpf_reg_state *old_reg,
+				    const struct bpf_reg_state *new_reg)
+{
+	bpf_diag_record_reg_mod_reason(env, insn_idx, frameno, dst_reg,
+				       src_valid, src_reg, opcode, true,
+				       stack_frameno, stack_spi,
+				       BPF_DIAG_REG_MOD_WRITE, old_reg,
+				       new_reg);
+}
+
+void bpf_diag_record_reg_invalidate(struct bpf_verifier_env *env, u32 insn_idx,
+				    u32 frameno, u8 dst_reg,
+				    enum bpf_diag_reg_mod_reason reason,
+				    const struct bpf_reg_state *old_reg,
+				    const struct bpf_reg_state *new_reg)
+{
+	bpf_diag_record_reg_mod_reason(env, insn_idx, frameno, dst_reg,
+				       false, 0, 0, false, 0, 0, reason, old_reg,
+				       new_reg);
+}
+
+void bpf_diag_record_stack_arg(struct bpf_verifier_env *env, u32 insn_idx,
+			       u32 frameno, u8 slot,
+			       enum bpf_diag_stack_arg_reason reason,
+			       const struct bpf_reg_state *old_reg,
+			       const struct bpf_reg_state *new_reg)
+{
+	struct bpf_diag_history_event event = {
+		.insn_idx = insn_idx,
+		.kind = BPF_DIAG_HISTORY_STACK_ARG,
+		.stack_arg.frameno = frameno,
+		.stack_arg.slot = slot,
+		.stack_arg.reason = reason,
+	};
+
+	bpf_diag_snapshot_one_reg(&event.stack_arg.old, old_reg);
+	bpf_diag_snapshot_one_reg(&event.stack_arg.new, new_reg);
+
+	if (reason == BPF_DIAG_STACK_ARG_WRITE &&
+	    bpf_diag_snapshot_eq(&event.stack_arg.old, &event.stack_arg.new))
+		return;
+
+	bpf_diag_append_history(env, &event);
+}
+
+void bpf_diag_record_stack_slot(struct bpf_verifier_env *env, u32 insn_idx,
+				u32 frameno, u16 spi,
+				enum bpf_diag_stack_slot_reason reason,
+				const struct bpf_reg_state *old_reg,
+				const struct bpf_reg_state *new_reg)
+{
+	struct bpf_diag_history_event event = {
+		.insn_idx = insn_idx,
+		.kind = BPF_DIAG_HISTORY_STACK_SLOT,
+		.stack_slot.frameno = frameno,
+		.stack_slot.spi = spi,
+		.stack_slot.reason = reason,
+	};
+
+	bpf_diag_snapshot_one_reg(&event.stack_slot.old, old_reg);
+	bpf_diag_snapshot_one_reg(&event.stack_slot.new, new_reg);
+
+	if (reason == BPF_DIAG_STACK_SLOT_SPILL &&
+	    bpf_diag_snapshot_eq(&event.stack_slot.old,
+				 &event.stack_slot.new))
+		return;
+	if (reason == BPF_DIAG_STACK_SLOT_WRITE &&
+	    bpf_diag_snapshot_eq(&event.stack_slot.old,
+				 &event.stack_slot.new))
+		return;
+
+	bpf_diag_append_history(env, &event);
+}
+
+struct bpf_diag_history_filter {
+	const struct bpf_diag_history_opts *opts;
+	bool stack_slot_valid;
+	u32 stack_frameno;
+	u16 stack_spi;
+	u32 stack_until_idx;
+};
+
+static bool bpf_diag_reg_event_matches(const struct bpf_diag_history_event *event,
+				       const struct bpf_diag_history_opts *opts)
+{
+	return opts->scope == BPF_DIAG_HISTORY_SCOPE_REG &&
+	       event->kind == BPF_DIAG_HISTORY_REG_MOD &&
+	       event->reg.dst_reg == opts->regno &&
+	       event->reg.frameno == opts->frameno;
+}
+
+static bool bpf_diag_stack_slot_matches(const struct bpf_diag_history_event *event,
+					const struct bpf_diag_history_filter *filter)
+{
+	return event->kind == BPF_DIAG_HISTORY_STACK_SLOT &&
+	       event->stack_slot.spi == filter->stack_spi &&
+	       event->stack_slot.frameno == filter->stack_frameno;
+}
+
+static bool bpf_diag_reg_event_keeps_lineage(const struct bpf_diag_history_event *event)
+{
+	if (event->reg.reason != BPF_DIAG_REG_MOD_WRITE)
+		return false;
+
+	switch (event->reg.opcode) {
+	case BPF_ADD:
+	case BPF_SUB:
+	case BPF_MUL:
+	case BPF_OR:
+	case BPF_AND:
+	case BPF_LSH:
+	case BPF_RSH:
+	case BPF_ARSH:
+	case BPF_XOR:
+	case BPF_NEG:
+	case BPF_END:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static void bpf_diag_history_follow_reg_stack(const struct bpf_diag_log *log,
+					      struct bpf_diag_history_filter *filter)
+{
+	const struct bpf_diag_history_opts *opts = filter->opts;
+	int i;
+
+	if (!opts || opts->scope != BPF_DIAG_HISTORY_SCOPE_REG)
+		return;
+
+	for (i = log->cnt; i > 0; i--) {
+		const struct bpf_diag_history_event *event;
+
+		event = bpf_diag_history_event(log, i - 1);
+		if (!bpf_diag_reg_event_matches(event, opts))
+			continue;
+
+		if (event->reg.stack_slot_valid) {
+			filter->stack_slot_valid = true;
+			filter->stack_frameno = event->reg.stack_frameno;
+			filter->stack_spi = event->reg.stack_spi;
+			filter->stack_until_idx = i - 1;
+			return;
+		}
+
+		if (!bpf_diag_reg_event_keeps_lineage(event))
+			return;
+	}
+}
+
+static int bpf_diag_history_stack_start_idx(const struct bpf_diag_log *log,
+					    const struct bpf_diag_history_filter *filter)
+{
+	int fallback = filter->stack_until_idx;
+	int i;
+
+	for (i = filter->stack_until_idx + 1; i > 0; i--) {
+		const struct bpf_diag_history_event *event;
+
+		event = bpf_diag_history_event(log, i - 1);
+		if (!bpf_diag_stack_slot_matches(event, filter))
+			continue;
+
+		fallback = i - 1;
+		if (event->stack_slot.reason == BPF_DIAG_STACK_SLOT_SPILL)
+			return fallback;
+	}
+
+	return fallback;
+}
+
+static int bpf_diag_history_start_idx(const struct bpf_diag_log *log,
+				      const struct bpf_diag_history_filter *filter)
+{
+	const struct bpf_diag_history_opts *opts = filter->opts;
+	int i;
+
+	if (!opts || opts->scope == BPF_DIAG_HISTORY_SCOPE_ALL)
+		return 0;
+	if (filter->stack_slot_valid)
+		return bpf_diag_history_stack_start_idx(log, filter);
+
+	for (i = log->cnt; i > 0; i--) {
+		const struct bpf_diag_history_event *event;
+
+		event = bpf_diag_history_event(log, i - 1);
+
+		if (bpf_diag_reg_event_matches(event, opts))
+			return i - 1;
+		if (opts->scope == BPF_DIAG_HISTORY_SCOPE_STACK_ARG &&
+		    event->kind == BPF_DIAG_HISTORY_STACK_ARG &&
+		    event->stack_arg.slot == opts->stack_arg_slot &&
+		    event->stack_arg.frameno == opts->frameno)
+			return i - 1;
+	}
+
+	return 0;
+}
+
+static bool
+bpf_diag_history_event_visible(const struct bpf_diag_history_event *event,
+			       u32 idx,
+			       const struct bpf_diag_history_filter *filter)
+{
+	const struct bpf_diag_history_opts *opts = filter->opts;
+
+	if (!opts || opts->scope == BPF_DIAG_HISTORY_SCOPE_ALL)
+		return true;
+
+	switch (event->kind) {
+	case BPF_DIAG_HISTORY_BRANCH:
+		return true;
+	case BPF_DIAG_HISTORY_REG_MOD:
+		return opts->scope == BPF_DIAG_HISTORY_SCOPE_REG &&
+		       event->reg.dst_reg == opts->regno &&
+		       event->reg.frameno == opts->frameno;
+	case BPF_DIAG_HISTORY_STACK_ARG:
+		return opts->scope == BPF_DIAG_HISTORY_SCOPE_STACK_ARG &&
+		       event->stack_arg.slot == opts->stack_arg_slot &&
+		       event->stack_arg.frameno == opts->frameno;
+	case BPF_DIAG_HISTORY_STACK_SLOT:
+		return filter->stack_slot_valid &&
+		       idx <= filter->stack_until_idx &&
+		       bpf_diag_stack_slot_matches(event, filter);
+	default:
+		return false;
+	}
+}
+
+static const char *bpf_diag_s64_bound_name(s64 value)
+{
+	if (value == S64_MIN)
+		return "S64_MIN";
+	if (value == S64_MAX)
+		return "S64_MAX";
+	return NULL;
+}
+
+static const char *bpf_diag_u64_bound_name(u64 value)
+{
+	if (value == U64_MAX)
+		return "U64_MAX";
+	return NULL;
+}
+
+static void bpf_diag_format_s64_value(char *buf, size_t size, s64 value)
+{
+	const char *name = bpf_diag_s64_bound_name(value);
+
+	if (name)
+		strscpy(buf, name, size);
+	else
+		scnprintf(buf, size, "%lld", value);
+}
+
+static void bpf_diag_format_u64_value(char *buf, size_t size, u64 value)
+{
+	const char *name = bpf_diag_u64_bound_name(value);
+
+	if (name)
+		strscpy(buf, name, size);
+	else
+		scnprintf(buf, size, "%llu", value);
+}
+
+static bool bpf_diag_range_unknown(s64 smin, s64 smax, u64 umin, u64 umax)
+{
+	return smin == S64_MIN && smax == S64_MAX &&
+	       umin == 0 && umax == U64_MAX;
+}
+
+static bool bpf_diag_cnum64_unknown(struct cnum64 range)
+{
+	return bpf_diag_range_unknown(cnum64_smin(range), cnum64_smax(range),
+				      cnum64_umin(range), cnum64_umax(range));
+}
+
+static bool bpf_diag_snapshot_unknown(const struct bpf_diag_reg_snapshot *snapshot)
+{
+	return tnum_is_unknown(snapshot->var_off) &&
+	       bpf_diag_cnum64_unknown(snapshot->r64);
+}
+
+static void bpf_diag_format_scalar_range(struct bpf_diag_reg_fmt *fmt,
+					 char *buf, size_t size,
+					 struct cnum64 range)
+{
+	s64 smin = cnum64_smin(range);
+	s64 smax = cnum64_smax(range);
+	u64 umin = cnum64_umin(range);
+	u64 umax = cnum64_umax(range);
+
+	bpf_diag_format_s64_value(fmt->smin_buf, sizeof(fmt->smin_buf), smin);
+	bpf_diag_format_s64_value(fmt->smax_buf, sizeof(fmt->smax_buf), smax);
+	bpf_diag_format_u64_value(fmt->umin_buf, sizeof(fmt->umin_buf), umin);
+	bpf_diag_format_u64_value(fmt->umax_buf, sizeof(fmt->umax_buf), umax);
+
+	scnprintf(buf, size,
+		  "signed range [%s, %s], unsigned range [%s, %s]",
+		  fmt->smin_buf, fmt->smax_buf, fmt->umin_buf, fmt->umax_buf);
+}
+
+static void bpf_diag_format_var_offset(struct bpf_diag_reg_fmt *fmt,
+				       char *buf, size_t size,
+				       const struct bpf_diag_reg_snapshot *snapshot)
+{
+	if (tnum_is_const(snapshot->var_off)) {
+		scnprintf(buf, size, "at offset %lld",
+			  (s64)snapshot->var_off.value);
+		return;
+	}
+
+	if (bpf_diag_snapshot_unknown(snapshot)) {
+		scnprintf(buf, size, "with unknown offset");
+		return;
+	}
+
+	bpf_diag_format_scalar_range(fmt, fmt->range, sizeof(fmt->range),
+				     snapshot->r64);
+	scnprintf(buf, size,
+		  "with variable offset: known bits %#llx, unknown mask %#llx, %s",
+		  snapshot->var_off.value, snapshot->var_off.mask, fmt->range);
+}
+
+static bool bpf_diag_format_snapshot_btf_type(char *buf, size_t size,
+					      const struct bpf_diag_reg_snapshot *snapshot)
+{
+	if (!snapshot->btf || !snapshot->btf_id)
+		return false;
+
+	bpf_diag_format_btf_type(buf, size, snapshot->btf, snapshot->btf_id);
+	return true;
+}
+
+static const char *bpf_diag_reg_map_name(const struct bpf_map *map)
+{
+	if (!map || !map->name[0])
+		return NULL;
+
+	return map->name;
+}
+
+static void bpf_diag_format_reg_snapshot(struct bpf_verifier_env *env,
+					 struct bpf_diag_reg_fmt *fmt,
+					 char *buf, size_t size,
+					 const struct bpf_diag_reg_snapshot *snapshot)
+{
+	const char *type_name = reg_type_str(env, snapshot->type);
+	const char *map_name;
+	bool has_btf_type;
+
+	bpf_diag_format_var_offset(fmt, fmt->offset_desc,
+				   sizeof(fmt->offset_desc), snapshot);
+	has_btf_type = bpf_diag_format_snapshot_btf_type(fmt->btf_type,
+							 sizeof(fmt->btf_type),
+							 snapshot);
+
+	if (snapshot->type == SCALAR_VALUE) {
+		if (tnum_is_const(snapshot->var_off)) {
+			scnprintf(buf, size, "integer scalar value %lld",
+				  (s64)snapshot->var_off.value);
+			return;
+		}
+
+		if (bpf_diag_snapshot_unknown(snapshot)) {
+			scnprintf(buf, size, "integer scalar with unknown value");
+			return;
+		}
+
+		if (cnum64_is_const(snapshot->r64)) {
+			scnprintf(buf, size, "integer scalar value %lld",
+				  cnum64_smin(snapshot->r64));
+			return;
+		}
+
+		bpf_diag_format_scalar_range(fmt, fmt->range,
+					     sizeof(fmt->range),
+					     snapshot->r64);
+		scnprintf(buf, size, "integer scalar with %s", fmt->range);
+		return;
+	}
+
+	if (snapshot->type == NOT_INIT) {
+		scnprintf(buf, size, "uninitialized value");
+		return;
+	}
+
+	if (base_type(snapshot->type) == PTR_TO_CTX) {
+		scnprintf(buf, size, "context pointer %s", fmt->offset_desc);
+		return;
+	}
+
+	if (base_type(snapshot->type) == PTR_TO_STACK) {
+		scnprintf(buf, size, "stack pointer %s", fmt->offset_desc);
+		return;
+	}
+
+	if (base_type(snapshot->type) == PTR_TO_MAP_VALUE) {
+		map_name = bpf_diag_reg_map_name(snapshot->map_ptr);
+		if (map_name) {
+			scnprintf(buf, size, "%s from %s %s",
+				  type_may_be_null(snapshot->type) ?
+				  "nullable map value" : "map value",
+				  map_name, fmt->offset_desc);
+			return;
+		}
+		scnprintf(buf, size, "%s %s",
+			  type_may_be_null(snapshot->type) ?
+			  "nullable map value" : "map value",
+			  fmt->offset_desc);
+		return;
+	}
+
+	if (base_type(snapshot->type) == CONST_PTR_TO_MAP) {
+		map_name = bpf_diag_reg_map_name(snapshot->map_ptr);
+		if (map_name)
+			scnprintf(buf, size, "map pointer for map %s", map_name);
+		else
+			scnprintf(buf, size, "map pointer");
+		return;
+	}
+
+	if (type_is_non_owning_ref(snapshot->type)) {
+		if (has_btf_type)
+			scnprintf(buf, size,
+				  "borrowed allocated object pointer type=%s",
+				  fmt->btf_type);
+		else
+			scnprintf(buf, size, "borrowed allocated object pointer");
+		return;
+	}
+
+	if (type_is_ptr_alloc_obj(snapshot->type)) {
+		if (has_btf_type)
+			scnprintf(buf, size,
+				  "owned allocated object pointer type=%s",
+				  fmt->btf_type);
+		else
+			scnprintf(buf, size, "owned allocated object pointer");
+		return;
+	}
+
+	if (base_type(snapshot->type) == PTR_TO_BTF_ID && has_btf_type) {
+		scnprintf(buf, size, "%s type=%s %s", type_name,
+			  fmt->btf_type, fmt->offset_desc);
+		return;
+	}
+
+	scnprintf(buf, size, "%s %s", type_name, fmt->offset_desc);
+}
+
+static void bpf_diag_print_reg_mod(struct bpf_verifier_env *env,
+				   const struct bpf_diag_history_event *event)
+{
+	struct bpf_diag_scratch *scratch = bpf_diag_scratch(env);
+	struct bpf_diag_reg_fmt *fmt = &scratch->reg_fmt;
+	const char *reason = NULL;
+
+	memset(fmt, 0, sizeof(*fmt));
+
+	bpf_diag_format_reg_snapshot(env, fmt, fmt->old_buf,
+				     sizeof(fmt->old_buf), &event->reg.old);
+	bpf_diag_format_reg_snapshot(env, fmt, fmt->new_buf,
+				     sizeof(fmt->new_buf), &event->reg.new);
+
+	switch (event->reg.reason) {
+	case BPF_DIAG_REG_MOD_REF_RELEASE:
+		reason = "resource release invalidated this pointer";
+		break;
+	case BPF_DIAG_REG_MOD_PKT_DATA_CHANGE:
+		reason = "packet data may have moved";
+		break;
+	case BPF_DIAG_REG_MOD_NON_OWN_REF:
+		reason = "leaving the protected region invalidated this borrowed pointer";
+		break;
+	case BPF_DIAG_REG_MOD_WRITE:
+	default:
+		break;
+	}
+
+	if (reason) {
+		bpf_diag_report_source(env, event->insn_idx, "invalidated",
+				       "R%d: %s; previous value was %s",
+				       event->reg.dst_reg, reason,
+				       fmt->old_buf);
+		return;
+	}
+
+	bpf_diag_report_source(env, event->insn_idx, "update",
+			       "R%d changed from %s to %s",
+			       event->reg.dst_reg, fmt->old_buf, fmt->new_buf);
+}
+
+static int bpf_diag_stack_argno(u8 slot)
+{
+	return MAX_BPF_FUNC_REG_ARGS + slot + 1;
+}
+
+static void bpf_diag_print_stack_arg(struct bpf_verifier_env *env,
+				     const struct bpf_diag_history_event *event)
+{
+	struct bpf_diag_scratch *scratch = bpf_diag_scratch(env);
+	struct bpf_diag_reg_fmt *fmt = &scratch->reg_fmt;
+	const char *reason = NULL;
+	int argno = bpf_diag_stack_argno(event->stack_arg.slot);
+
+	memset(fmt, 0, sizeof(*fmt));
+
+	bpf_diag_format_reg_snapshot(env, fmt, fmt->old_buf,
+				     sizeof(fmt->old_buf),
+				     &event->stack_arg.old);
+	bpf_diag_format_reg_snapshot(env, fmt, fmt->new_buf,
+				     sizeof(fmt->new_buf),
+				     &event->stack_arg.new);
+
+	switch (event->stack_arg.reason) {
+	case BPF_DIAG_STACK_ARG_REF_RELEASE:
+		reason = "resource release invalidated this value";
+		break;
+	case BPF_DIAG_STACK_ARG_PKT_DATA_CHANGE:
+		reason = "packet data may have moved";
+		break;
+	case BPF_DIAG_STACK_ARG_NON_OWN_REF:
+		reason = "leaving the protected region invalidated this borrowed pointer";
+		break;
+	case BPF_DIAG_STACK_ARG_WRITE:
+	default:
+		break;
+	}
+
+	if (reason) {
+		bpf_diag_report_source(env, event->insn_idx, "invalidated",
+				       "stack arg%d: %s; previous value was %s",
+				       argno, reason, fmt->old_buf);
+		return;
+	}
+
+	bpf_diag_report_source(env, event->insn_idx, "update",
+			       "stack arg%d changed from %s to %s",
+			       argno, fmt->old_buf, fmt->new_buf);
+}
+
+static int bpf_diag_stack_slot_off(u16 spi)
+{
+	return -(spi + 1) * BPF_REG_SIZE;
+}
+
+static void bpf_diag_print_stack_slot(struct bpf_verifier_env *env,
+				      const struct bpf_diag_history_event *event)
+{
+	struct bpf_diag_scratch *scratch = bpf_diag_scratch(env);
+	struct bpf_diag_reg_fmt *fmt = &scratch->reg_fmt;
+	const char *reason = NULL;
+	int off = bpf_diag_stack_slot_off(event->stack_slot.spi);
+
+	memset(fmt, 0, sizeof(*fmt));
+
+	bpf_diag_format_reg_snapshot(env, fmt, fmt->old_buf,
+				     sizeof(fmt->old_buf),
+				     &event->stack_slot.old);
+	bpf_diag_format_reg_snapshot(env, fmt, fmt->new_buf,
+				     sizeof(fmt->new_buf),
+				     &event->stack_slot.new);
+
+	switch (event->stack_slot.reason) {
+	case BPF_DIAG_STACK_SLOT_REF_RELEASE:
+		reason = "resource release invalidated this spilled value";
+		break;
+	case BPF_DIAG_STACK_SLOT_PKT_DATA_CHANGE:
+		reason = "packet data may have moved";
+		break;
+	case BPF_DIAG_STACK_SLOT_NON_OWN_REF:
+		reason = "leaving the protected region invalidated this borrowed pointer";
+		break;
+	case BPF_DIAG_STACK_SLOT_WRITE:
+		reason = "a later stack write overwrote this spilled value";
+		break;
+	case BPF_DIAG_STACK_SLOT_SPILL:
+	default:
+		break;
+	}
+
+	if (reason) {
+		bpf_diag_report_source(env, event->insn_idx, "invalidated",
+				       "stack slot fp%d: %s; previous value was %s",
+				       off, reason, fmt->old_buf);
+		return;
+	}
+
+	bpf_diag_report_source(env, event->insn_idx, "spilled",
+			       "stack slot fp%d changed from %s to %s",
+			       off, fmt->old_buf, fmt->new_buf);
+}
+
+void bpf_diag_print_history(struct bpf_verifier_env *env,
+			    const struct bpf_diag_history_opts *opts)
 {
 	const struct bpf_diag_history_event *event;
+	struct bpf_diag_history_filter filter = {
+		.opts = opts,
+	};
 	const struct bpf_diag_log *log;
 	bool printed = false;
+	int start_idx;
 	u32 i;
 
 	bpf_diag_report_section(env, "Causal path");
@@ -810,8 +1556,12 @@ void bpf_diag_print_history(struct bpf_verifier_env *env)
 	}
 	log = &env->diag->log;
 
-	for (i = 0; i < log->cnt; i++) {
+	bpf_diag_history_follow_reg_stack(log, &filter);
+	start_idx = bpf_diag_history_start_idx(log, &filter);
+	for (i = start_idx; i < log->cnt; i++) {
 		event = bpf_diag_history_event(log, i);
+		if (!bpf_diag_history_event_visible(event, i, &filter))
+			continue;
 
 		switch (event->kind) {
 		case BPF_DIAG_HISTORY_BRANCH:
@@ -823,6 +1573,18 @@ void bpf_diag_print_history(struct bpf_verifier_env *env)
 					       "not followed");
 			printed = true;
 			break;
+		case BPF_DIAG_HISTORY_REG_MOD:
+			bpf_diag_print_reg_mod(env, event);
+			printed = true;
+			break;
+		case BPF_DIAG_HISTORY_STACK_ARG:
+			bpf_diag_print_stack_arg(env, event);
+			printed = true;
+			break;
+		case BPF_DIAG_HISTORY_STACK_SLOT:
+			bpf_diag_print_stack_slot(env, event);
+			printed = true;
+			break;
 		default:
 			break;
 		}
diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
index 4bc44be757c4..7af0a694890b 100644
--- a/kernel/bpf/diagnostics.h
+++ b/kernel/bpf/diagnostics.h
@@ -5,9 +5,49 @@
 #define __BPF_DIAGNOSTICS_H
 
 #include <linux/compiler_attributes.h>
+#include <linux/cnum.h>
+#include <linux/tnum.h>
 #include <linux/types.h>
 
+struct bpf_map;
+struct bpf_reg_state;
 struct bpf_verifier_env;
+struct btf;
+struct btf_type;
+
+void bpf_diag_format_btf_type(char *buf, size_t size, const struct btf *btf,
+			      u32 type_id);
+
+struct bpf_diag_reg_snapshot {
+	u32 type;
+	const struct bpf_map *map_ptr;
+	const struct btf *btf;
+	u32 btf_id;
+	struct tnum var_off;
+	struct cnum64 r64;
+};
+
+enum bpf_diag_reg_mod_reason {
+	BPF_DIAG_REG_MOD_WRITE,
+	BPF_DIAG_REG_MOD_REF_RELEASE,
+	BPF_DIAG_REG_MOD_PKT_DATA_CHANGE,
+	BPF_DIAG_REG_MOD_NON_OWN_REF,
+};
+
+enum bpf_diag_stack_arg_reason {
+	BPF_DIAG_STACK_ARG_WRITE,
+	BPF_DIAG_STACK_ARG_REF_RELEASE,
+	BPF_DIAG_STACK_ARG_PKT_DATA_CHANGE,
+	BPF_DIAG_STACK_ARG_NON_OWN_REF,
+};
+
+enum bpf_diag_stack_slot_reason {
+	BPF_DIAG_STACK_SLOT_SPILL,
+	BPF_DIAG_STACK_SLOT_WRITE,
+	BPF_DIAG_STACK_SLOT_REF_RELEASE,
+	BPF_DIAG_STACK_SLOT_PKT_DATA_CHANGE,
+	BPF_DIAG_STACK_SLOT_NON_OWN_REF,
+};
 
 struct bpf_diag_history_event {
 	u32 insn_idx;
@@ -16,11 +56,51 @@ struct bpf_diag_history_event {
 		struct {
 			bool cond_true;
 		} branch;
+		struct {
+			u32 frameno;
+			u8 dst_reg;
+			u8 src_reg;
+			u8 opcode;
+			bool src_valid;
+			bool stack_slot_valid;
+			u8 reason;
+			u32 stack_frameno;
+			u16 stack_spi;
+			struct bpf_diag_reg_snapshot old, new;
+		} reg;
+		struct {
+			u32 frameno;
+			u8 slot;
+			u8 reason;
+			struct bpf_diag_reg_snapshot old, new;
+		} stack_arg;
+		struct {
+			u32 frameno;
+			u16 spi;
+			u8 reason;
+			struct bpf_diag_reg_snapshot old, new;
+		} stack_slot;
 	};
 };
 
 enum bpf_diag_history_kind {
 	BPF_DIAG_HISTORY_BRANCH,
+	BPF_DIAG_HISTORY_REG_MOD,
+	BPF_DIAG_HISTORY_STACK_ARG,
+	BPF_DIAG_HISTORY_STACK_SLOT,
+};
+
+enum bpf_diag_history_scope {
+	BPF_DIAG_HISTORY_SCOPE_ALL,
+	BPF_DIAG_HISTORY_SCOPE_REG,
+	BPF_DIAG_HISTORY_SCOPE_STACK_ARG,
+};
+
+struct bpf_diag_history_opts {
+	enum bpf_diag_history_scope scope;
+	u32 frameno;
+	int regno;
+	int stack_arg_slot;
 };
 
 bool bpf_diag_enabled(const struct bpf_verifier_env *env);
@@ -33,6 +113,10 @@ const char *bpf_diag_scratch_printf(struct bpf_verifier_env *env,
 				    unsigned int slot,
 				    const char *fmt, ...)
 	__printf(3, 4);
+const char *bpf_diag_format_btf_type_scratch(struct bpf_verifier_env *env,
+					     unsigned int slot,
+					     const struct btf *btf,
+					     u32 type_id);
 u64 bpf_diag_event_log_pos(struct bpf_verifier_env *env);
 void bpf_diag_event_log_reset(struct bpf_verifier_env *env, u64 pos);
 void bpf_diag_free(struct bpf_verifier_env *env);
@@ -43,6 +127,33 @@ void bpf_diag_report_source(struct bpf_verifier_env *env, u32 insn_idx,
 	__printf(4, 5);
 void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx,
 			    bool cond_true);
-void bpf_diag_print_history(struct bpf_verifier_env *env);
+void bpf_diag_record_reg_mod(struct bpf_verifier_env *env, u32 insn_idx,
+			     u32 frameno, u8 dst_reg, bool src_valid,
+			     u8 src_reg, u8 opcode,
+			     const struct bpf_reg_state *old_reg,
+			     const struct bpf_reg_state *new_reg);
+void bpf_diag_record_reg_stack_fill(struct bpf_verifier_env *env, u32 insn_idx,
+				    u32 frameno, u8 dst_reg, u32 stack_frameno,
+				    u16 stack_spi, bool src_valid, u8 src_reg,
+				    u8 opcode,
+				    const struct bpf_reg_state *old_reg,
+				    const struct bpf_reg_state *new_reg);
+void bpf_diag_record_reg_invalidate(struct bpf_verifier_env *env, u32 insn_idx,
+				    u32 frameno, u8 dst_reg,
+				    enum bpf_diag_reg_mod_reason reason,
+				    const struct bpf_reg_state *old_reg,
+				    const struct bpf_reg_state *new_reg);
+void bpf_diag_record_stack_arg(struct bpf_verifier_env *env, u32 insn_idx,
+			       u32 frameno, u8 slot,
+			       enum bpf_diag_stack_arg_reason reason,
+			       const struct bpf_reg_state *old_reg,
+			       const struct bpf_reg_state *new_reg);
+void bpf_diag_record_stack_slot(struct bpf_verifier_env *env, u32 insn_idx,
+				u32 frameno, u16 spi,
+				enum bpf_diag_stack_slot_reason reason,
+				const struct bpf_reg_state *old_reg,
+				const struct bpf_reg_state *new_reg);
+void bpf_diag_print_history(struct bpf_verifier_env *env,
+			    const struct bpf_diag_history_opts *opts);
 
 #endif /* __BPF_DIAGNOSTICS_H */
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index ca4bba163418..fedabb6bb515 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -3406,6 +3406,22 @@ static void assign_scalar_id_before_mov(struct bpf_verifier_env *env,
 		src_reg->id = ++env->id_gen;
 }
 
+static int bpf_diag_stack_slot(const struct bpf_func_state *state,
+			       const struct bpf_stack_state *stack)
+{
+	unsigned long start, end, addr;
+
+	if (!stack)
+		return -1;
+
+	addr = (unsigned long)stack;
+	start = (unsigned long)state->stack;
+	end = (unsigned long)(state->stack + state->allocated_stack / BPF_REG_SIZE);
+	if (addr < start || addr >= end)
+		return -1;
+	return stack - state->stack;
+}
+
 static void save_register_state(struct bpf_verifier_env *env,
 				struct bpf_func_state *state,
 				int spi, struct bpf_reg_state *reg,
@@ -3485,6 +3501,8 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
 	struct bpf_reg_state *reg = NULL;
 	int insn_flags = INSN_F_STACK_ACCESS;
 	int hist_spi = spi, hist_frame = state->frameno;
+	struct bpf_reg_state old_spill = state->stack[spi].spilled_ptr;
+	bool old_was_spill = bpf_is_spilled_reg(&state->stack[spi]);
 
 	/* caller checked that off % size == 0 and -MAX_BPF_STACK <= off < 0,
 	 * so it's aligned access and [off, off + size) are within stack limits
@@ -3533,6 +3551,11 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
 		/* Break the relation on a narrowing spill. */
 		if (!reg_value_fits)
 			state->stack[spi].spilled_ptr.id = 0;
+		if (bpf_is_spilled_reg(&state->stack[spi]))
+			bpf_diag_record_stack_slot(env, insn_idx, state->frameno,
+						   spi, BPF_DIAG_STACK_SLOT_SPILL,
+						   &old_spill,
+						   &state->stack[spi].spilled_ptr);
 	} else if (!reg && !(off % BPF_REG_SIZE) && is_bpf_st_mem(insn) &&
 		   env->bpf_capable) {
 		struct bpf_reg_state *tmp_reg = &env->fake_reg[0];
@@ -3541,6 +3564,11 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
 		__mark_reg_known(tmp_reg, insn->imm);
 		tmp_reg->type = SCALAR_VALUE;
 		save_register_state(env, state, spi, tmp_reg, size);
+		if (bpf_is_spilled_reg(&state->stack[spi]))
+			bpf_diag_record_stack_slot(env, insn_idx, state->frameno,
+						   spi, BPF_DIAG_STACK_SLOT_SPILL,
+						   &old_spill,
+						   &state->stack[spi].spilled_ptr);
 	} else if (reg && is_spillable_regtype(reg->type)) {
 		/* register containing pointer is being spilled into stack */
 		if (size != BPF_REG_SIZE) {
@@ -3553,6 +3581,11 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
 			return -EINVAL;
 		}
 		save_register_state(env, state, spi, reg, size);
+		if (bpf_is_spilled_reg(&state->stack[spi]))
+			bpf_diag_record_stack_slot(env, insn_idx, state->frameno,
+						   spi, BPF_DIAG_STACK_SLOT_SPILL,
+						   &old_spill,
+						   &state->stack[spi].spilled_ptr);
 	} else {
 		u8 type = STACK_MISC;
 
@@ -3577,6 +3610,11 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
 		for (i = 0; i < size; i++)
 			state->stack[spi].slot_type[(slot - i) % BPF_REG_SIZE] = type;
 		insn_flags = 0; /* not a register spill */
+		if (old_was_spill && !bpf_is_spilled_reg(&state->stack[spi]))
+			bpf_diag_record_stack_slot(env, insn_idx, state->frameno,
+						   spi, BPF_DIAG_STACK_SLOT_WRITE,
+						   &old_spill,
+						   &state->stack[spi].spilled_ptr);
 	}
 
 	if (insn_flags)
@@ -4061,6 +4099,8 @@ static int check_stack_arg_write(struct bpf_verifier_env *env, struct bpf_func_s
 	struct bpf_subprog_info *subprog = &env->subprog_info[state->subprogno];
 	int spi = -off / BPF_REG_SIZE - 1;
 	struct bpf_reg_state *arg;
+	struct bpf_reg_state old_arg = {};
+	bool slot_exists;
 	int err;
 
 	if (spi >= max_stack_arg_regs) {
@@ -4069,6 +4109,7 @@ static int check_stack_arg_write(struct bpf_verifier_env *env, struct bpf_func_s
 		return -EINVAL;
 	}
 
+	slot_exists = spi < state->out_stack_arg_cnt;
 	err = grow_stack_arg_slots(env, state, spi + 1);
 	if (err)
 		return err;
@@ -4077,13 +4118,25 @@ static int check_stack_arg_write(struct bpf_verifier_env *env, struct bpf_func_s
 	if (spi + 1 > subprog->max_out_stack_arg_cnt)
 		subprog->max_out_stack_arg_cnt = spi + 1;
 
+	arg = &state->stack_arg_regs[spi];
+	if (slot_exists)
+		old_arg = *arg;
+	else
+		bpf_mark_reg_not_init(env, &old_arg);
+
 	if (value_reg) {
 		state->stack_arg_regs[spi] = *value_reg;
+		bpf_diag_record_stack_arg(env, env->insn_idx, state->frameno,
+					  spi, BPF_DIAG_STACK_ARG_WRITE,
+					  &old_arg,
+					  &state->stack_arg_regs[spi]);
 	} else {
 		/* BPF_ST: store immediate, treat as scalar */
-		arg = &state->stack_arg_regs[spi];
 		arg->type = SCALAR_VALUE;
 		__mark_reg_known(arg, env->prog->insnsi[env->insn_idx].imm);
+		bpf_diag_record_stack_arg(env, env->insn_idx, state->frameno,
+					  spi, BPF_DIAG_STACK_ARG_WRITE,
+					  &old_arg, arg);
 	}
 	state->no_stack_arg_load = true;
 	return bpf_push_jmp_history(env, env->cur_state,
@@ -6349,6 +6402,32 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
 static int save_aux_ptr_type(struct bpf_verifier_env *env, enum bpf_reg_type type,
 			     bool allow_trust_mismatch);
 
+static bool bpf_diag_stack_read_origin(struct bpf_verifier_env *env,
+				       const struct bpf_reg_state *reg,
+				       int off, int size,
+				       u32 *frameno, u16 *spi)
+{
+	struct bpf_func_state *state;
+	int first_slot, last_slot;
+
+	if (reg->type != PTR_TO_STACK || !tnum_is_const(reg->var_off))
+		return false;
+
+	off += reg->var_off.value;
+	if (off >= 0 || off + size > 0)
+		return false;
+
+	first_slot = -off - 1;
+	last_slot = -off - size;
+	if (first_slot / BPF_REG_SIZE != last_slot / BPF_REG_SIZE)
+		return false;
+
+	state = bpf_func(env, reg);
+	*frameno = state->frameno;
+	*spi = first_slot / BPF_REG_SIZE;
+	return true;
+}
+
 static int check_load_mem(struct bpf_verifier_env *env, struct bpf_insn *insn,
 			  bool strict_alignment_once, bool is_ldsx,
 			  bool allow_trust_mismatch, const char *ctx)
@@ -6356,15 +6435,32 @@ static int check_load_mem(struct bpf_verifier_env *env, struct bpf_insn *insn,
 	struct bpf_verifier_state *vstate = env->cur_state;
 	struct bpf_func_state *state = vstate->frame[vstate->curframe];
 	struct bpf_reg_state *regs = cur_regs(env);
+	struct bpf_reg_state old_dst = {};
 	enum bpf_reg_type src_reg_type;
+	u32 stack_frameno = 0;
+	u16 stack_spi = 0;
+	bool have_old_dst;
+	bool stack_origin;
+	int size;
 	int err;
 
+	have_old_dst = insn->dst_reg < MAX_BPF_REG;
+	if (have_old_dst)
+		old_dst = regs[insn->dst_reg];
+
 	/* Handle stack arg read */
 	if (is_stack_arg_ldx(insn)) {
 		err = check_reg_arg(env, insn->dst_reg, DST_OP_NO_MARK);
 		if (err)
 			return err;
-		return check_stack_arg_read(env, state, insn->off, insn->dst_reg);
+		err = check_stack_arg_read(env, state, insn->off, insn->dst_reg);
+		if (!err && have_old_dst)
+			bpf_diag_record_reg_mod(env, env->insn_idx,
+						state->frameno, insn->dst_reg,
+						true, insn->src_reg,
+						BPF_OP(insn->code), &old_dst,
+						&regs[insn->dst_reg]);
+		return err;
 	}
 
 	/* check src operand */
@@ -6385,9 +6481,32 @@ static int check_load_mem(struct bpf_verifier_env *env, struct bpf_insn *insn,
 	err = check_mem_access(env, env->insn_idx, regs + insn->src_reg, argno_from_reg(insn->src_reg), insn->off,
 			       BPF_SIZE(insn->code), BPF_READ, insn->dst_reg,
 			       strict_alignment_once, is_ldsx);
+	size = bpf_size_to_bytes(BPF_SIZE(insn->code));
+	stack_origin = !err &&
+		       bpf_diag_stack_read_origin(env, regs + insn->src_reg,
+						  insn->off, size,
+						  &stack_frameno, &stack_spi);
 	err = err ?: save_aux_ptr_type(env, src_reg_type,
 				       allow_trust_mismatch);
 	err = err ?: reg_bounds_sanity_check(env, &regs[insn->dst_reg], ctx);
+	if (!err && have_old_dst) {
+		if (stack_origin)
+			bpf_diag_record_reg_stack_fill(env, env->insn_idx,
+						       state->frameno,
+						       insn->dst_reg,
+						       stack_frameno,
+						       stack_spi, true,
+						       insn->src_reg,
+						       BPF_OP(insn->code),
+						       &old_dst,
+						       &regs[insn->dst_reg]);
+		else
+			bpf_diag_record_reg_mod(env, env->insn_idx,
+						state->frameno, insn->dst_reg,
+						true, insn->src_reg,
+						BPF_OP(insn->code), &old_dst,
+						&regs[insn->dst_reg]);
+	}
 
 	return err;
 }
@@ -8867,6 +8986,34 @@ static int check_func_proto(const struct bpf_func_proto *fn, struct bpf_call_arg
 	       check_btf_id_ok(fn) ? 0 : -EINVAL;
 }
 
+static int bpf_diag_stack_arg_slot(const struct bpf_func_state *state,
+				   const struct bpf_reg_state *reg)
+{
+	unsigned long start, end, addr;
+
+	if (!state->stack_arg_regs)
+		return -1;
+
+	addr = (unsigned long)reg;
+	start = (unsigned long)state->stack_arg_regs;
+	end = (unsigned long)(state->stack_arg_regs + state->out_stack_arg_cnt);
+	if (addr < start || addr >= end)
+		return -1;
+	return reg - state->stack_arg_regs;
+}
+
+static int bpf_diag_func_regno(const struct bpf_func_state *state,
+			       const struct bpf_reg_state *reg)
+{
+	unsigned long start = (unsigned long)state->regs;
+	unsigned long end = (unsigned long)(state->regs + MAX_BPF_REG);
+	unsigned long addr = (unsigned long)reg;
+
+	if (addr < start || addr >= end)
+		return -1;
+	return reg - state->regs;
+}
+
 /* Packet data might have moved, any old PTR_TO_PACKET[_META,_END]
  * are now invalid, so turn them into unknown SCALAR_VALUE.
  *
@@ -8875,12 +9022,38 @@ static int check_func_proto(const struct bpf_func_proto *fn, struct bpf_call_arg
  */
 static void clear_all_pkt_pointers(struct bpf_verifier_env *env)
 {
+	struct bpf_stack_state *stack;
 	struct bpf_func_state *state;
 	struct bpf_reg_state *reg;
 
-	bpf_for_each_reg_in_vstate(env->cur_state, state, reg, ({
-		if (reg_is_pkt_pointer_any(reg) || reg_is_dynptr_slice_pkt(reg))
+	bpf_for_each_reg_in_vstate_mask(env->cur_state, state, reg, stack,
+					1 << STACK_SPILL, ({
+		if (reg_is_pkt_pointer_any(reg) || reg_is_dynptr_slice_pkt(reg)) {
+			struct bpf_reg_state old_reg = *reg;
+			int regno = bpf_diag_func_regno(state, reg);
+			int slot = bpf_diag_stack_arg_slot(state, reg);
+			int spi = bpf_diag_stack_slot(state, stack);
+
 			mark_reg_invalid(env, reg);
+			if (regno >= 0)
+				bpf_diag_record_reg_invalidate(env,
+							       env->insn_idx,
+							       state->frameno,
+							       regno,
+							       BPF_DIAG_REG_MOD_PKT_DATA_CHANGE,
+							       &old_reg, reg);
+			if (slot >= 0)
+				bpf_diag_record_stack_arg(env, env->insn_idx,
+							  state->frameno,
+							  slot,
+							  BPF_DIAG_STACK_ARG_PKT_DATA_CHANGE,
+							  &old_reg, reg);
+			if (spi >= 0)
+				bpf_diag_record_stack_slot(env, env->insn_idx,
+							   state->frameno, spi,
+							   BPF_DIAG_STACK_SLOT_PKT_DATA_CHANGE,
+							   &old_reg, reg);
+		}
 	}));
 }
 
@@ -8986,6 +9159,11 @@ static int release_reference(struct bpf_verifier_env *env, int id)
 		}
 
 		bpf_for_each_reg_in_vstate_mask(vstate, state, reg, stack, mask, ({
+			struct bpf_reg_state old_reg;
+			int regno;
+			int slot;
+			int spi;
+
 			if (reg->id != id && reg->parent_id != id)
 				continue;
 
@@ -8996,10 +9174,31 @@ static int release_reference(struct bpf_verifier_env *env, int id)
 					return err;
 			}
 
+			old_reg = *reg;
+			regno = bpf_diag_func_regno(state, reg);
+			slot = bpf_diag_stack_arg_slot(state, reg);
+			spi = bpf_diag_stack_slot(state, stack);
 			if (!stack || stack->slot_type[BPF_REG_SIZE - 1] == STACK_SPILL)
 				mark_reg_invalid(env, reg);
 			else if (stack->slot_type[BPF_REG_SIZE - 1] == STACK_DYNPTR)
 				invalidate_dynptr(env, stack);
+			if (regno >= 0)
+				bpf_diag_record_reg_invalidate(env,
+							       env->insn_idx,
+							       state->frameno,
+							       regno,
+							       BPF_DIAG_REG_MOD_REF_RELEASE,
+							       &old_reg, reg);
+			if (slot >= 0)
+				bpf_diag_record_stack_arg(env, env->insn_idx,
+							  state->frameno, slot,
+							  BPF_DIAG_STACK_ARG_REF_RELEASE,
+							  &old_reg, reg);
+			if (spi >= 0)
+				bpf_diag_record_stack_slot(env, env->insn_idx,
+							   state->frameno, spi,
+							   BPF_DIAG_STACK_SLOT_REF_RELEASE,
+							   &old_reg, reg);
 		}));
 	}
 
@@ -9008,12 +9207,37 @@ static int release_reference(struct bpf_verifier_env *env, int id)
 
 static void invalidate_non_owning_refs(struct bpf_verifier_env *env)
 {
-	struct bpf_func_state *unused;
+	struct bpf_stack_state *stack;
+	struct bpf_func_state *state;
 	struct bpf_reg_state *reg;
 
-	bpf_for_each_reg_in_vstate(env->cur_state, unused, reg, ({
-		if (type_is_non_owning_ref(reg->type))
+	bpf_for_each_reg_in_vstate_mask(env->cur_state, state, reg, stack,
+					1 << STACK_SPILL, ({
+		if (type_is_non_owning_ref(reg->type)) {
+			struct bpf_reg_state old_reg = *reg;
+			int regno = bpf_diag_func_regno(state, reg);
+			int slot = bpf_diag_stack_arg_slot(state, reg);
+			int spi = bpf_diag_stack_slot(state, stack);
+
 			mark_reg_invalid(env, reg);
+			if (regno >= 0)
+				bpf_diag_record_reg_invalidate(env,
+							       env->insn_idx,
+							       state->frameno,
+							       regno,
+							       BPF_DIAG_REG_MOD_NON_OWN_REF,
+							       &old_reg, reg);
+			if (slot >= 0)
+				bpf_diag_record_stack_arg(env, env->insn_idx,
+							  state->frameno, slot,
+							  BPF_DIAG_STACK_ARG_NON_OWN_REF,
+							  &old_reg, reg);
+			if (spi >= 0)
+				bpf_diag_record_stack_slot(env, env->insn_idx,
+							   state->frameno, spi,
+							   BPF_DIAG_STACK_SLOT_NON_OWN_REF,
+							   &old_reg, reg);
+		}
 	}));
 }
 
@@ -10179,6 +10403,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
 	const struct bpf_func_proto *fn = NULL;
 	enum bpf_return_type ret_type;
 	enum bpf_type_flag ret_flag;
+	struct bpf_reg_state old_r0;
 	struct bpf_reg_state *regs;
 	struct bpf_call_arg_meta meta;
 	int insn_idx = *insn_idx_p;
@@ -10253,6 +10478,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
 		return err;
 
 	regs = cur_regs(env);
+	old_r0 = regs[BPF_REG_0];
 
 	/* Mark slots with STACK_MISC in case of raw mode, stack offset
 	 * is inferred from register state.
@@ -10603,6 +10829,10 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
 	if (err)
 		return err;
 
+	bpf_diag_record_reg_mod(env, insn_idx, env->cur_state->curframe,
+				BPF_REG_0, false, 0, 0, &old_r0,
+				&regs[BPF_REG_0]);
+
 	err = check_map_func_compatibility(env, meta.map.ptr, func_id);
 	if (err)
 		return err;
@@ -12918,6 +13148,7 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 	const struct btf_type *t, *ptr_type;
 	struct bpf_kfunc_call_arg_meta meta;
 	struct bpf_insn_aux_data *insn_aux;
+	struct bpf_reg_state old_r0;
 	int err, insn_idx = *insn_idx_p;
 	const struct btf_param *args;
 	u32 i, nargs, ptr_type_id;
@@ -13114,6 +13345,7 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 		}
 	}
 
+	old_r0 = regs[BPF_REG_0];
 	for (i = 0; i < CALLER_SAVED_REGS; i++) {
 		u32 regno = caller_saved[i];
 
@@ -13282,6 +13514,10 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 			return err;
 	}
 
+	bpf_diag_record_reg_mod(env, insn_idx, env->cur_state->curframe,
+				BPF_REG_0, false, 0, 0, &old_r0,
+				&regs[BPF_REG_0]);
+
 	if (meta.func_id == special_kfunc_list[KF_bpf_session_cookie])
 		env->prog->call_session_cookie = true;
 
@@ -14915,10 +15151,17 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
 /* check validity of 32-bit and 64-bit arithmetic operations */
 static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
 {
+	struct bpf_func_state *state = cur_func(env);
 	struct bpf_reg_state *regs = cur_regs(env);
+	struct bpf_reg_state old_dst = {};
 	u8 opcode = BPF_OP(insn->code);
+	bool have_old_dst;
 	int err;
 
+	have_old_dst = insn->dst_reg < MAX_BPF_REG;
+	if (have_old_dst)
+		old_dst = regs[insn->dst_reg];
+
 	if (opcode == BPF_END || opcode == BPF_NEG) {
 		/* check src operand */
 		err = check_reg_arg(env, insn->dst_reg, SRC_OP);
@@ -15099,7 +15342,17 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
 			return err;
 	}
 
-	return reg_bounds_sanity_check(env, &regs[insn->dst_reg], "alu");
+	err = reg_bounds_sanity_check(env, &regs[insn->dst_reg], "alu");
+	if (err)
+		return err;
+
+	if (have_old_dst)
+		bpf_diag_record_reg_mod(env, env->insn_idx, state->frameno,
+					insn->dst_reg,
+					BPF_SRC(insn->code) == BPF_X,
+					insn->src_reg, opcode, &old_dst,
+					&regs[insn->dst_reg]);
+	return 0;
 }
 
 static void find_good_pkt_pointers(struct bpf_verifier_state *vstate,
-- 
2.53.0


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

* [PATCH bpf-next v2 06/17] bpf: Track verifier reference diagnostic events
  2026-06-19 20:59 [PATCH bpf-next v2 00/17] Redesign Verification Errors Kumar Kartikeya Dwivedi
                   ` (4 preceding siblings ...)
  2026-06-19 20:59 ` [PATCH bpf-next v2 05/17] bpf: Track verifier register diagnostic events Kumar Kartikeya Dwivedi
@ 2026-06-19 20:59 ` Kumar Kartikeya Dwivedi
  2026-06-19 20:59 ` [PATCH bpf-next v2 07/17] bpf: Track verifier context " Kumar Kartikeya Dwivedi
                   ` (10 subsequent siblings)
  16 siblings, 0 replies; 34+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-06-19 20:59 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team

Add reference acquire and release events to diagnostic history so Resource
Lifetime Safety reports can show the lifetime of a specific reference id along
the path.

Record acquisitions after the verifier assigns the reference id. Record
releases only after release_reference_nomark() succeeds, including the
kptr_xchg RCU conversion path and owning-to-non-owning conversion path that
consume an owning reference.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 kernel/bpf/diagnostics.c | 53 ++++++++++++++++++++++++++++++++++++++++
 kernel/bpf/diagnostics.h | 11 +++++++++
 kernel/bpf/verifier.c    | 18 +++++++++++---
 3 files changed, 79 insertions(+), 3 deletions(-)

diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
index f51b2860c11d..58cc7c18cf98 100644
--- a/kernel/bpf/diagnostics.c
+++ b/kernel/bpf/diagnostics.c
@@ -1015,6 +1015,32 @@ void bpf_diag_record_stack_slot(struct bpf_verifier_env *env, u32 insn_idx,
 	bpf_diag_append_history(env, &event);
 }
 
+static void bpf_diag_record_ref(struct bpf_verifier_env *env, u32 insn_idx,
+				u8 kind, u32 ref_id)
+{
+	struct bpf_diag_history_event event = {
+		.insn_idx = insn_idx,
+		.kind = kind,
+		.ref.ref_id = ref_id,
+	};
+
+	bpf_diag_append_history(env, &event);
+}
+
+void bpf_diag_record_ref_acquire(struct bpf_verifier_env *env, u32 insn_idx,
+				 u32 ref_id)
+{
+	bpf_diag_record_ref(env, insn_idx, BPF_DIAG_HISTORY_REF_ACQUIRE,
+			    ref_id);
+}
+
+void bpf_diag_record_ref_release(struct bpf_verifier_env *env, u32 insn_idx,
+				 u32 ref_id)
+{
+	bpf_diag_record_ref(env, insn_idx, BPF_DIAG_HISTORY_REF_RELEASE,
+			    ref_id);
+}
+
 struct bpf_diag_history_filter {
 	const struct bpf_diag_history_opts *opts;
 	bool stack_slot_valid;
@@ -1136,6 +1162,10 @@ static int bpf_diag_history_start_idx(const struct bpf_diag_log *log,
 		    event->stack_arg.slot == opts->stack_arg_slot &&
 		    event->stack_arg.frameno == opts->frameno)
 			return i - 1;
+		if (opts->scope == BPF_DIAG_HISTORY_SCOPE_REF &&
+		    event->kind == BPF_DIAG_HISTORY_REF_ACQUIRE &&
+		    event->ref.ref_id == opts->ref_id)
+			return i - 1;
 	}
 
 	return 0;
@@ -1166,6 +1196,10 @@ bpf_diag_history_event_visible(const struct bpf_diag_history_event *event,
 		return filter->stack_slot_valid &&
 		       idx <= filter->stack_until_idx &&
 		       bpf_diag_stack_slot_matches(event, filter);
+	case BPF_DIAG_HISTORY_REF_ACQUIRE:
+	case BPF_DIAG_HISTORY_REF_RELEASE:
+		return opts->scope == BPF_DIAG_HISTORY_SCOPE_REF &&
+		       event->ref.ref_id == opts->ref_id;
 	default:
 		return false;
 	}
@@ -1536,6 +1570,20 @@ static void bpf_diag_print_stack_slot(struct bpf_verifier_env *env,
 			       off, fmt->old_buf, fmt->new_buf);
 }
 
+static void bpf_diag_print_ref_event(struct bpf_verifier_env *env,
+				     const struct bpf_diag_history_event *event)
+{
+	if (event->kind == BPF_DIAG_HISTORY_REF_ACQUIRE) {
+		bpf_diag_report_source(env, event->insn_idx, "acquired",
+				       "owned resource (id=%u)",
+				       event->ref.ref_id);
+		return;
+	}
+
+	bpf_diag_report_source(env, event->insn_idx, "released",
+			       "owned resource (id=%u)", event->ref.ref_id);
+}
+
 void bpf_diag_print_history(struct bpf_verifier_env *env,
 			    const struct bpf_diag_history_opts *opts)
 {
@@ -1585,6 +1633,11 @@ void bpf_diag_print_history(struct bpf_verifier_env *env,
 			bpf_diag_print_stack_slot(env, event);
 			printed = true;
 			break;
+		case BPF_DIAG_HISTORY_REF_ACQUIRE:
+		case BPF_DIAG_HISTORY_REF_RELEASE:
+			bpf_diag_print_ref_event(env, event);
+			printed = true;
+			break;
 		default:
 			break;
 		}
diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
index 7af0a694890b..af8b738f7087 100644
--- a/kernel/bpf/diagnostics.h
+++ b/kernel/bpf/diagnostics.h
@@ -80,6 +80,9 @@ struct bpf_diag_history_event {
 			u8 reason;
 			struct bpf_diag_reg_snapshot old, new;
 		} stack_slot;
+		struct {
+			u32 ref_id;
+		} ref;
 	};
 };
 
@@ -88,12 +91,15 @@ enum bpf_diag_history_kind {
 	BPF_DIAG_HISTORY_REG_MOD,
 	BPF_DIAG_HISTORY_STACK_ARG,
 	BPF_DIAG_HISTORY_STACK_SLOT,
+	BPF_DIAG_HISTORY_REF_ACQUIRE,
+	BPF_DIAG_HISTORY_REF_RELEASE,
 };
 
 enum bpf_diag_history_scope {
 	BPF_DIAG_HISTORY_SCOPE_ALL,
 	BPF_DIAG_HISTORY_SCOPE_REG,
 	BPF_DIAG_HISTORY_SCOPE_STACK_ARG,
+	BPF_DIAG_HISTORY_SCOPE_REF,
 };
 
 struct bpf_diag_history_opts {
@@ -101,6 +107,7 @@ struct bpf_diag_history_opts {
 	u32 frameno;
 	int regno;
 	int stack_arg_slot;
+	u32 ref_id;
 };
 
 bool bpf_diag_enabled(const struct bpf_verifier_env *env);
@@ -153,6 +160,10 @@ void bpf_diag_record_stack_slot(struct bpf_verifier_env *env, u32 insn_idx,
 				enum bpf_diag_stack_slot_reason reason,
 				const struct bpf_reg_state *old_reg,
 				const struct bpf_reg_state *new_reg);
+void bpf_diag_record_ref_acquire(struct bpf_verifier_env *env, u32 insn_idx,
+				 u32 ref_id);
+void bpf_diag_record_ref_release(struct bpf_verifier_env *env, u32 insn_idx,
+				 u32 ref_id);
 void bpf_diag_print_history(struct bpf_verifier_env *env,
 			    const struct bpf_diag_history_opts *opts);
 
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index fedabb6bb515..93941deb2cd8 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -1438,6 +1438,7 @@ static int acquire_reference(struct bpf_verifier_env *env, int insn_idx, int par
 	s->type = REF_TYPE_PTR;
 	s->id = ++env->id_gen;
 	s->parent_id = parent_id;
+	bpf_diag_record_ref_acquire(env, insn_idx, s->id);
 	return s->id;
 }
 
@@ -9140,8 +9141,12 @@ static int release_reference(struct bpf_verifier_env *env, int id)
 	if (err)
 		return err;
 
-	if (find_reference_state(vstate, id))
-		WARN_ON_ONCE(release_reference_nomark(vstate, id));
+	if (find_reference_state(vstate, id)) {
+		err = release_reference_nomark(vstate, id);
+		WARN_ON_ONCE(err);
+		if (!err)
+			bpf_diag_record_ref_release(env, env->insn_idx, id);
+	}
 
 	while ((id = idstack_pop(idstack))) {
 		/*
@@ -9263,6 +9268,9 @@ static int ref_convert_alloc_rcu_protected(struct bpf_verifier_env *env, u32 id)
 	int err;
 
 	err = release_reference_nomark(env->cur_state, id);
+	if (err)
+		return err;
+	bpf_diag_record_ref_release(env, env->insn_idx, id);
 
 	bpf_for_each_reg_in_vstate(env->cur_state, state, reg, ({
 		if (reg->id != id)
@@ -11746,8 +11754,12 @@ static void ref_convert_owning_non_owning(struct bpf_verifier_env *env, u32 id)
 {
 	struct bpf_func_state *unused;
 	struct bpf_reg_state *reg;
+	int err;
 
-	WARN_ON_ONCE(release_reference_nomark(env->cur_state, id));
+	err = release_reference_nomark(env->cur_state, id);
+	WARN_ON_ONCE(err);
+	if (!err)
+		bpf_diag_record_ref_release(env, env->insn_idx, id);
 
 	bpf_for_each_reg_in_vstate(env->cur_state, unused, reg, ({
 		if (reg->id == id) {
-- 
2.53.0


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

* [PATCH bpf-next v2 07/17] bpf: Track verifier context diagnostic events
  2026-06-19 20:59 [PATCH bpf-next v2 00/17] Redesign Verification Errors Kumar Kartikeya Dwivedi
                   ` (5 preceding siblings ...)
  2026-06-19 20:59 ` [PATCH bpf-next v2 06/17] bpf: Track verifier reference " Kumar Kartikeya Dwivedi
@ 2026-06-19 20:59 ` Kumar Kartikeya Dwivedi
  2026-06-19 21:13   ` sashiko-bot
  2026-06-19 21:46   ` bot+bpf-ci
  2026-06-19 20:59 ` [PATCH bpf-next v2 08/17] bpf: Report Register Type Safety errors Kumar Kartikeya Dwivedi
                   ` (9 subsequent siblings)
  16 siblings, 2 replies; 34+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-06-19 20:59 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team

Record verifier context transitions in the diagnostic history so later reports
can anchor causal paths to the critical section that made an operation invalid.

This covers lock, IRQ, RCU, and preempt regions without adding any new
verifier error reports. Category-specific commits decide where those recorded
events should be rendered.

Use context depth when selecting scoped history so nested regions anchor at the
outer active region, and fall back to the earliest retained event when the
matching entry was pruned.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 kernel/bpf/diagnostics.c | 85 +++++++++++++++++++++++++++++++++++++++-
 kernel/bpf/diagnostics.h | 20 ++++++++++
 kernel/bpf/verifier.c    | 35 ++++++++++++++---
 3 files changed, 133 insertions(+), 7 deletions(-)

diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
index 58cc7c18cf98..d6a9a2315f54 100644
--- a/kernel/bpf/diagnostics.c
+++ b/kernel/bpf/diagnostics.c
@@ -1041,6 +1041,53 @@ void bpf_diag_record_ref_release(struct bpf_verifier_env *env, u32 insn_idx,
 			    ref_id);
 }
 
+void bpf_diag_record_context(struct bpf_verifier_env *env, u32 insn_idx,
+			     enum bpf_diag_context_kind ctx_kind, bool enter,
+			     u32 depth)
+{
+	/* Keep leave events so context rendering can stop at a depth-zero exit
+	 * and show nested-region depth accurately for the active path.
+	 */
+	struct bpf_diag_history_event event = {
+		.insn_idx = insn_idx,
+		.kind = BPF_DIAG_HISTORY_CONTEXT,
+		.ctx.kind = ctx_kind,
+		.ctx.enter = enter,
+		.ctx.depth = depth,
+	};
+
+	if (ctx_kind == BPF_DIAG_CONTEXT_NONE)
+		return;
+
+	bpf_diag_append_history(env, &event);
+}
+
+static int bpf_diag_history_context_start_idx(const struct bpf_diag_log *log,
+					      const struct bpf_diag_history_opts *opts)
+{
+	int i;
+
+	if (!opts->ctx_depth)
+		return 0;
+
+	for (i = log->cnt; i > 0; i--) {
+		const struct bpf_diag_history_event *event;
+
+		event = bpf_diag_history_event(log, i - 1);
+
+		if (event->kind != BPF_DIAG_HISTORY_CONTEXT ||
+		    event->ctx.kind != opts->ctx_kind)
+			continue;
+
+		if (event->ctx.enter && event->ctx.depth == 1)
+			return i - 1;
+		if (!event->ctx.enter && event->ctx.depth == 0)
+			return 0;
+	}
+
+	return 0;
+}
+
 struct bpf_diag_history_filter {
 	const struct bpf_diag_history_opts *opts;
 	bool stack_slot_valid;
@@ -1147,6 +1194,8 @@ static int bpf_diag_history_start_idx(const struct bpf_diag_log *log,
 
 	if (!opts || opts->scope == BPF_DIAG_HISTORY_SCOPE_ALL)
 		return 0;
+	if (opts->scope == BPF_DIAG_HISTORY_SCOPE_CONTEXT)
+		return bpf_diag_history_context_start_idx(log, opts);
 	if (filter->stack_slot_valid)
 		return bpf_diag_history_stack_start_idx(log, filter);
 
@@ -1179,7 +1228,7 @@ bpf_diag_history_event_visible(const struct bpf_diag_history_event *event,
 	const struct bpf_diag_history_opts *opts = filter->opts;
 
 	if (!opts || opts->scope == BPF_DIAG_HISTORY_SCOPE_ALL)
-		return true;
+		return event->kind != BPF_DIAG_HISTORY_CONTEXT;
 
 	switch (event->kind) {
 	case BPF_DIAG_HISTORY_BRANCH:
@@ -1200,6 +1249,9 @@ bpf_diag_history_event_visible(const struct bpf_diag_history_event *event,
 	case BPF_DIAG_HISTORY_REF_RELEASE:
 		return opts->scope == BPF_DIAG_HISTORY_SCOPE_REF &&
 		       event->ref.ref_id == opts->ref_id;
+	case BPF_DIAG_HISTORY_CONTEXT:
+		return opts->scope == BPF_DIAG_HISTORY_SCOPE_CONTEXT &&
+		       event->ctx.kind == opts->ctx_kind;
 	default:
 		return false;
 	}
@@ -1584,6 +1636,33 @@ static void bpf_diag_print_ref_event(struct bpf_verifier_env *env,
 			       "owned resource (id=%u)", event->ref.ref_id);
 }
 
+static const char *bpf_diag_context_name(enum bpf_diag_context_kind kind)
+{
+	switch (kind) {
+	case BPF_DIAG_CONTEXT_RCU:
+		return "RCU read lock region";
+	case BPF_DIAG_CONTEXT_PREEMPT:
+		return "non-preemptible region";
+	case BPF_DIAG_CONTEXT_IRQ:
+		return "IRQ-disabled region";
+	case BPF_DIAG_CONTEXT_LOCK:
+		return "lock region";
+	case BPF_DIAG_CONTEXT_NONE:
+	default:
+		return "context";
+	}
+}
+
+static void bpf_diag_print_context_event(struct bpf_verifier_env *env,
+					 const struct bpf_diag_history_event *event)
+{
+	bpf_diag_report_source(env, event->insn_idx, "context",
+			       "%s %s; depth is now %u",
+			       event->ctx.enter ? "entered" : "left",
+			       bpf_diag_context_name(event->ctx.kind),
+			       event->ctx.depth);
+}
+
 void bpf_diag_print_history(struct bpf_verifier_env *env,
 			    const struct bpf_diag_history_opts *opts)
 {
@@ -1638,6 +1717,10 @@ void bpf_diag_print_history(struct bpf_verifier_env *env,
 			bpf_diag_print_ref_event(env, event);
 			printed = true;
 			break;
+		case BPF_DIAG_HISTORY_CONTEXT:
+			bpf_diag_print_context_event(env, event);
+			printed = true;
+			break;
 		default:
 			break;
 		}
diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
index af8b738f7087..684574388343 100644
--- a/kernel/bpf/diagnostics.h
+++ b/kernel/bpf/diagnostics.h
@@ -83,6 +83,11 @@ struct bpf_diag_history_event {
 		struct {
 			u32 ref_id;
 		} ref;
+		struct {
+			u8 kind;
+			bool enter;
+			u32 depth;
+		} ctx;
 	};
 };
 
@@ -93,6 +98,7 @@ enum bpf_diag_history_kind {
 	BPF_DIAG_HISTORY_STACK_SLOT,
 	BPF_DIAG_HISTORY_REF_ACQUIRE,
 	BPF_DIAG_HISTORY_REF_RELEASE,
+	BPF_DIAG_HISTORY_CONTEXT,
 };
 
 enum bpf_diag_history_scope {
@@ -100,6 +106,15 @@ enum bpf_diag_history_scope {
 	BPF_DIAG_HISTORY_SCOPE_REG,
 	BPF_DIAG_HISTORY_SCOPE_STACK_ARG,
 	BPF_DIAG_HISTORY_SCOPE_REF,
+	BPF_DIAG_HISTORY_SCOPE_CONTEXT,
+};
+
+enum bpf_diag_context_kind {
+	BPF_DIAG_CONTEXT_NONE,
+	BPF_DIAG_CONTEXT_RCU,
+	BPF_DIAG_CONTEXT_PREEMPT,
+	BPF_DIAG_CONTEXT_IRQ,
+	BPF_DIAG_CONTEXT_LOCK,
 };
 
 struct bpf_diag_history_opts {
@@ -108,6 +123,8 @@ struct bpf_diag_history_opts {
 	int regno;
 	int stack_arg_slot;
 	u32 ref_id;
+	enum bpf_diag_context_kind ctx_kind;
+	u32 ctx_depth;
 };
 
 bool bpf_diag_enabled(const struct bpf_verifier_env *env);
@@ -164,6 +181,9 @@ void bpf_diag_record_ref_acquire(struct bpf_verifier_env *env, u32 insn_idx,
 				 u32 ref_id);
 void bpf_diag_record_ref_release(struct bpf_verifier_env *env, u32 insn_idx,
 				 u32 ref_id);
+void bpf_diag_record_context(struct bpf_verifier_env *env, u32 insn_idx,
+			     enum bpf_diag_context_kind ctx_kind, bool enter,
+			     u32 depth);
 void bpf_diag_print_history(struct bpf_verifier_env *env,
 			    const struct bpf_diag_history_opts *opts);
 
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 93941deb2cd8..e584dec04b34 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -1064,7 +1064,7 @@ static int is_iter_reg_valid_init(struct bpf_verifier_env *env, struct bpf_reg_s
 }
 
 static int acquire_irq_state(struct bpf_verifier_env *env, int insn_idx);
-static int release_irq_state(struct bpf_verifier_state *state, int id);
+static int release_irq_state(struct bpf_verifier_env *env, int id);
 
 static int mark_stack_slot_irq_flag(struct bpf_verifier_env *env,
 				     struct bpf_kfunc_call_arg_meta *meta,
@@ -1123,7 +1123,7 @@ static int unmark_stack_slot_irq_flag(struct bpf_verifier_env *env, struct bpf_r
 		return -EINVAL;
 	}
 
-	err = release_irq_state(env->cur_state, st->id);
+	err = release_irq_state(env, st->id);
 	WARN_ON_ONCE(err && err != -EACCES);
 	if (err) {
 		int insn_idx = 0;
@@ -1458,6 +1458,8 @@ static int acquire_lock_state(struct bpf_verifier_env *env, int insn_idx, enum r
 	state->active_locks++;
 	state->active_lock_id = id;
 	state->active_lock_ptr = ptr;
+	bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_LOCK, true,
+				state->active_locks);
 	return 0;
 }
 
@@ -1473,6 +1475,7 @@ static int acquire_irq_state(struct bpf_verifier_env *env, int insn_idx)
 	s->id = ++env->id_gen;
 
 	state->active_irq_id = s->id;
+	bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_IRQ, true, 1);
 	return s->id;
 }
 
@@ -1514,8 +1517,10 @@ static bool reg_is_referenced(struct bpf_verifier_env *env, const struct bpf_reg
 	return find_reference_state(env->cur_state, reg->id);
 }
 
-static int release_lock_state(struct bpf_verifier_state *state, int type, int id, void *ptr)
+static int release_lock_state(struct bpf_verifier_env *env, int type, int id,
+			      void *ptr)
 {
+	struct bpf_verifier_state *state = env->cur_state;
 	void *prev_ptr = NULL;
 	u32 prev_id = 0;
 	int i;
@@ -1528,6 +1533,9 @@ static int release_lock_state(struct bpf_verifier_state *state, int type, int id
 			/* Reassign active lock (id, ptr). */
 			state->active_lock_id = prev_id;
 			state->active_lock_ptr = prev_ptr;
+			bpf_diag_record_context(env, env->insn_idx,
+						BPF_DIAG_CONTEXT_LOCK, false,
+						state->active_locks);
 			return 0;
 		}
 		if (state->refs[i].type & REF_TYPE_LOCK_MASK) {
@@ -1538,8 +1546,9 @@ static int release_lock_state(struct bpf_verifier_state *state, int type, int id
 	return -EINVAL;
 }
 
-static int release_irq_state(struct bpf_verifier_state *state, int id)
+static int release_irq_state(struct bpf_verifier_env *env, int id)
 {
+	struct bpf_verifier_state *state = env->cur_state;
 	u32 prev_id = 0;
 	int i;
 
@@ -1552,6 +1561,9 @@ static int release_irq_state(struct bpf_verifier_state *state, int id)
 		if (state->refs[i].id == id) {
 			release_reference_state(state, i);
 			state->active_irq_id = prev_id;
+			bpf_diag_record_context(env, env->insn_idx,
+						BPF_DIAG_CONTEXT_IRQ, false,
+						state->active_irq_id ? 1 : 0);
 			return 0;
 		} else {
 			prev_id = state->refs[i].id;
@@ -7209,7 +7221,7 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state
 			verbose(env, "%s_unlock cannot be out of order\n", lock_str);
 			return -EINVAL;
 		}
-		if (release_lock_state(cur, type, reg->id, ptr)) {
+		if (release_lock_state(env, type, reg->id, ptr)) {
 			verbose(env, "%s_unlock of different lock\n", lock_str);
 			return -EINVAL;
 		}
@@ -13290,21 +13302,32 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 
 	if (rcu_lock) {
 		env->cur_state->active_rcu_locks++;
+		bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_RCU,
+					true, env->cur_state->active_rcu_locks);
 	} else if (rcu_unlock) {
 		if (env->cur_state->active_rcu_locks == 0) {
 			verbose(env, "unmatched rcu read unlock (kernel function %s)\n", func_name);
 			return -EINVAL;
 		}
-		if (--env->cur_state->active_rcu_locks == 0)
+		env->cur_state->active_rcu_locks--;
+		bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_RCU,
+					false, env->cur_state->active_rcu_locks);
+		if (env->cur_state->active_rcu_locks == 0)
 			invalidate_rcu_protected_refs(env);
 	} else if (preempt_disable) {
 		env->cur_state->active_preempt_locks++;
+		bpf_diag_record_context(env, insn_idx,
+					BPF_DIAG_CONTEXT_PREEMPT, true,
+					env->cur_state->active_preempt_locks);
 	} else if (preempt_enable) {
 		if (env->cur_state->active_preempt_locks == 0) {
 			verbose(env, "unmatched attempt to enable preemption (kernel function %s)\n", func_name);
 			return -EINVAL;
 		}
 		env->cur_state->active_preempt_locks--;
+		bpf_diag_record_context(env, insn_idx,
+					BPF_DIAG_CONTEXT_PREEMPT, false,
+					env->cur_state->active_preempt_locks);
 	}
 
 	if (sleepable && !in_sleepable_context(env)) {
-- 
2.53.0


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

* [PATCH bpf-next v2 08/17] bpf: Report Register Type Safety errors
  2026-06-19 20:59 [PATCH bpf-next v2 00/17] Redesign Verification Errors Kumar Kartikeya Dwivedi
                   ` (6 preceding siblings ...)
  2026-06-19 20:59 ` [PATCH bpf-next v2 07/17] bpf: Track verifier context " Kumar Kartikeya Dwivedi
@ 2026-06-19 20:59 ` Kumar Kartikeya Dwivedi
  2026-06-19 20:59 ` [PATCH bpf-next v2 09/17] bpf: Report Memory Safety bounds errors Kumar Kartikeya Dwivedi
                   ` (8 subsequent siblings)
  16 siblings, 0 replies; 34+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-06-19 20:59 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team

Augment selected register-state verifier failures with Register Type Safety
reports. The existing verbose verifier messages remain in place; the new
reports add reason, source context, causal path, and suggestions.

Cover invalid pointer dereferences, unreadable registers, missing outgoing
stack arguments for bpf2bpf and kfunc calls, and rejected pointer arithmetic.
Use scoped diagnostic history so reports start from the latest relevant value
change and then show later branch outcomes.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 kernel/bpf/diagnostics.c | 234 ++++++++++++++++++++++++++++++++++++++-
 kernel/bpf/diagnostics.h |  22 ++++
 kernel/bpf/verifier.c    | 178 +++++++++++++++++++++++++++--
 3 files changed, 423 insertions(+), 11 deletions(-)

diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
index d6a9a2315f54..92a4aef3cc90 100644
--- a/kernel/bpf/diagnostics.c
+++ b/kernel/bpf/diagnostics.c
@@ -837,6 +837,217 @@ void bpf_diag_report_source(struct bpf_verifier_env *env, u32 insn_idx,
 	kfree(msg);
 }
 
+static u32 bpf_diag_current_frameno(const struct bpf_verifier_env *env)
+{
+	return env->cur_state->frame[env->cur_state->curframe]->frameno;
+}
+
+static int bpf_diag_stack_argno(u8 slot);
+
+void bpf_diag_report_register_type(struct bpf_verifier_env *env,
+				   u32 insn_idx, int regno,
+				   const char *problem, const char *reason,
+				   const char *suggestion)
+{
+	struct bpf_diag_history_opts opts = {
+		.scope = BPF_DIAG_HISTORY_SCOPE_REG,
+		.frameno = bpf_diag_current_frameno(env),
+		.regno = regno,
+	};
+
+	bpf_diag_report_header(env, BPF_DIAG_CATEGORY_REGISTER_TYPE_SAFETY,
+			       problem);
+	bpf_diag_report_reason(env, "%s", reason);
+
+	bpf_diag_report_section(env, "At");
+	bpf_diag_report_source(env, insn_idx, "error", "%s", problem);
+
+	if (regno >= 0)
+		bpf_diag_print_history(env, &opts);
+
+	bpf_diag_report_suggestion(env, "%s", suggestion);
+}
+
+static const char *bpf_diag_arg_ordinal(int argno)
+{
+	switch (argno) {
+	case 1:
+		return "first";
+	case 2:
+		return "second";
+	case 3:
+		return "third";
+	case 4:
+		return "fourth";
+	case 5:
+		return "fifth";
+	case 6:
+		return "sixth";
+	case 7:
+		return "seventh";
+	case 8:
+		return "eighth";
+	case 9:
+		return "ninth";
+	case 10:
+		return "tenth";
+	case 11:
+		return "eleventh";
+	case 12:
+		return "twelfth";
+	default:
+		return NULL;
+	}
+}
+
+void bpf_diag_report_invalid_deref(struct bpf_verifier_env *env, u32 insn_idx,
+				   int regno, const char *reg_name,
+				   const char *type_name,
+				   enum bpf_diag_invalid_deref_kind kind,
+				   s64 offset)
+{
+	struct bpf_diag_history_opts opts = {
+		.scope = BPF_DIAG_HISTORY_SCOPE_REG,
+		.frameno = bpf_diag_current_frameno(env),
+		.regno = regno,
+	};
+
+	bpf_diag_report_header(env, BPF_DIAG_CATEGORY_REGISTER_TYPE_SAFETY,
+			       "invalid dereference");
+
+	switch (kind) {
+	case BPF_DIAG_DEREF_SCALAR:
+		bpf_diag_report_reason(env,
+				       "%s is an integer scalar here, not a pointer to memory.",
+				       reg_name);
+		break;
+	case BPF_DIAG_DEREF_NULLABLE_PTR:
+		bpf_diag_report_reason(env,
+				       "%s may be NULL here (%s). The program could dereference NULL on this path, so the verifier cannot prove this access is safe.",
+				       reg_name, type_name);
+		break;
+	case BPF_DIAG_DEREF_MODIFIED_PTR:
+		bpf_diag_report_reason(env,
+				       "%s has offset %lld here, but this pointer type must be dereferenced in its original form.",
+				       reg_name, offset);
+		break;
+	case BPF_DIAG_DEREF_INVALID_PTR:
+	default:
+		bpf_diag_report_reason(env,
+				       "%s has type %s here, which is not valid for this memory access.",
+				       reg_name, type_name);
+		break;
+	}
+
+	bpf_diag_report_section(env, "At");
+	if (kind == BPF_DIAG_DEREF_MODIFIED_PTR)
+		bpf_diag_report_source(env, insn_idx, "error",
+				       "dereference requires the original %s pointer",
+				       type_name);
+	else
+		bpf_diag_report_source(env, insn_idx, "error",
+				       "invalid dereference of %s (%s)",
+				       reg_name, type_name);
+
+	if (regno >= 0)
+		bpf_diag_print_history(env, &opts);
+
+	switch (kind) {
+	case BPF_DIAG_DEREF_NULLABLE_PTR:
+		bpf_diag_report_suggestion(env,
+					   "Add a NULL check before the access and dereference the pointer only on the non-NULL path.");
+		break;
+	case BPF_DIAG_DEREF_MODIFIED_PTR:
+		bpf_diag_report_suggestion(env,
+					   "Preserve the original pointer in another register, or use only offsets this pointer type permits before dereferencing it.");
+		break;
+	case BPF_DIAG_DEREF_SCALAR:
+	case BPF_DIAG_DEREF_INVALID_PTR:
+	default:
+		bpf_diag_report_suggestion(env,
+					   "Preserve a pointer-valued register where needed, or reload and revalidate the pointer after scalar arithmetic, helper calls, or other operations that can invalidate it.");
+		break;
+	}
+}
+
+void bpf_diag_report_unreadable_reg(struct bpf_verifier_env *env,
+				    u32 insn_idx, int regno)
+{
+	struct bpf_diag_history_opts opts = {
+		.scope = BPF_DIAG_HISTORY_SCOPE_REG,
+		.frameno = bpf_diag_current_frameno(env),
+		.regno = regno,
+	};
+
+	bpf_diag_report_header(env, BPF_DIAG_CATEGORY_REGISTER_TYPE_SAFETY,
+			       "unreadable register");
+	bpf_diag_report_reason(env,
+			       "R%d is not readable here. A previous operation may have invalidated this register, so the verifier cannot use it as an input.",
+			       regno);
+
+	bpf_diag_report_section(env, "At");
+	bpf_diag_report_source(env, insn_idx, "error",
+			       "R%d is not readable", regno);
+
+	if (regno >= 0)
+		bpf_diag_print_history(env, &opts);
+
+	bpf_diag_report_suggestion(env,
+				   "Avoid using the register after it is invalidated, or reload and revalidate a fresh pointer before this instruction.");
+}
+
+static void bpf_diag_format_stack_arg(char *buf, size_t size, u8 slot)
+{
+	int argno = bpf_diag_stack_argno(slot);
+	const char *ordinal = bpf_diag_arg_ordinal(argno);
+
+	if (ordinal)
+		scnprintf(buf, size, "outgoing stack argument %u (%s argument)",
+			  slot + 1, ordinal);
+	else
+		scnprintf(buf, size, "outgoing stack argument %u", slot + 1);
+}
+
+void bpf_diag_report_stack_arg_uninit(struct bpf_verifier_env *env,
+				      u32 insn_idx, int nargs,
+				      int stack_arg_slot,
+				      const char *callee_name)
+{
+	struct bpf_diag_history_opts opts = {
+		.scope = BPF_DIAG_HISTORY_SCOPE_STACK_ARG,
+		.frameno = bpf_diag_current_frameno(env),
+		.stack_arg_slot = stack_arg_slot,
+	};
+	const char *arg_buf;
+
+	arg_buf = bpf_diag_scratch_buf(env, 1, NULL);
+	if (arg_buf)
+		bpf_diag_format_stack_arg((char *)arg_buf, BPF_DIAG_SCRATCH_STR_LEN,
+					  stack_arg_slot);
+	else
+		arg_buf = "";
+	bpf_diag_report_header(env, BPF_DIAG_CATEGORY_REGISTER_TYPE_SAFETY,
+			       "missing stack argument");
+	if (callee_name && *callee_name)
+		bpf_diag_report_reason(env,
+				       "Function %s expects %d arguments, but %s is not initialized at this call.",
+				       callee_name, nargs, arg_buf);
+	else
+		bpf_diag_report_reason(env,
+				       "The callee expects %d arguments, but %s is not initialized at this call.",
+				       nargs, arg_buf);
+
+	bpf_diag_report_section(env, "At");
+	bpf_diag_report_source(env, insn_idx, "error",
+			       "%s is not initialized", arg_buf);
+
+	if (stack_arg_slot >= 0)
+		bpf_diag_print_history(env, &opts);
+
+	bpf_diag_report_suggestion(env,
+				   "Write the outgoing stack argument after any operation that may invalidate stored pointer values, and before making this call.");
+}
+
 void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx,
 			    bool cond_true)
 {
@@ -1726,7 +1937,24 @@ void bpf_diag_print_history(struct bpf_verifier_env *env,
 		}
 	}
 
-	if (!printed)
-		bpf_diag_write(env,
-			       "  no retained diagnostic events on this path\n");
+	if (!printed) {
+		if (opts && opts->scope == BPF_DIAG_HISTORY_SCOPE_STACK_ARG &&
+		    opts->stack_arg_slot >= 0) {
+			const char *arg_buf;
+
+			arg_buf = bpf_diag_scratch_buf(env, 0, NULL);
+			if (arg_buf)
+				bpf_diag_format_stack_arg((char *)arg_buf,
+							  BPF_DIAG_SCRATCH_STR_LEN,
+							  opts->stack_arg_slot);
+			else
+				arg_buf = "this outgoing stack argument";
+			bpf_diag_write(env,
+				       "  no retained writes for %s on this path\n",
+				       arg_buf);
+		} else {
+			bpf_diag_write(env,
+				       "  no retained diagnostic events on this path\n");
+		}
+	}
 }
diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
index 684574388343..d6e858cd39f2 100644
--- a/kernel/bpf/diagnostics.h
+++ b/kernel/bpf/diagnostics.h
@@ -117,6 +117,13 @@ enum bpf_diag_context_kind {
 	BPF_DIAG_CONTEXT_LOCK,
 };
 
+enum bpf_diag_invalid_deref_kind {
+	BPF_DIAG_DEREF_SCALAR,
+	BPF_DIAG_DEREF_NULLABLE_PTR,
+	BPF_DIAG_DEREF_MODIFIED_PTR,
+	BPF_DIAG_DEREF_INVALID_PTR,
+};
+
 struct bpf_diag_history_opts {
 	enum bpf_diag_history_scope scope;
 	u32 frameno;
@@ -149,6 +156,21 @@ void bpf_diag_report_header(struct bpf_verifier_env *env,
 void bpf_diag_report_source(struct bpf_verifier_env *env, u32 insn_idx,
 			    const char *label, const char *fmt, ...)
 	__printf(4, 5);
+void bpf_diag_report_register_type(struct bpf_verifier_env *env,
+				   u32 insn_idx, int regno,
+				   const char *problem, const char *reason,
+				   const char *suggestion);
+void bpf_diag_report_invalid_deref(struct bpf_verifier_env *env, u32 insn_idx,
+				   int regno, const char *reg_name,
+				   const char *type_name,
+				   enum bpf_diag_invalid_deref_kind kind,
+				   s64 offset);
+void bpf_diag_report_unreadable_reg(struct bpf_verifier_env *env,
+				    u32 insn_idx, int regno);
+void bpf_diag_report_stack_arg_uninit(struct bpf_verifier_env *env,
+				      u32 insn_idx, int nargs,
+				      int stack_arg_slot,
+				      const char *callee_name);
 void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx,
 			    bool cond_true);
 void bpf_diag_record_reg_mod(struct bpf_verifier_env *env, u32 insn_idx,
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index e584dec04b34..2c5f24528071 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -3145,6 +3145,8 @@ static int __check_reg_arg(struct bpf_verifier_env *env, struct bpf_reg_state *r
 		/* check whether register used as source operand can be read */
 		if (reg->type == NOT_INIT) {
 			verbose(env, "R%d !read_ok\n", regno);
+			bpf_diag_report_unreadable_reg(env, env->insn_idx,
+						       regno);
 			return -EACCES;
 		}
 		/* We don't need to worry about FP liveness because it's read-only */
@@ -4197,8 +4199,9 @@ static int mark_stack_arg_precision(struct bpf_verifier_env *env, int arg_idx)
 	return mark_chain_precision_batch(env, env->cur_state);
 }
 
-static int check_outgoing_stack_args(struct bpf_verifier_env *env, struct bpf_func_state *caller,
-				     int nargs)
+static int check_outgoing_stack_args(struct bpf_verifier_env *env,
+				     struct bpf_func_state *caller, int nargs,
+				     const char *callee_name)
 {
 	int i, spi;
 
@@ -4208,6 +4211,9 @@ static int check_outgoing_stack_args(struct bpf_verifier_env *env, struct bpf_fu
 		    caller->stack_arg_regs[spi].type == NOT_INIT) {
 			verbose(env, "callee expects %d args, stack arg%d is not initialized\n",
 				nargs, spi + 1);
+			bpf_diag_report_stack_arg_uninit(env, env->insn_idx,
+							 nargs, spi,
+							 callee_name);
 			return -EFAULT;
 		}
 	}
@@ -4362,6 +4368,12 @@ static int __check_ptr_off_reg(struct bpf_verifier_env *env,
 	if (!fixed_off_ok && reg->var_off.value != 0) {
 		verbose(env, "dereference of modified %s ptr %s off=%lld disallowed\n",
 			reg_type_str(env, reg->type), reg_arg_name(env, argno), reg->var_off.value);
+		bpf_diag_report_invalid_deref(env, env->insn_idx,
+					      reg_from_argno(argno),
+					      reg_arg_name(env, argno),
+					      reg_type_str(env, reg->type),
+					      BPF_DIAG_DEREF_MODIFIED_PTR,
+					      reg->var_off.value);
 		return -EACCES;
 	}
 
@@ -6241,6 +6253,12 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
 		if (type_may_be_null(reg->type)) {
 			verbose(env, "%s invalid mem access '%s'\n", reg_arg_name(env, argno),
 				reg_type_str(env, reg->type));
+			bpf_diag_report_invalid_deref(env, insn_idx,
+						      reg_from_argno(argno),
+						      reg_arg_name(env, argno),
+						      reg_type_str(env, reg->type),
+						      BPF_DIAG_DEREF_NULLABLE_PTR,
+						      0);
 			return -EACCES;
 		}
 
@@ -6396,8 +6414,19 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
 		if (t == BPF_READ && value_regno >= 0)
 			mark_reg_unknown(env, regs, value_regno);
 	} else {
+		enum bpf_diag_invalid_deref_kind kind = BPF_DIAG_DEREF_INVALID_PTR;
+
 		verbose(env, "%s invalid mem access '%s'\n", reg_arg_name(env, argno),
 			reg_type_str(env, reg->type));
+		if (reg->type == SCALAR_VALUE)
+			kind = BPF_DIAG_DEREF_SCALAR;
+		else if (type_may_be_null(reg->type))
+			kind = BPF_DIAG_DEREF_NULLABLE_PTR;
+		bpf_diag_report_invalid_deref(env, insn_idx,
+					      reg_from_argno(argno),
+					      reg_arg_name(env, argno),
+					      reg_type_str(env, reg->type),
+					      kind, 0);
 		return -EACCES;
 	}
 
@@ -7973,6 +8002,39 @@ static const struct bpf_reg_types *compatible_reg_types[__BPF_ARG_TYPE_MAX] = {
 	[ARG_PTR_TO_DYNPTR]		= &dynptr_types,
 };
 
+static const char *bpf_diag_reg_type_plain(struct bpf_verifier_env *env,
+					   enum bpf_reg_type type)
+{
+	switch (base_type(type)) {
+	case NOT_INIT:
+		return "an uninitialized value";
+	case SCALAR_VALUE:
+		return "an integer scalar";
+	case PTR_TO_CTX:
+		return "a context pointer";
+	case PTR_TO_STACK:
+		return "a stack pointer";
+	case PTR_TO_MAP_VALUE:
+		if (type_may_be_null(type))
+			return "a nullable map value pointer";
+		return "a map value pointer";
+	case PTR_TO_MEM:
+		if (type_may_be_null(type))
+			return "a nullable memory pointer";
+		return "a memory pointer";
+	case PTR_TO_BTF_ID:
+		if (type_is_non_owning_ref(type))
+			return "a borrowed allocated object pointer";
+		if (type_is_ptr_alloc_obj(type))
+			return "an owned allocated object pointer";
+		if (type_flag(type) & PTR_UNTRUSTED)
+			return "an untrusted kernel object pointer";
+		return "a kernel object pointer";
+	default:
+		return reg_type_str(env, type);
+	}
+}
+
 static int check_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno,
 			  enum bpf_arg_type arg_type,
 			  const u32 *arg_btf_id,
@@ -9389,14 +9451,17 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
 	ret = btf_prepare_func_args(env, subprog);
 	if (ret) {
 		if (bpf_in_stack_arg_cnt(sub) > 0) {
-			err = check_outgoing_stack_args(env, caller, sub->arg_cnt);
+			err = check_outgoing_stack_args(env, caller,
+							sub->arg_cnt,
+							subprog_name(env, subprog));
 			if (err)
 				return err;
 		}
 		return ret;
 	}
 
-	ret = check_outgoing_stack_args(env, caller, sub->arg_cnt);
+	ret = check_outgoing_stack_args(env, caller, sub->arg_cnt,
+					subprog_name(env, subprog));
 	if (ret)
 		return ret;
 
@@ -12207,7 +12272,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
 		return -ENOTSUPP;
 	}
 
-	ret = check_outgoing_stack_args(env, caller, nargs);
+	ret = check_outgoing_stack_args(env, caller, nargs, func_name);
 	if (ret)
 		return ret;
 
@@ -13934,6 +13999,7 @@ static int sanitize_check_bounds(struct bpf_verifier_env *env,
  */
 static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
 				   struct bpf_insn *insn,
+				   u32 ptr_regno,
 				   const struct bpf_reg_state *ptr_reg,
 				   const struct bpf_reg_state *off_reg)
 {
@@ -13946,6 +14012,7 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
 	struct bpf_sanitize_info info = {};
 	u8 opcode = BPF_OP(insn->code);
 	u32 dst = insn->dst_reg;
+	const char *reason;
 	int ret, bounds_ret;
 
 	dst_reg = &regs[dst];
@@ -13969,12 +14036,28 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
 		verbose(env,
 			"R%d 32-bit pointer arithmetic prohibited\n",
 			dst);
+		reason = bpf_diag_scratch_printf(env, 0,
+						 "R%d holds %s. 32-bit ALU operations on pointers discard pointer tracking, so the verifier cannot keep the result as a safe pointer.",
+						 ptr_regno,
+						 bpf_diag_reg_type_plain(env, ptr_reg->type));
+		bpf_diag_report_register_type(env, env->insn_idx, ptr_regno,
+					      "32-bit pointer arithmetic",
+					      reason,
+					      "Use a 64-bit ALU instruction with an allowed, bounded scalar offset.");
 		return -EACCES;
 	}
 
 	if (ptr_reg->type & PTR_MAYBE_NULL) {
 		verbose(env, "R%d pointer arithmetic on %s prohibited, null-check it first\n",
 			dst, reg_type_str(env, ptr_reg->type));
+		reason = bpf_diag_scratch_printf(env, 0,
+						 "R%d may be NULL (%s). Pointer arithmetic is allowed only after the program proves the pointer is non-NULL on this path.",
+						 ptr_regno,
+						 reg_type_str(env, ptr_reg->type));
+		bpf_diag_report_register_type(env, env->insn_idx, ptr_regno,
+					      "pointer arithmetic before NULL check",
+					      reason,
+					      "Make sure that a NULL check precedes any arithmetic performed on the pointer.");
 		return -EACCES;
 	}
 
@@ -14011,6 +14094,14 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
 	default:
 		verbose(env, "R%d pointer arithmetic on %s prohibited\n",
 			dst, reg_type_str(env, ptr_reg->type));
+		reason = bpf_diag_scratch_printf(env, 0,
+						 "R%d holds %s. This pointer kind does not allow offset arithmetic.",
+						 ptr_regno,
+						 bpf_diag_reg_type_plain(env, ptr_reg->type));
+		bpf_diag_report_register_type(env, env->insn_idx, ptr_regno,
+					      "pointer arithmetic is not allowed",
+					      reason,
+					      "Do not change this pointer's offset; use it only in operations accepted for its kind.");
 		return -EACCES;
 	}
 
@@ -14020,9 +14111,30 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
 	dst_reg->type = ptr_reg->type;
 	dst_reg->id = ptr_reg->id;
 
-	if (!check_reg_sane_offset_scalar(env, off_reg, ptr_reg->type) ||
-	    !check_reg_sane_offset_ptr(env, ptr_reg, ptr_reg->type))
+	if (!check_reg_sane_offset_scalar(env, off_reg, ptr_reg->type)) {
+		reason = bpf_diag_scratch_printf(env, 0,
+						 "The scalar offset used with R%d is unbounded or outside the verifier's safe pointer-offset range [-%u, %u].",
+						 ptr_regno, BPF_MAX_VAR_OFF,
+						 BPF_MAX_VAR_OFF);
+		bpf_diag_report_register_type(env, env->insn_idx, ptr_regno,
+					      "pointer offset is not safe",
+					      reason,
+					      "Clamp or bounds-check the scalar offset before applying it to the pointer.");
+		return -EINVAL;
+	}
+	if (!check_reg_sane_offset_ptr(env, ptr_reg, ptr_reg->type)) {
+		reason = bpf_diag_scratch_printf(env, 0,
+						 "R%d already has an offset outside the verifier's safe range [-%u, %u] for %s.",
+						 ptr_regno,
+						 BPF_MAX_VAR_OFF,
+						 BPF_MAX_VAR_OFF,
+						 bpf_diag_reg_type_plain(env, ptr_reg->type));
+		bpf_diag_report_register_type(env, env->insn_idx, ptr_regno,
+					      "pointer offset is not safe",
+					      reason,
+					      "Keep the base pointer within the verifier's allowed offset range before applying more arithmetic.");
 		return -EINVAL;
+	}
 
 	/* pointer types do not carry 32-bit bounds at the moment. */
 	__mark_reg32_unbounded(dst_reg);
@@ -14065,6 +14177,15 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
 			/* scalar -= pointer.  Creates an unknown scalar */
 			verbose(env, "R%d tried to subtract pointer from scalar\n",
 				dst);
+			reason = bpf_diag_scratch_printf(env,
+							 0,
+							 "This operation subtracts pointer register R%d from scalar register R%d. The verifier only tracks pointer-minus-scalar arithmetic for allowed pointer types.",
+							 ptr_regno, dst);
+			bpf_diag_report_register_type(env, env->insn_idx,
+						      ptr_regno,
+						      "pointer subtracted from scalar",
+						      reason,
+						      "Keep the pointer as the base; only add or subtract bounded scalars when permitted.");
 			return -EACCES;
 		}
 		/* We don't allow subtraction from FP, because (according to
@@ -14074,6 +14195,15 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
 		if (ptr_reg->type == PTR_TO_STACK) {
 			verbose(env, "R%d subtraction from stack pointer prohibited\n",
 				dst);
+			reason = bpf_diag_scratch_printf(env,
+							 0,
+							 "R%d is a stack pointer. The verifier does not allow BPF_SUB to move stack pointers.",
+							 ptr_regno);
+			bpf_diag_report_register_type(env, env->insn_idx,
+						      ptr_regno,
+						      "subtraction from stack pointer",
+						      reason,
+						      "Use addition from R10 to form stack addresses within the tracked stack frame.");
 			return -EACCES;
 		}
 		dst_reg->r64 = cnum64_add(ptr_reg->r64, cnum64_negate(off_reg->r64));
@@ -14099,16 +14229,45 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
 		/* bitwise ops on pointers are troublesome, prohibit. */
 		verbose(env, "R%d bitwise operator %s on pointer prohibited\n",
 			dst, bpf_alu_string[opcode >> 4]);
+		reason = bpf_diag_scratch_printf(env, 0,
+						 "R%d holds %s. Bitwise operator %s would destroy the pointer value the verifier is tracking.",
+						 ptr_regno,
+						 bpf_diag_reg_type_plain(env, ptr_reg->type),
+						 bpf_alu_string[opcode >> 4]);
+		bpf_diag_report_register_type(env, env->insn_idx, ptr_regno,
+					      "bitwise operation on pointer",
+					      reason,
+					      "Do bitwise operations on scalar values, not on pointer-valued registers.");
 		return -EACCES;
 	default:
 		/* other operators (e.g. MUL,LSH) produce non-pointer results */
 		verbose(env, "R%d pointer arithmetic with %s operator prohibited\n",
 			dst, bpf_alu_string[opcode >> 4]);
+		reason = bpf_diag_scratch_printf(env, 0,
+						 "R%d holds %s. Operator %s is not one of the limited pointer arithmetic operations the verifier can track.",
+						 ptr_regno,
+						 bpf_diag_reg_type_plain(env, ptr_reg->type),
+						 bpf_alu_string[opcode >> 4]);
+		bpf_diag_report_register_type(env, env->insn_idx, ptr_regno,
+					      "invalid pointer arithmetic operator",
+					      reason,
+					      "Use only verifier-supported addition or subtraction with a bounded scalar offset, or perform this operation on a scalar value.");
 		return -EACCES;
 	}
 
-	if (!check_reg_sane_offset_ptr(env, dst_reg, ptr_reg->type))
+	if (!check_reg_sane_offset_ptr(env, dst_reg, ptr_reg->type)) {
+		reason = bpf_diag_scratch_printf(env, 0,
+						 "After this arithmetic, R%d would be outside the verifier's safe offset range [-%u, %u] for %s.",
+						 dst,
+						 BPF_MAX_VAR_OFF,
+						 BPF_MAX_VAR_OFF,
+						 bpf_diag_reg_type_plain(env, ptr_reg->type));
+		bpf_diag_report_register_type(env, env->insn_idx, ptr_regno,
+					      "pointer offset is not safe",
+					      reason,
+					      "Tighten the scalar bounds before the arithmetic so the resulting pointer remains within the allowed range.");
 		return -EINVAL;
+	}
 	reg_bounds_sync(dst_reg);
 	bounds_ret = sanitize_check_bounds(env, insn, dst_reg);
 	if (bounds_ret == -EACCES)
@@ -15080,6 +15239,7 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
 				if (err)
 					return err;
 				return adjust_ptr_min_max_vals(env, insn,
+							       insn->src_reg,
 							       src_reg, dst_reg);
 			}
 		} else if (ptr_reg) {
@@ -15088,6 +15248,7 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
 			if (err)
 				return err;
 			return adjust_ptr_min_max_vals(env, insn,
+						       insn->dst_reg,
 						       dst_reg, src_reg);
 		} else if (dst_reg->precise) {
 			/* if dst_reg is precise, src_reg should be precise as well */
@@ -15104,6 +15265,7 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
 		src_reg = &off_reg;
 		if (ptr_reg) /* pointer += K */
 			return adjust_ptr_min_max_vals(env, insn,
+						       insn->dst_reg,
 						       ptr_reg, src_reg);
 	}
 
-- 
2.53.0


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

* [PATCH bpf-next v2 09/17] bpf: Report Memory Safety bounds errors
  2026-06-19 20:59 [PATCH bpf-next v2 00/17] Redesign Verification Errors Kumar Kartikeya Dwivedi
                   ` (7 preceding siblings ...)
  2026-06-19 20:59 ` [PATCH bpf-next v2 08/17] bpf: Report Register Type Safety errors Kumar Kartikeya Dwivedi
@ 2026-06-19 20:59 ` Kumar Kartikeya Dwivedi
  2026-06-19 21:46   ` bot+bpf-ci
  2026-06-19 23:40   ` Alexei Starovoitov
  2026-06-19 20:59 ` [PATCH bpf-next v2 10/17] bpf: Report Resource Lifetime reference leaks Kumar Kartikeya Dwivedi
                   ` (7 subsequent siblings)
  16 siblings, 2 replies; 34+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-06-19 20:59 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team

Augment selected memory-range verifier failures with Memory Safety reports
while preserving the existing terse verifier messages for compatibility.

Cover stack spill corruption, uninitialized stack reads, variable stack helper
accesses, and check_mem_region_access() range-proof failures. The bounds report
spells out the required offset + access_size <= object_size proof with concrete
values and uses scoped diagnostic history for causal context.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 kernel/bpf/diagnostics.c | 161 +++++++++++++++++++++++++++++++++++++++
 kernel/bpf/diagnostics.h |  16 ++++
 kernel/bpf/verifier.c    |  56 ++++++++++++++
 3 files changed, 233 insertions(+)

diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
index 92a4aef3cc90..933540eb105b 100644
--- a/kernel/bpf/diagnostics.c
+++ b/kernel/bpf/diagnostics.c
@@ -6,6 +6,7 @@
 #include <linux/btf.h>
 #include <linux/ctype.h>
 #include <linux/kernel.h>
+#include <linux/overflow.h>
 #include <linux/slab.h>
 #include <linux/stdarg.h>
 #include <linux/string.h>
@@ -1048,6 +1049,19 @@ void bpf_diag_report_stack_arg_uninit(struct bpf_verifier_env *env,
 				   "Write the outgoing stack argument after any operation that may invalidate stored pointer values, and before making this call.");
 }
 
+void bpf_diag_report_memory(struct bpf_verifier_env *env, u32 insn_idx,
+			    const char *problem, const char *reason,
+			    const char *suggestion)
+{
+	bpf_diag_report_header(env, BPF_DIAG_CATEGORY_MEMORY_SAFETY, problem);
+	bpf_diag_report_reason(env, "%s", reason);
+
+	bpf_diag_report_section(env, "At");
+	bpf_diag_report_source(env, insn_idx, "error", "%s", problem);
+
+	bpf_diag_report_suggestion(env, "%s", suggestion);
+}
+
 void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx,
 			    bool cond_true)
 {
@@ -1541,6 +1555,153 @@ static void bpf_diag_format_scalar_range(struct bpf_diag_reg_fmt *fmt,
 		  fmt->smin_buf, fmt->smax_buf, fmt->umin_buf, fmt->umax_buf);
 }
 
+static void bpf_diag_format_s64_sum(char *buf, size_t size, s64 value,
+				    int addend)
+{
+	s64 sum;
+
+	if (check_add_overflow(value, (s64)addend, &sum)) {
+		if (addend < 0)
+			scnprintf(buf, size, "%lld plus %d (below S64_MIN)",
+				  value, addend);
+		else
+			scnprintf(buf, size, "%lld plus %d (above S64_MAX)",
+				  value, addend);
+		return;
+	}
+
+	scnprintf(buf, size, "%lld", sum);
+}
+
+static void bpf_diag_format_access_offset(struct bpf_verifier_env *env,
+					  char *buf, size_t size, int off,
+					  const struct bpf_reg_state *reg)
+{
+	struct bpf_diag_scratch *scratch = bpf_diag_scratch(env);
+	struct bpf_diag_reg_fmt *fmt;
+	char *start;
+
+	if (tnum_is_const(reg->var_off)) {
+		start = bpf_diag_scratch_buf(env, 2,
+					     NULL);
+		if (!start) {
+			scnprintf(buf, size, "constant");
+			return;
+		}
+		bpf_diag_format_s64_sum(start, BPF_DIAG_SCRATCH_STR_LEN,
+					(s64)reg->var_off.value, off);
+		scnprintf(buf, size, "constant %s", start);
+		return;
+	}
+
+	if (tnum_is_unknown(reg->var_off) &&
+	    bpf_diag_cnum64_unknown(reg->r64)) {
+		scnprintf(buf, size, "unknown");
+		return;
+	}
+
+	fmt = &scratch->reg_fmt;
+	memset(fmt, 0, sizeof(*fmt));
+
+	bpf_diag_format_scalar_range(fmt, fmt->range, sizeof(fmt->range),
+				     reg->r64);
+	if (off)
+		scnprintf(buf, size,
+			  "variable: known bits %#llx, unknown mask %#llx, plus fixed offset %d; %s",
+			  (u64)reg->var_off.value, reg->var_off.mask, off,
+			  fmt->range);
+	else
+		scnprintf(buf, size,
+			  "variable: known bits %#llx, unknown mask %#llx; %s",
+			  (u64)reg->var_off.value, reg->var_off.mask,
+			  fmt->range);
+}
+
+static u64 bpf_diag_mem_max_start(const struct bpf_reg_state *reg, int off)
+{
+	/* A negative fixed offset can clamp the maximum start to zero when
+	 * the unsigned variable maximum is smaller than -off.
+	 */
+	if (off < 0 && reg_umax(reg) < (u64)-off)
+		return 0;
+	return reg_umax(reg) + off;
+}
+
+void bpf_diag_report_mem_bounds(struct bpf_verifier_env *env, u32 insn_idx,
+				int regno, const char *reg_name,
+				const char *type_name,
+				enum bpf_diag_mem_bounds_kind kind,
+				int off, int size, u32 mem_size,
+				const struct bpf_reg_state *reg)
+{
+	struct bpf_diag_history_opts opts = {
+		.scope = BPF_DIAG_HISTORY_SCOPE_REG,
+		.frameno = bpf_diag_current_frameno(env),
+		.regno = regno,
+	};
+	char *offset_desc, *proof, *start;
+	u64 max_start, max_end;
+
+	if (!bpf_diag_enabled(env))
+		return;
+
+	offset_desc = bpf_diag_scratch_buf(env, 0, NULL);
+	proof = bpf_diag_scratch_buf(env, 1, NULL);
+	start = bpf_diag_scratch_buf(env, 2, NULL);
+	if (!offset_desc || !proof || !start)
+		return;
+
+	switch (kind) {
+	case BPF_DIAG_MEM_NEGATIVE_MIN:
+		bpf_diag_format_s64_sum(start, BPF_DIAG_SCRATCH_STR_LEN, reg_smin(reg),
+					off);
+		scnprintf(proof, BPF_DIAG_SCRATCH_STR_LEN,
+			  "the smallest possible access starts at %s, below 0",
+			  start);
+		break;
+	case BPF_DIAG_MEM_MIN_OUT_OF_RANGE:
+		bpf_diag_format_s64_sum(start, BPF_DIAG_SCRATCH_STR_LEN, reg_smin(reg),
+					off);
+		scnprintf(proof, BPF_DIAG_SCRATCH_STR_LEN,
+			  "the smallest possible access starts at %s, outside object_size %u",
+			  start, mem_size);
+		break;
+	case BPF_DIAG_MEM_UNBOUNDED:
+		scnprintf(proof, BPF_DIAG_SCRATCH_STR_LEN,
+			  "%s has unsigned maximum %llu, which exceeds BPF_MAX_VAR_OFF %u",
+			  reg_name, reg_umax(reg), BPF_MAX_VAR_OFF);
+		break;
+	case BPF_DIAG_MEM_MAX_OUT_OF_RANGE:
+	default:
+		max_start = bpf_diag_mem_max_start(reg, off);
+		max_end = max_start + size;
+		scnprintf(proof, BPF_DIAG_SCRATCH_STR_LEN,
+			  "the largest possible access ends at %llu: start %llu + access_size %d, beyond object_size %u",
+			  max_end, max_start, size, mem_size);
+		break;
+	}
+
+	bpf_diag_format_access_offset(env, offset_desc, BPF_DIAG_SCRATCH_STR_LEN,
+				      off, reg);
+
+	bpf_diag_report_header(env, BPF_DIAG_CATEGORY_MEMORY_SAFETY,
+			       "access outside bounds");
+	bpf_diag_report_reason(env,
+			       "The verifier cannot prove offset + access_size <= object_size. Here, %s. %s is %s; offset is %s; access_size is %d; object_size is %u.",
+			       proof, reg_name, type_name, offset_desc, size,
+			       mem_size);
+
+	bpf_diag_report_section(env, "At");
+	bpf_diag_report_source(env, insn_idx, "error",
+			       "access may be outside object bounds");
+
+	if (regno >= 0)
+		bpf_diag_print_history(env, &opts);
+
+	bpf_diag_report_suggestion(env,
+				   "Add or adjust a bounds check that proves offset + access_size stays within the object.");
+}
+
 static void bpf_diag_format_var_offset(struct bpf_diag_reg_fmt *fmt,
 				       char *buf, size_t size,
 				       const struct bpf_diag_reg_snapshot *snapshot)
diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
index d6e858cd39f2..fea7731d431c 100644
--- a/kernel/bpf/diagnostics.h
+++ b/kernel/bpf/diagnostics.h
@@ -124,6 +124,13 @@ enum bpf_diag_invalid_deref_kind {
 	BPF_DIAG_DEREF_INVALID_PTR,
 };
 
+enum bpf_diag_mem_bounds_kind {
+	BPF_DIAG_MEM_NEGATIVE_MIN,
+	BPF_DIAG_MEM_MIN_OUT_OF_RANGE,
+	BPF_DIAG_MEM_UNBOUNDED,
+	BPF_DIAG_MEM_MAX_OUT_OF_RANGE,
+};
+
 struct bpf_diag_history_opts {
 	enum bpf_diag_history_scope scope;
 	u32 frameno;
@@ -171,6 +178,15 @@ void bpf_diag_report_stack_arg_uninit(struct bpf_verifier_env *env,
 				      u32 insn_idx, int nargs,
 				      int stack_arg_slot,
 				      const char *callee_name);
+void bpf_diag_report_memory(struct bpf_verifier_env *env, u32 insn_idx,
+			    const char *problem, const char *reason,
+			    const char *suggestion);
+void bpf_diag_report_mem_bounds(struct bpf_verifier_env *env, u32 insn_idx,
+				int regno, const char *reg_name,
+				const char *type_name,
+				enum bpf_diag_mem_bounds_kind kind,
+				int off, int size, u32 mem_size,
+				const struct bpf_reg_state *reg);
 void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx,
 			    bool cond_true);
 void bpf_diag_record_reg_mod(struct bpf_verifier_env *env, u32 insn_idx,
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 2c5f24528071..af04709c5178 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -3527,6 +3527,13 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
 	    !bpf_is_spilled_scalar_reg(&state->stack[spi]) &&
 	    size != BPF_REG_SIZE) {
 		verbose(env, "attempt to corrupt spilled pointer on stack\n");
+		bpf_diag_report_memory(env, insn_idx,
+				       "stack spill corruption",
+				       bpf_diag_scratch_printf(env,
+							       0,
+							       "This store writes %d bytes at stack offset %d into a stack slot that currently holds a spilled pointer. Partial writes to spilled pointers are rejected because they can corrupt pointer metadata and leak kernel pointers.",
+							       size, off),
+				       "Write the full 8-byte spilled pointer slot, or use a separate stack slot for scalar data before overwriting only part of it.");
 		return -EACCES;
 	}
 
@@ -3811,6 +3818,18 @@ static void mark_reg_stack_read(struct bpf_verifier_env *env,
 	}
 }
 
+static void bpf_diag_report_stack_read_uninit(struct bpf_verifier_env *env,
+					      int off, int i, int size)
+{
+	bpf_diag_report_memory(env, env->insn_idx,
+			       "uninitialized stack read",
+			       bpf_diag_scratch_printf(env,
+						       0,
+						       "This rejected read uses %d bytes at stack offset %d, but byte %d in that range is uninitialized on this path. Programs with sufficient privilege can be allowed to read uninitialized stack bytes, but this program is being rejected without that allowance.",
+						       size, off, i),
+			       "Initialize every byte in the stack range before reading it, adjust the offset and size so the read covers only initialized bytes, or load with the privilege needed for uninitialized stack reads.");
+}
+
 /* Read the stack at 'off' and put the results into the register indicated by
  * 'dst_regno'. It handles reg filling if the addressed stack slot is a
  * spilled reg.
@@ -3896,9 +3915,12 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
 					if (type == STACK_POISON) {
 						verbose(env, "reading from stack off %d+%d size %d, slot poisoned by dead code elimination\n",
 							off, i, size);
+						return -EFAULT;
 					} else {
 						verbose(env, "invalid read from stack off %d+%d size %d\n",
 							off, i, size);
+						bpf_diag_report_stack_read_uninit(env, off, i,
+										  size);
 					}
 					return -EACCES;
 				}
@@ -3951,9 +3973,12 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
 			if (type == STACK_POISON) {
 				verbose(env, "reading from stack off %d+%d size %d, slot poisoned by dead code elimination\n",
 					off, i, size);
+				return -EFAULT;
 			} else {
 				verbose(env, "invalid read from stack off %d+%d size %d\n",
 					off, i, size);
+				bpf_diag_report_stack_read_uninit(env, off, i,
+								  size);
 			}
 			return -EACCES;
 		}
@@ -4045,6 +4070,13 @@ static int check_stack_read(struct bpf_verifier_env *env,
 		tnum_strn(tn_buf, sizeof(tn_buf), reg->var_off);
 		verbose(env, "variable offset stack pointer cannot be passed into helper function; var_off=%s off=%d size=%d\n",
 			tn_buf, off, size);
+		bpf_diag_report_memory(env, env->insn_idx,
+				       "variable stack access",
+				       bpf_diag_scratch_printf(env,
+							       0,
+							       "The helper would access the stack through variable offset %s plus fixed offset %d and size %d. Helper stack memory arguments require a constant stack offset and a precise initialized range.",
+							       tn_buf, off, size),
+				       "Use a fixed stack offset for helper memory arguments, or copy the needed bytes into a fixed stack slot first.");
 		return -EACCES;
 	}
 	/* Variable offset is prohibited for unprivileged mode for simplicity
@@ -4312,6 +4344,12 @@ static int check_mem_region_access(struct bpf_verifier_env *env, struct bpf_reg_
 	      reg_smin(reg) + off < 0)) {
 		verbose(env, "%s min value is negative, either use unsigned index or do a if (index >=0) check.\n",
 			reg_arg_name(env, argno));
+		bpf_diag_report_mem_bounds(env, env->insn_idx,
+					   reg_from_argno(argno),
+					   reg_arg_name(env, argno),
+					   reg_type_str(env, reg->type),
+					   BPF_DIAG_MEM_NEGATIVE_MIN,
+					   off, size, mem_size, reg);
 		return -EACCES;
 	}
 	err = __check_mem_access(env, reg, argno, reg_smin(reg) + off, size,
@@ -4319,6 +4357,12 @@ static int check_mem_region_access(struct bpf_verifier_env *env, struct bpf_reg_
 	if (err) {
 		verbose(env, "%s min value is outside of the allowed memory range\n",
 			reg_arg_name(env, argno));
+		bpf_diag_report_mem_bounds(env, env->insn_idx,
+					   reg_from_argno(argno),
+					   reg_arg_name(env, argno),
+					   reg_type_str(env, reg->type),
+					   BPF_DIAG_MEM_MIN_OUT_OF_RANGE,
+					   off, size, mem_size, reg);
 		return err;
 	}
 
@@ -4329,6 +4373,12 @@ static int check_mem_region_access(struct bpf_verifier_env *env, struct bpf_reg_
 	if (reg_umax(reg) >= BPF_MAX_VAR_OFF) {
 		verbose(env, "%s unbounded memory access, make sure to bounds check any such access\n",
 			reg_arg_name(env, argno));
+		bpf_diag_report_mem_bounds(env, env->insn_idx,
+					   reg_from_argno(argno),
+					   reg_arg_name(env, argno),
+					   reg_type_str(env, reg->type),
+					   BPF_DIAG_MEM_UNBOUNDED,
+					   off, size, mem_size, reg);
 		return -EACCES;
 	}
 	err = __check_mem_access(env, reg, argno, reg_umax(reg) + off, size,
@@ -4336,6 +4386,12 @@ static int check_mem_region_access(struct bpf_verifier_env *env, struct bpf_reg_
 	if (err) {
 		verbose(env, "%s max value is outside of the allowed memory range\n",
 			reg_arg_name(env, argno));
+		bpf_diag_report_mem_bounds(env, env->insn_idx,
+					   reg_from_argno(argno),
+					   reg_arg_name(env, argno),
+					   reg_type_str(env, reg->type),
+					   BPF_DIAG_MEM_MAX_OUT_OF_RANGE,
+					   off, size, mem_size, reg);
 		return err;
 	}
 
-- 
2.53.0


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

* [PATCH bpf-next v2 10/17] bpf: Report Resource Lifetime reference leaks
  2026-06-19 20:59 [PATCH bpf-next v2 00/17] Redesign Verification Errors Kumar Kartikeya Dwivedi
                   ` (8 preceding siblings ...)
  2026-06-19 20:59 ` [PATCH bpf-next v2 09/17] bpf: Report Memory Safety bounds errors Kumar Kartikeya Dwivedi
@ 2026-06-19 20:59 ` Kumar Kartikeya Dwivedi
  2026-06-19 21:12   ` sashiko-bot
  2026-06-19 23:42   ` Alexei Starovoitov
  2026-06-19 20:59 ` [PATCH bpf-next v2 11/17] bpf: Report Call Type Safety argument errors Kumar Kartikeya Dwivedi
                   ` (6 subsequent siblings)
  16 siblings, 2 replies; 34+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-06-19 20:59 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team

Augment selected Resource Lifetime Safety failures with structured diagnostics
while preserving the existing verifier messages.

Report unreleased references from check_reference_leak() using
reference-scoped diagnostic history, and add state reports for dynptr,
iterator, lock, and IRQ-flag lifetime misuse.

IRQ restore mismatch and out-of-order diagnostics use IRQ context-scoped
history when an IRQ-disabled region is active, so retained save/restore context
is still visible after per-state history removal.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 kernel/bpf/diagnostics.c | 65 ++++++++++++++++++++++++++++
 kernel/bpf/diagnostics.h | 11 +++++
 kernel/bpf/verifier.c    | 92 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 168 insertions(+)

diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
index 933540eb105b..e9c58f84ec89 100644
--- a/kernel/bpf/diagnostics.c
+++ b/kernel/bpf/diagnostics.c
@@ -1702,6 +1702,71 @@ void bpf_diag_report_mem_bounds(struct bpf_verifier_env *env, u32 insn_idx,
 				   "Add or adjust a bounds check that proves offset + access_size stays within the object.");
 }
 
+void bpf_diag_report_resource_state(struct bpf_verifier_env *env,
+				    u32 insn_idx, const char *problem,
+				    const char *reason,
+				    const char *suggestion)
+{
+	bpf_diag_report_header(env, BPF_DIAG_CATEGORY_RESOURCE_LIFETIME_SAFETY,
+			       problem);
+	bpf_diag_report_reason(env, "%s", reason);
+
+	bpf_diag_report_section(env, "At");
+	bpf_diag_report_source(env, insn_idx, "error", "%s", problem);
+
+	bpf_diag_report_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_report_irq_resource_state(struct bpf_verifier_env *env,
+					u32 insn_idx, const char *problem,
+					const char *reason,
+					const char *suggestion,
+					u32 depth)
+{
+	struct bpf_diag_history_opts opts = {
+		.scope = BPF_DIAG_HISTORY_SCOPE_CONTEXT,
+		.ctx_kind = BPF_DIAG_CONTEXT_IRQ,
+		.ctx_depth = depth,
+	};
+
+	bpf_diag_report_header(env, BPF_DIAG_CATEGORY_RESOURCE_LIFETIME_SAFETY,
+			       problem);
+	bpf_diag_report_reason(env, "%s", reason);
+
+	bpf_diag_report_section(env, "At");
+	bpf_diag_report_source(env, insn_idx, "error", "%s", problem);
+
+	if (depth)
+		bpf_diag_print_history(env, &opts);
+
+	bpf_diag_report_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_report_ref_leak(struct bpf_verifier_env *env, u32 ref_id,
+			      u32 alloc_insn, u32 fail_insn)
+{
+	struct bpf_diag_history_opts opts = {
+		.scope = BPF_DIAG_HISTORY_SCOPE_REF,
+		.ref_id = ref_id,
+	};
+
+	bpf_diag_report_header(env, BPF_DIAG_CATEGORY_RESOURCE_LIFETIME_SAFETY,
+			       "unreleased resource");
+	bpf_diag_report_reason(env,
+			       "Owned resource (id=%u) was acquired at instruction %u and still needs to be released before this exit path.",
+			       ref_id, alloc_insn);
+
+	bpf_diag_report_section(env, "At");
+	bpf_diag_report_source(env, fail_insn, "error",
+			       "owned resource (id=%u) still needs release",
+			       ref_id);
+
+	bpf_diag_print_history(env, &opts);
+
+	bpf_diag_report_suggestion(env,
+				   "Release or transfer ownership of the acquired resource on every path before the program exits.");
+}
+
 static void bpf_diag_format_var_offset(struct bpf_diag_reg_fmt *fmt,
 				       char *buf, size_t size,
 				       const struct bpf_diag_reg_snapshot *snapshot)
diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
index fea7731d431c..4e0bb27ea951 100644
--- a/kernel/bpf/diagnostics.h
+++ b/kernel/bpf/diagnostics.h
@@ -187,6 +187,17 @@ void bpf_diag_report_mem_bounds(struct bpf_verifier_env *env, u32 insn_idx,
 				enum bpf_diag_mem_bounds_kind kind,
 				int off, int size, u32 mem_size,
 				const struct bpf_reg_state *reg);
+void bpf_diag_report_resource_state(struct bpf_verifier_env *env,
+				    u32 insn_idx, const char *problem,
+				    const char *reason,
+				    const char *suggestion);
+void bpf_diag_report_irq_resource_state(struct bpf_verifier_env *env,
+					u32 insn_idx, const char *problem,
+					const char *reason,
+					const char *suggestion,
+					u32 depth);
+void bpf_diag_report_ref_leak(struct bpf_verifier_env *env, u32 ref_id,
+			      u32 alloc_insn, u32 fail_insn);
 void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx,
 			    bool cond_true);
 void bpf_diag_record_reg_mod(struct bpf_verifier_env *env, u32 insn_idx,
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index af04709c5178..db151e6b8949 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -835,6 +835,10 @@ static int destroy_if_dynptr_stack_slot(struct bpf_verifier_env *env,
 	if (dynptr_type_referenced(state->stack[spi].spilled_ptr.dynptr.type) &&
 	    dynptr_ref_cnt(env, state->stack[spi].spilled_ptr.parent_id) <= 1) {
 		verbose(env, "cannot overwrite referenced dynptr\n");
+		bpf_diag_report_resource_state(env, env->insn_idx,
+					       "referenced dynptr overwrite",
+					       "This stack slot contains a dynptr that owns or protects a referenced resource. Overwriting the last dynptr for that resource would lose the verifier-tracked release path.",
+					       "Release or clone the dynptr so another live dynptr still tracks the referenced resource before overwriting this stack slot.");
 		return -EINVAL;
 	}
 
@@ -1120,6 +1124,15 @@ static int unmark_stack_slot_irq_flag(struct bpf_verifier_env *env, struct bpf_r
 
 		verbose(env, "irq flag acquired by %s kfuncs cannot be restored with %s kfuncs\n",
 			flag_kfunc, used_kfunc);
+		bpf_diag_report_irq_resource_state(env, env->insn_idx,
+						   "IRQ flag restore mismatch",
+						   bpf_diag_scratch_printf(env,
+									   0,
+									   "This IRQ flag was saved by %s IRQ kfuncs, but the restore call belongs to the %s IRQ kfunc family. Save and restore operations must use the same family.",
+									   flag_kfunc,
+									   used_kfunc),
+						   "Restore the flag with the matching IRQ restore kfunc for the save operation that created it.",
+						   env->cur_state->active_irq_id ? 1 : 0);
 		return -EINVAL;
 	}
 
@@ -1137,6 +1150,11 @@ static int unmark_stack_slot_irq_flag(struct bpf_verifier_env *env, struct bpf_r
 
 		verbose(env, "cannot restore irq state out of order, expected id=%d acquired at insn_idx=%d\n",
 			env->cur_state->active_irq_id, insn_idx);
+		bpf_diag_report_irq_resource_state(env, env->insn_idx,
+						   "IRQ flag restore out of order",
+						   "IRQ-disabled regions must be restored in last-in, first-out order, but this restore does not match the currently active IRQ flag.",
+						   "Restore nested IRQ flags in the reverse order they were saved.",
+						   env->cur_state->active_irq_id ? 1 : 0);
 		return err;
 	}
 
@@ -7258,11 +7276,19 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state
 			if (find_lock_state(env->cur_state, REF_TYPE_LOCK, 0, NULL)) {
 				verbose(env,
 					"Locking two bpf_spin_locks are not allowed\n");
+				bpf_diag_report_resource_state(env, env->insn_idx,
+							       "nested spin lock",
+							       "This path already holds a bpf_spin_lock. The verifier allows only one regular BPF spin lock at a time.",
+							       "Unlock the current bpf_spin_lock before taking another one.");
 				return -EINVAL;
 			}
 		} else if (is_res_lock && cur->active_locks) {
 			if (find_lock_state(env->cur_state, REF_TYPE_RES_LOCK | REF_TYPE_RES_LOCK_IRQ, reg->id, ptr)) {
 				verbose(env, "Acquiring the same lock again, AA deadlock detected\n");
+				bpf_diag_report_resource_state(env, env->insn_idx,
+							       "recursive resource spin lock",
+							       "This path already holds the same resource spin lock. Taking it again would deadlock.",
+							       "Avoid reacquiring the same resource spin lock before it is unlocked.");
 				return -EINVAL;
 			}
 		}
@@ -7289,6 +7315,10 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state
 
 		if (!cur->active_locks) {
 			verbose(env, "%s_unlock without taking a lock\n", lock_str);
+			bpf_diag_report_resource_state(env, env->insn_idx,
+						       "unlock without lock",
+						       "This unlock operation has no matching active lock on the current path.",
+						       "Take the matching lock before this unlock, or remove the unmatched unlock path.");
 			return -EINVAL;
 		}
 
@@ -7300,14 +7330,26 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state
 			type = REF_TYPE_LOCK;
 		if (!find_lock_state(cur, type, reg->id, ptr)) {
 			verbose(env, "%s_unlock of different lock\n", lock_str);
+			bpf_diag_report_resource_state(env, env->insn_idx,
+						       "unlock of different lock",
+						       "This unlock does not match any active lock with the same tracked identity on the current path.",
+						       "Unlock the same lock object that was most recently acquired.");
 			return -EINVAL;
 		}
 		if (reg->id != cur->active_lock_id || ptr != cur->active_lock_ptr) {
 			verbose(env, "%s_unlock cannot be out of order\n", lock_str);
+			bpf_diag_report_resource_state(env, env->insn_idx,
+						       "unlock out of order",
+						       "Locks must be released in last-in, first-out order, but this unlock does not match the currently active lock.",
+						       "Release nested locks in the reverse order they were acquired.");
 			return -EINVAL;
 		}
 		if (release_lock_state(env, type, reg->id, ptr)) {
 			verbose(env, "%s_unlock of different lock\n", lock_str);
+			bpf_diag_report_resource_state(env, env->insn_idx,
+						       "unlock of different lock",
+						       "The verifier could not release a lock state matching this unlock operation.",
+						       "Pass the same lock object and lock kind that were used for the matching lock operation.");
 			return -EINVAL;
 		}
 
@@ -7473,6 +7515,10 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
 		verbose(env,
 			"%s expected pointer to stack or const struct bpf_dynptr\n",
 			reg_arg_name(env, argno));
+		bpf_diag_report_resource_state(env, insn_idx,
+					       "invalid dynptr argument",
+					       "A dynptr argument must be a pointer to a dynptr stack slot or a verifier-provided const struct bpf_dynptr.",
+					       "Pass the address of a stack dynptr object, or use a const dynptr pointer returned by the verifier-supported path.");
 		return -EINVAL;
 	}
 
@@ -7495,6 +7541,10 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
 
 		if (!is_dynptr_reg_valid_uninit(env, reg)) {
 			verbose(env, "Dynptr has to be an uninitialized dynptr\n");
+			bpf_diag_report_resource_state(env, insn_idx,
+						       "dynptr is already initialized",
+						       "This kfunc constructs a dynptr and requires an uninitialized dynptr stack slot, but the selected slot already holds dynptr state.",
+						       "Use a fresh stack dynptr slot, or release/destroy the existing dynptr before reusing the slot.");
 			return -EINVAL;
 		}
 
@@ -7511,12 +7561,20 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
 		/* For the reg->type == PTR_TO_STACK case, bpf_dynptr is never const */
 		if (reg->type == CONST_PTR_TO_DYNPTR && (arg_type & OBJ_RELEASE)) {
 			verbose(env, "CONST_PTR_TO_DYNPTR cannot be released\n");
+			bpf_diag_report_resource_state(env, insn_idx,
+						       "const dynptr release",
+						       "This release operation was given a const dynptr. Const dynptr values are verifier-provided views and cannot be released by the program.",
+						       "Release only mutable dynptrs that the program initialized or reserved.");
 			return -EINVAL;
 		}
 
 		if (!is_dynptr_reg_valid_init(env, reg)) {
 			verbose(env, "Expected an initialized dynptr as %s\n",
 				reg_arg_name(env, argno));
+			bpf_diag_report_resource_state(env, insn_idx,
+						       "uninitialized dynptr use",
+						       "This operation requires an initialized dynptr, but the stack slot does not currently hold a valid dynptr on this path.",
+						       "Initialize the dynptr on every path before this call, and avoid overwriting or releasing it before this use.");
 			return -EINVAL;
 		}
 
@@ -7526,6 +7584,10 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
 				"Expected a dynptr of type %s as %s\n",
 				dynptr_type_str(arg_to_dynptr_type(arg_type)),
 				reg_arg_name(env, argno));
+			bpf_diag_report_resource_state(env, insn_idx,
+						       "wrong dynptr type",
+						       "The dynptr is initialized, but it was created for a different backing object type than this operation accepts.",
+						       "Use a dynptr constructor that matches this operation, or call an operation that accepts the dynptr's current type.");
 			return -EINVAL;
 		}
 
@@ -7594,6 +7656,10 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
 	if (reg->type != PTR_TO_STACK) {
 		verbose(env, "%s expected pointer to an iterator on stack\n",
 			reg_arg_name(env, argno));
+		bpf_diag_report_resource_state(env, insn_idx,
+					       "invalid iterator argument",
+					       "Iterator state must live in verifier-tracked stack memory, but this argument is not a stack pointer.",
+					       "Pass the address of a stack iterator object for iterator new, next, and destroy calls.");
 		return -EINVAL;
 	}
 
@@ -7607,6 +7673,10 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
 	if (btf_id < 0) {
 		verbose(env, "expected valid iter pointer as %s\n",
 			reg_arg_name(env, argno));
+		bpf_diag_report_resource_state(env, insn_idx,
+					       "invalid iterator type",
+					       "The kfunc expects a recognized iterator state pointer, but this argument does not match a valid iterator type.",
+					       "Pass the exact iterator state type expected by this kfunc.");
 		return -EINVAL;
 	}
 	t = btf_type_by_id(meta->btf, btf_id);
@@ -7617,6 +7687,10 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
 		if (!is_iter_reg_valid_uninit(env, reg, nr_slots)) {
 			verbose(env, "expected uninitialized iter_%s as %s\n",
 				iter_type_str(meta->btf, btf_id), reg_arg_name(env, argno));
+			bpf_diag_report_resource_state(env, insn_idx,
+						       "iterator is already initialized",
+						       "Iterator creation requires an uninitialized iterator stack object, but this stack range already contains iterator state.",
+						       "Use a fresh iterator stack slot, or destroy the existing iterator before reusing the slot.");
 			return -EINVAL;
 		}
 
@@ -7641,9 +7715,17 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
 		case -EINVAL:
 			verbose(env, "expected an initialized iter_%s as %s\n",
 				iter_type_str(meta->btf, btf_id), reg_arg_name(env, argno));
+			bpf_diag_report_resource_state(env, insn_idx,
+						       "uninitialized iterator use",
+						       "This iterator operation requires an initialized iterator state object, but the stack range does not contain a live iterator on this path.",
+						       "Call the matching iterator new kfunc on every path before calling next or destroy, and do not destroy the iterator before this use.");
 			return err;
 		case -EPROTO:
 			verbose(env, "expected an RCU CS when using %s\n", meta->func_name);
+			bpf_diag_report_resource_state(env, insn_idx,
+						       "iterator requires RCU read lock",
+						       "This iterator was created for use under RCU protection, but this path is not currently inside an RCU read lock region.",
+						       "Wrap iterator use in bpf_rcu_read_lock() and bpf_rcu_read_unlock(), keeping all exit paths balanced.");
 			return err;
 		default:
 			return err;
@@ -10333,6 +10415,8 @@ static int check_reference_leak(struct bpf_verifier_env *env, bool exception_exi
 			continue;
 		verbose(env, "Unreleased reference id=%d alloc_insn=%d\n",
 			state->refs[i].id, state->refs[i].insn_idx);
+		bpf_diag_report_ref_leak(env, state->refs[i].id,
+					 state->refs[i].insn_idx, env->insn_idx);
 		refs_lingering = true;
 	}
 	return refs_lingering ? -EINVAL : 0;
@@ -11829,6 +11913,10 @@ static int process_irq_flag(struct bpf_verifier_env *env, struct bpf_reg_state *
 		if (!is_irq_flag_reg_valid_uninit(env, reg)) {
 			verbose(env, "expected uninitialized irq flag as %s\n",
 				reg_arg_name(env, argno));
+			bpf_diag_report_resource_state(env, env->insn_idx,
+						       "IRQ flag is already initialized",
+						       "Saving IRQ state requires an uninitialized stack slot for the IRQ flag, but this slot already contains tracked IRQ flag state.",
+						       "Use a fresh stack slot for this save operation, or restore the existing IRQ flag before reusing the slot.");
 			return -EINVAL;
 		}
 
@@ -11845,6 +11933,10 @@ static int process_irq_flag(struct bpf_verifier_env *env, struct bpf_reg_state *
 		if (err) {
 			verbose(env, "expected an initialized irq flag as %s\n",
 				reg_arg_name(env, argno));
+			bpf_diag_report_resource_state(env, env->insn_idx,
+						       "uninitialized IRQ flag restore",
+						       "Restoring IRQ state requires a stack slot that was initialized by a matching IRQ save operation on this path.",
+						       "Pass the same stack slot that was previously initialized by the matching IRQ save kfunc.");
 			return err;
 		}
 
-- 
2.53.0


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

* [PATCH bpf-next v2 11/17] bpf: Report Call Type Safety argument errors
  2026-06-19 20:59 [PATCH bpf-next v2 00/17] Redesign Verification Errors Kumar Kartikeya Dwivedi
                   ` (9 preceding siblings ...)
  2026-06-19 20:59 ` [PATCH bpf-next v2 10/17] bpf: Report Resource Lifetime reference leaks Kumar Kartikeya Dwivedi
@ 2026-06-19 20:59 ` Kumar Kartikeya Dwivedi
  2026-06-19 21:47   ` bot+bpf-ci
  2026-06-19 20:59 ` [PATCH bpf-next v2 12/17] bpf: Report Execution Context Safety errors Kumar Kartikeya Dwivedi
                   ` (5 subsequent siblings)
  16 siblings, 1 reply; 34+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-06-19 20:59 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team

Augment selected helper and kfunc argument-contract failures with Call Type
Safety reports. Keep the existing terse verifier messages and add reason,
source context, causal register or stack-argument history, and targeted
suggestions.

Cover helper register-type mismatch, helper and kfunc non-NULL pointer
requirements, release-helper ownership requirements, scalar and constant kfunc
arguments, trusted and RCU pointer contracts, kfunc memory arguments,
memory/length pairs, refcounted kptrs, constant strings, and IRQ flag stack
arguments.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 kernel/bpf/diagnostics.c |  52 ++++++++
 kernel/bpf/diagnostics.h |   4 +
 kernel/bpf/verifier.c    | 249 +++++++++++++++++++++++++++++++++++++--
 3 files changed, 295 insertions(+), 10 deletions(-)

diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
index e9c58f84ec89..19e72b07afc1 100644
--- a/kernel/bpf/diagnostics.c
+++ b/kernel/bpf/diagnostics.c
@@ -901,6 +901,58 @@ static const char *bpf_diag_arg_ordinal(int argno)
 	}
 }
 
+void bpf_diag_report_call_type(struct bpf_verifier_env *env, u32 insn_idx,
+			       int argno, int regno, int stack_arg_slot,
+			       const char *call_name, const char *arg_name,
+			       const char *reason, const char *suggestion)
+{
+	struct bpf_diag_history_opts opts = {
+		.frameno = bpf_diag_current_frameno(env),
+	};
+	const char *ordinal = bpf_diag_arg_ordinal(argno);
+	const char *arg_desc;
+	bool print_history = true;
+
+	if (regno >= 0) {
+		opts.scope = BPF_DIAG_HISTORY_SCOPE_REG;
+		opts.regno = regno;
+	} else if (stack_arg_slot >= 0) {
+		opts.scope = BPF_DIAG_HISTORY_SCOPE_STACK_ARG;
+		opts.stack_arg_slot = stack_arg_slot;
+	} else {
+		print_history = false;
+	}
+
+	if (ordinal && arg_name)
+		arg_desc = bpf_diag_scratch_printf(env, 1,
+						   "%s argument (%s)",
+						   ordinal, arg_name);
+	else if (ordinal)
+		arg_desc = bpf_diag_scratch_printf(env, 1,
+						   "%s argument", ordinal);
+	else if (arg_name)
+		arg_desc = bpf_diag_scratch_printf(env, 1,
+						   "argument %s", arg_name);
+	else
+		arg_desc = bpf_diag_scratch_strcpy(env, 1,
+						   "argument");
+
+	bpf_diag_report_header(env, BPF_DIAG_CATEGORY_CALL_TYPE_SAFETY,
+			       "invalid call argument");
+	bpf_diag_report_reason(env,
+			       "The %s to %s does not satisfy the verifier contract: %s.",
+			       arg_desc, call_name, reason);
+
+	bpf_diag_report_section(env, "At");
+	bpf_diag_report_source(env, insn_idx, "error",
+			       "invalid %s for %s", arg_desc, call_name);
+
+	if (print_history)
+		bpf_diag_print_history(env, &opts);
+
+	bpf_diag_report_suggestion(env, "%s", suggestion);
+}
+
 void bpf_diag_report_invalid_deref(struct bpf_verifier_env *env, u32 insn_idx,
 				   int regno, const char *reg_name,
 				   const char *type_name,
diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
index 4e0bb27ea951..07d06d366f22 100644
--- a/kernel/bpf/diagnostics.h
+++ b/kernel/bpf/diagnostics.h
@@ -198,6 +198,10 @@ void bpf_diag_report_irq_resource_state(struct bpf_verifier_env *env,
 					u32 depth);
 void bpf_diag_report_ref_leak(struct bpf_verifier_env *env, u32 ref_id,
 			      u32 alloc_insn, u32 fail_insn);
+void bpf_diag_report_call_type(struct bpf_verifier_env *env, u32 insn_idx,
+			       int argno, int regno, int stack_arg_slot,
+			       const char *call_name, const char *arg_name,
+			       const char *reason, const char *suggestion);
 void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx,
 			    bool cond_true);
 void bpf_diag_record_reg_mod(struct bpf_verifier_env *env, u32 insn_idx,
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index db151e6b8949..fdbf92bffc17 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -320,6 +320,15 @@ static int arg_idx_from_argno(argno_t a)
 	return arg_from_argno(a) - 1;
 }
 
+static int bpf_diag_stack_arg_slot_from_argno(argno_t a)
+{
+	int arg = arg_from_argno(a);
+
+	if (arg <= MAX_BPF_FUNC_REG_ARGS)
+		return -1;
+	return arg - MAX_BPF_FUNC_REG_ARGS - 1;
+}
+
 static const char *btf_type_name(const struct btf *btf, u32 id)
 {
 	return btf_name_by_offset(btf, btf_type_by_id(btf, id)->name_off);
@@ -8161,6 +8170,8 @@ static const char *bpf_diag_reg_type_plain(struct bpf_verifier_env *env,
 			return "a nullable memory pointer";
 		return "a memory pointer";
 	case PTR_TO_BTF_ID:
+		if (type_may_be_null(type))
+			return "a nullable kernel object pointer";
 		if (type_is_non_owning_ref(type))
 			return "a borrowed allocated object pointer";
 		if (type_is_ptr_alloc_obj(type))
@@ -8173,6 +8184,60 @@ static const char *bpf_diag_reg_type_plain(struct bpf_verifier_env *env,
 	}
 }
 
+static void bpf_diag_report_call_arg(struct bpf_verifier_env *env,
+				     u32 insn_idx, argno_t argno,
+				     const char *call_name,
+				     const char *reason,
+				     const char *suggestion)
+{
+	bpf_diag_report_call_type(env, insn_idx, arg_from_argno(argno),
+				  reg_from_argno(argno),
+				  bpf_diag_stack_arg_slot_from_argno(argno),
+				  call_name && *call_name ? call_name : "call",
+				  reg_arg_name(env, argno), reason, suggestion);
+}
+
+static const char *diag_btf_type_name(struct bpf_verifier_env *env,
+				      const struct btf *btf, u32 type_id)
+{
+	return bpf_diag_format_btf_type_scratch(env, 1,
+						btf, type_id);
+}
+
+static const char *diag_arg_name(struct bpf_verifier_env *env,
+				 unsigned int slot, argno_t argno)
+{
+	return bpf_diag_scratch_strcpy(env, slot, reg_arg_name(env, argno));
+}
+
+static void diag_call_arg_fmt(struct bpf_verifier_env *env,
+			      u32 insn_idx, argno_t argno,
+			      const char *call_name,
+			      const char *suggestion,
+			      const char *fmt, ...) __printf(6, 7);
+static void diag_call_arg_fmt(struct bpf_verifier_env *env,
+			      u32 insn_idx, argno_t argno,
+			      const char *call_name,
+			      const char *suggestion,
+			      const char *fmt, ...)
+{
+	size_t size;
+	va_list args;
+	char *reason;
+
+	reason = bpf_diag_scratch_buf(env, 0, &size);
+	if (reason) {
+		va_start(args, fmt);
+		vscnprintf(reason, size, fmt, args);
+		va_end(args);
+	} else {
+		reason = "";
+	}
+
+	bpf_diag_report_call_arg(env, insn_idx, argno, call_name, reason,
+				 suggestion);
+}
+
 static int check_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno,
 			  enum bpf_arg_type arg_type,
 			  const u32 *arg_btf_id,
@@ -8226,6 +8291,32 @@ static int check_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *re
 	for (j = 0; j + 1 < i; j++)
 		verbose(env, "%s, ", reg_type_str(env, compatible->types[j]));
 	verbose(env, "%s\n", reg_type_str(env, compatible->types[j]));
+	{
+		size_t expected_size;
+		char *expected_buf;
+		const char *call_name;
+		int len = 0;
+
+		expected_buf = bpf_diag_scratch_buf(env, 1,
+						    &expected_size);
+		if (expected_buf) {
+			expected_buf[0] = '\0';
+			for (j = 0; j < i && len < expected_size; j++)
+				len += scnprintf(expected_buf + len,
+						 expected_size - len,
+						 "%s%s", j ? ", " : "",
+						 reg_type_str(env, compatible->types[j]));
+		} else {
+			expected_buf = "";
+		}
+
+		call_name = meta->func_id ? func_id_name(meta->func_id) : "callee";
+		diag_call_arg_fmt(env, env->insn_idx, argno, call_name,
+				  "Pass a value with one of the accepted pointer or scalar types for this call.",
+				  "it has type %s, but this argument accepts %s",
+				  bpf_diag_reg_type_plain(env, reg->type),
+				  expected_buf);
+	}
 	return -EACCES;
 
 found:
@@ -8262,6 +8353,10 @@ static int check_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *re
 		    (!type_may_be_null(arg_type) || arg_type_is_release(arg_type))) {
 			verbose(env, "Possibly NULL pointer passed to helper %s\n",
 				reg_arg_name(env, argno));
+			bpf_diag_report_call_arg(env, env->insn_idx, argno,
+						 func_id_name(meta->func_id),
+						 "the pointer may be NULL, but this helper requires a non-NULL pointer",
+						 "Add a NULL check and call the helper only on the non-NULL path.");
 			return -EACCES;
 		}
 
@@ -8597,7 +8692,7 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
 	    base_type(arg_type) == ARG_PTR_TO_SPIN_LOCK)
 		arg_btf_id = fn->arg_btf_id[arg];
 
-	err = check_reg_type(env, reg, argno_from_reg(regno), arg_type, arg_btf_id, meta);
+	err = check_reg_type(env, reg, argno, arg_type, arg_btf_id, meta);
 	if (err)
 		return err;
 
@@ -8610,6 +8705,10 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
 	    !reg_is_referenced(env, reg) && !bpf_register_is_null(reg)) {
 		verbose(env, "release helper %s expects referenced PTR_TO_BTF_ID passed to %s\n",
 			func_id_name(meta->func_id), reg_arg_name(env, argno));
+		bpf_diag_report_call_arg(env, insn_idx, argno,
+					 func_id_name(meta->func_id),
+					 "release helpers require a value that owns a live resource returned by a matching acquire helper",
+					 "Pass the resource-owning pointer returned by the matching acquire helper, and avoid calling the release helper after ownership has already been transferred or released.");
 		return -EINVAL;
 	}
 
@@ -11716,7 +11815,8 @@ static enum kfunc_ptr_arg_type
 get_kfunc_ptr_arg_type(struct bpf_verifier_env *env, struct bpf_func_state *caller,
 		       struct bpf_reg_state *regs, struct bpf_kfunc_call_arg_meta *meta,
 		       const struct btf_type *t, const struct btf_type *ref_t,
-		       const char *ref_tname, const struct btf_param *args,
+		       const char *ref_tname, u32 ref_id,
+		       const struct btf_param *args,
 		       int arg, int nargs, argno_t argno, struct bpf_reg_state *reg)
 {
 	bool arg_mem_size = false;
@@ -11808,9 +11908,20 @@ get_kfunc_ptr_arg_type(struct bpf_verifier_env *env, struct bpf_func_state *call
 	 */
 	if (!btf_type_is_scalar(ref_t) && !__btf_type_is_scalar_struct(env, meta->btf, ref_t, 0) &&
 	    (arg_mem_size ? !btf_type_is_void(ref_t) : 1)) {
+		const char *expected_type;
+
+		expected_type = bpf_diag_format_btf_type_scratch(env,
+								 1,
+								 meta->btf,
+								 ref_id);
 		verbose(env, "%s pointer type %s %s must point to %sscalar, or struct with scalar\n",
 			reg_arg_name(env, argno),
 			btf_type_str(ref_t), ref_tname, arg_mem_size ? "void, " : "");
+		diag_call_arg_fmt(env, env->insn_idx, argno, meta->func_name,
+				  "Pass a verifier-tracked pointer to the expected kernel object type, not a pointer to stack storage or another memory buffer.",
+				  "the kfunc expects a pointer to %s, but this argument is %s and cannot be used as that kernel object pointer",
+				  expected_type,
+				  bpf_diag_reg_type_plain(env, reg->type));
 		return -EINVAL;
 	}
 	return arg_mem_size ? KF_ARG_PTR_TO_MEM_SIZE : KF_ARG_PTR_TO_MEM;
@@ -12461,6 +12572,11 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
 		if (btf_type_is_scalar(t)) {
 			if (reg->type != SCALAR_VALUE) {
 				verbose(env, "%s is not a scalar\n", reg_arg_name(env, argno));
+				diag_call_arg_fmt(env, insn_idx, argno, func_name,
+						  "Pass an integer scalar value for this argument, not a pointer or resource object.",
+						  "the kfunc expects an integer scalar, but %s is %s",
+						  reg_arg_name(env, argno),
+						  bpf_diag_reg_type_plain(env, reg->type));
 				return -EINVAL;
 			}
 
@@ -12472,6 +12588,11 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
 				if (!tnum_is_const(reg->var_off)) {
 					verbose(env, "%s must be a known constant\n",
 						reg_arg_name(env, argno));
+					diag_call_arg_fmt(env, insn_idx, argno,
+							  func_name,
+							  "Pass a compile-time constant or a value the verifier can prove is constant at this call.",
+							  "the kfunc requires this scalar argument to be a verifier-known constant, but %s is variable on this path",
+							  reg_arg_name(env, argno));
 					return -EINVAL;
 				}
 				if (regno >= 0)
@@ -12498,6 +12619,11 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
 				if (!tnum_is_const(reg->var_off)) {
 					verbose(env, "%s is not a const\n",
 						reg_arg_name(env, argno));
+					diag_call_arg_fmt(env, insn_idx, argno,
+							  func_name,
+							  "Pass a verifier-known constant size for this kfunc buffer argument.",
+							  "the kfunc uses this argument as a return-buffer size, but %s is variable on this path",
+							  reg_arg_name(env, argno));
 					return -EINVAL;
 				}
 
@@ -12518,28 +12644,50 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
 			return -EINVAL;
 		}
 
+		ref_t = btf_type_skip_modifiers(btf, t->type, &ref_id);
+		ref_tname = btf_name_by_offset(btf, ref_t->name_off);
+
 		if ((bpf_register_is_null(reg) || type_may_be_null(reg->type)) &&
 		    !is_kfunc_arg_nullable(meta->btf, &args[i])) {
+			const char *expected_type;
+
+			expected_type = bpf_diag_format_btf_type_scratch(env,
+									 1,
+									 btf,
+									 ref_id);
 			verbose(env, "Possibly NULL pointer passed to trusted %s\n",
 				reg_arg_name(env, argno));
+			diag_call_arg_fmt(env, insn_idx, argno, func_name,
+					  "Add a NULL check and call the kfunc only on the non-NULL path.",
+					  "the pointer may be NULL, but this kfunc requires a non-NULL pointer to %s",
+					  expected_type);
 			return -EACCES;
 		}
 
 		if (regno == meta->release_regno && !is_kfunc_arg_dynptr(meta->btf, &args[i]) &&
 		    !reg_is_referenced(env, reg) && !bpf_register_is_null(reg)) {
+			const char *expected_type;
+
+			expected_type = bpf_diag_format_btf_type_scratch(env,
+									 1,
+									 btf,
+									 ref_id);
 			verbose(env, "release kfunc %s expects referenced PTR_TO_BTF_ID passed to %s\n",
 				func_name, reg_arg_name(env, argno));
+			diag_call_arg_fmt(env, insn_idx, argno, func_name,
+					  "Pass the resource-owning pointer returned by the matching acquire kfunc, and avoid calling the release kfunc after ownership has already been transferred or released.",
+					  "release kfuncs require a resource-owning pointer to %s returned by a matching acquire kfunc",
+					  expected_type);
 			return -EINVAL;
 		}
 
 		if (reg_is_referenced(env, reg))
 			update_ref_obj(&meta->ref_obj, reg);
 
-		ref_t = btf_type_skip_modifiers(btf, t->type, &ref_id);
-		ref_tname = btf_name_by_offset(btf, ref_t->name_off);
-
-		kf_arg_type = get_kfunc_ptr_arg_type(env, caller, regs, meta, t, ref_t, ref_tname,
-						     args, i, nargs, argno, reg);
+		kf_arg_type = get_kfunc_ptr_arg_type(env, caller, regs, meta,
+						     t, ref_t, ref_tname,
+						     ref_id, args, i, nargs,
+						     argno, reg);
 		if (kf_arg_type < 0)
 			return kf_arg_type;
 
@@ -12587,13 +12735,35 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
 		case KF_ARG_PTR_TO_BTF_ID:
 			if (!is_trusted_reg(env, reg)) {
 				if (!is_kfunc_rcu(meta)) {
+					const char *expected_type;
+
+					expected_type = diag_btf_type_name(env, btf,
+									   ref_id);
 					verbose(env, "%s must be referenced or trusted\n",
 						reg_arg_name(env, argno));
+					diag_call_arg_fmt(env, insn_idx, argno,
+							  func_name,
+							  "Pass a pointer acquired from a verifier-tracked source, or call this kfunc only inside the required protection if it accepts RCU pointers.",
+							  "the kfunc requires a trusted or resource-owning pointer to %s, but %s is %s",
+							  expected_type,
+							  reg_arg_name(env, argno),
+							  bpf_diag_reg_type_plain(env, reg->type));
 					return -EINVAL;
 				}
 				if (!is_rcu_reg(reg)) {
+					const char *expected_type;
+
+					expected_type = diag_btf_type_name(env, btf,
+									   ref_id);
 					verbose(env, "%s must be a rcu pointer\n",
 						reg_arg_name(env, argno));
+					diag_call_arg_fmt(env, insn_idx, argno,
+							  func_name,
+							  "Use this kfunc with a pointer that is valid in an RCU read lock region.",
+							  "the kfunc requires an RCU-protected pointer to %s, but %s is %s",
+							  expected_type,
+							  reg_arg_name(env, argno),
+							  bpf_diag_reg_type_plain(env, reg->type));
 					return -EINVAL;
 				}
 			}
@@ -12636,6 +12806,11 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
 			if (reg->type != PTR_TO_CTX) {
 				verbose(env, "%s expected pointer to ctx, but got %s\n",
 					reg_arg_name(env, argno), reg_type_str(env, reg->type));
+				diag_call_arg_fmt(env, insn_idx, argno, func_name,
+						  "Pass the original program context pointer or preserve it before modifying registers.",
+						  "the kfunc expects a context pointer, but %s is %s",
+						  reg_arg_name(env, argno),
+						  bpf_diag_reg_type_plain(env, reg->type));
 				return -EINVAL;
 			}
 
@@ -12662,10 +12837,19 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
 			} else {
 				verbose(env, "%s expected pointer to allocated object\n",
 					reg_arg_name(env, argno));
+				diag_call_arg_fmt(env, insn_idx, argno, func_name,
+						  "Pass a pointer returned by the matching BPF object allocation path.",
+						  "the kfunc expects an allocated object pointer, but %s is %s",
+						  reg_arg_name(env, argno),
+						  bpf_diag_reg_type_plain(env, reg->type));
 				return -EINVAL;
 			}
 			if (!reg_is_referenced(env, reg)) {
 				verbose(env, "allocated object must be referenced\n");
+				diag_call_arg_fmt(env, insn_idx, argno, func_name,
+						  "Pass the owned object pointer before it is released or transferred.",
+						  "the allocated object pointer in %s must still carry verifier-tracked ownership, but this pointer no longer owns a live resource",
+						  reg_arg_name(env, argno));
 				return -EINVAL;
 			}
 			if (meta->btf == btf_vmlinux) {
@@ -12831,8 +13015,18 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
 				return -EINVAL;
 			}
 			ret = check_mem_reg(env, reg, argno, type_size);
-			if (ret < 0)
+			if (ret < 0) {
+				const char *expected_type;
+
+				expected_type = diag_btf_type_name(env, btf,
+								   ref_id);
+				diag_call_arg_fmt(env, insn_idx, argno, func_name,
+						  "Pass stack, map, context, or other verifier-known memory of the expected type and size, not an integer cast to a pointer.",
+						  "the kfunc expects %u bytes of memory for %s, but it is %s and not verifier-known memory",
+						  type_size, expected_type,
+						  bpf_diag_reg_type_plain(env, reg->type));
 				return ret;
+			}
 			break;
 		case KF_ARG_PTR_TO_MEM_SIZE:
 		{
@@ -12846,9 +13040,21 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
 				ret = check_kfunc_mem_size_reg(env, buff_reg, size_reg,
 							       argno, next_argno);
 				if (ret < 0) {
-					verbose(env, "%s and ", reg_arg_name(env, argno));
+					const char *mem_arg, *size_arg_name;
+
+					mem_arg = diag_arg_name(env, 1,
+								argno);
+					size_arg_name = diag_arg_name(env, 2,
+								      next_argno);
+					verbose(env, "%s and ", mem_arg);
 					verbose(env, "%s memory, len pair leads to invalid memory access\n",
-						reg_arg_name(env, next_argno));
+						size_arg_name);
+					diag_call_arg_fmt(env, insn_idx, argno,
+							  func_name,
+							  "Pass a stack, map, context, or other verifier-known memory pointer, and keep the paired length within that object.",
+							  "it is the memory pointer in a memory/length pair with %s, but it is %s and does not point to verifier-readable memory for the requested length",
+							  size_arg_name,
+							  bpf_diag_reg_type_plain(env, reg->type));
 					return ret;
 				}
 			}
@@ -12861,6 +13067,11 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
 				if (!tnum_is_const(size_reg->var_off)) {
 					verbose(env, "%s must be a known constant\n",
 						reg_arg_name(env, next_argno));
+					diag_call_arg_fmt(env, insn_idx, next_argno,
+							  func_name,
+							  "Pass a compile-time constant or verifier-known constant for this memory size argument.",
+							  "the kfunc requires the paired memory size argument %s to be a verifier-known constant, but it is variable on this path",
+							  reg_arg_name(env, next_argno));
 					return -EINVAL;
 				}
 				meta->arg_constant.found = true;
@@ -12880,8 +13091,16 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
 			break;
 		case KF_ARG_PTR_TO_REFCOUNTED_KPTR:
 			if (!type_is_ptr_alloc_obj(reg->type)) {
+				const char *expected_type;
+
+				expected_type = diag_btf_type_name(env, btf,
+								   ref_id);
 				verbose(env, "%s is neither owning or non-owning ref\n",
 					reg_arg_name(env, argno));
+				diag_call_arg_fmt(env, insn_idx, argno, func_name,
+						  "Pass a pointer returned by the matching BPF object allocation or lookup operation for this kfunc.",
+						  "the kfunc expects a pointer to BPF-managed refcounted object type %s, but this argument is not such an object pointer",
+						  expected_type);
 				return -EINVAL;
 			}
 			if (!type_is_non_owning_ref(reg->type))
@@ -12906,6 +13125,11 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
 			if (reg->type != PTR_TO_MAP_VALUE) {
 				verbose(env, "%s doesn't point to a const string\n",
 					reg_arg_name(env, argno));
+				diag_call_arg_fmt(env, insn_idx, argno, func_name,
+						  "Pass a constant string pointer that the verifier recognizes, such as a string stored in a read-only map value.",
+						  "the kfunc expects a pointer to a constant string stored in verifier-known memory, but %s is %s",
+						  reg_arg_name(env, argno),
+						  bpf_diag_reg_type_plain(env, reg->type));
 				return -EINVAL;
 			}
 			ret = check_arg_const_str(env, reg, argno);
@@ -12946,6 +13170,11 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
 			if (reg->type != PTR_TO_STACK) {
 				verbose(env, "%s doesn't point to an irq flag on stack\n",
 					reg_arg_name(env, argno));
+				diag_call_arg_fmt(env, insn_idx, argno, func_name,
+						  "Pass the same stack slot used by bpf_local_irq_save() or bpf_res_spin_lock_irqsave().",
+						  "the kfunc expects a stack pointer to an IRQ flag slot, but %s is %s",
+						  reg_arg_name(env, argno),
+						  bpf_diag_reg_type_plain(env, reg->type));
 				return -EINVAL;
 			}
 			ret = process_irq_flag(env, reg, argno, meta);
-- 
2.53.0


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

* [PATCH bpf-next v2 12/17] bpf: Report Execution Context Safety errors
  2026-06-19 20:59 [PATCH bpf-next v2 00/17] Redesign Verification Errors Kumar Kartikeya Dwivedi
                   ` (10 preceding siblings ...)
  2026-06-19 20:59 ` [PATCH bpf-next v2 11/17] bpf: Report Call Type Safety argument errors Kumar Kartikeya Dwivedi
@ 2026-06-19 20:59 ` Kumar Kartikeya Dwivedi
  2026-06-19 21:19   ` sashiko-bot
  2026-06-19 23:44   ` Alexei Starovoitov
  2026-06-19 20:59 ` [PATCH bpf-next v2 13/17] bpf: Report Program Structure CFG errors Kumar Kartikeya Dwivedi
                   ` (4 subsequent siblings)
  16 siblings, 2 replies; 34+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-06-19 20:59 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team

Augment selected sleepability and critical-section failures with Execution
Context Safety reports. Keep the existing verifier messages and add source
context, path history, and suggestions tied to the active context.

Use the context history recorded earlier to anchor causal paths to lock, IRQ,
RCU, and preempt regions instead of unrelated register updates.

Cover global calls while holding a lock, sleepable global function calls,
sleepable helpers, sleepable kfunc calls from disallowed contexts, operations
that exit while a context is still active, and unmatched context exits.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 kernel/bpf/diagnostics.c | 165 +++++++++++++++++++++++++++++++++++++++
 kernel/bpf/diagnostics.h |  14 ++++
 kernel/bpf/verifier.c    | 114 +++++++++++++++++++++++++++
 3 files changed, 293 insertions(+)

diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
index 19e72b07afc1..7c903e502973 100644
--- a/kernel/bpf/diagnostics.c
+++ b/kernel/bpf/diagnostics.c
@@ -844,6 +844,7 @@ static u32 bpf_diag_current_frameno(const struct bpf_verifier_env *env)
 }
 
 static int bpf_diag_stack_argno(u8 slot);
+static const char *bpf_diag_context_name(enum bpf_diag_context_kind kind);
 
 void bpf_diag_report_register_type(struct bpf_verifier_env *env,
 				   u32 insn_idx, int regno,
@@ -953,6 +954,170 @@ void bpf_diag_report_call_type(struct bpf_verifier_env *env, u32 insn_idx,
 	bpf_diag_report_suggestion(env, "%s", suggestion);
 }
 
+static const char *bpf_diag_context_constraint(enum bpf_diag_context_kind kind)
+{
+	switch (kind) {
+	case BPF_DIAG_CONTEXT_RCU:
+		return "RCU read-side critical sections cannot call operations that may sleep";
+	case BPF_DIAG_CONTEXT_PREEMPT:
+		return "preemption-disabled code cannot call operations that may sleep";
+	case BPF_DIAG_CONTEXT_IRQ:
+		return "IRQ-disabled code cannot call operations that may sleep";
+	case BPF_DIAG_CONTEXT_LOCK:
+		return "code holding a BPF spin lock cannot call operations that may sleep";
+	case BPF_DIAG_CONTEXT_NONE:
+	default:
+		return NULL;
+	}
+}
+
+static void bpf_diag_format_active_context(char *buf, size_t size, u32 depth,
+					   const char *context)
+{
+	if (depth == 1)
+		scnprintf(buf, size, "an active %s (depth 1)", context);
+	else
+		scnprintf(buf, size, "%u active %ss (depth %u)", depth,
+			  context, depth);
+}
+
+static u32 bpf_diag_context_depth(struct bpf_verifier_env *env,
+				  enum bpf_diag_context_kind kind)
+{
+	switch (kind) {
+	case BPF_DIAG_CONTEXT_RCU:
+		return env->cur_state->active_rcu_locks;
+	case BPF_DIAG_CONTEXT_PREEMPT:
+		return env->cur_state->active_preempt_locks;
+	case BPF_DIAG_CONTEXT_IRQ:
+		return env->cur_state->active_irq_id ? 1 : 0;
+	case BPF_DIAG_CONTEXT_LOCK:
+		return env->cur_state->active_locks;
+	case BPF_DIAG_CONTEXT_NONE:
+	default:
+		return 0;
+	}
+}
+
+void bpf_diag_report_execution_context(struct bpf_verifier_env *env,
+				       u32 insn_idx, const char *operation,
+				       enum bpf_diag_context_kind ctx_kind,
+				       const char *context,
+				       const char *suggestion)
+{
+	u32 depth = bpf_diag_context_depth(env, ctx_kind);
+	struct bpf_diag_history_opts opts = {
+		.scope = BPF_DIAG_HISTORY_SCOPE_CONTEXT,
+		.ctx_kind = ctx_kind,
+		.ctx_depth = depth,
+	};
+	const char *depth_buf;
+
+	bpf_diag_report_header(env, BPF_DIAG_CATEGORY_EXECUTION_CONTEXT_SAFETY,
+			       "operation is not allowed in this context");
+	if (bpf_diag_context_constraint(ctx_kind)) {
+		if (depth) {
+			depth_buf = bpf_diag_scratch_buf(env,
+							 2,
+							 NULL);
+			if (depth_buf)
+				bpf_diag_format_active_context((char *)depth_buf,
+							       BPF_DIAG_SCRATCH_STR_LEN,
+							       depth, context);
+			else
+				depth_buf = "";
+			bpf_diag_report_reason(env,
+					       "The operation %s cannot be used in %s because %s. This path is still inside %s.",
+					       operation, context,
+					       bpf_diag_context_constraint(ctx_kind),
+					       depth_buf);
+		} else {
+			bpf_diag_report_reason(env,
+					       "The operation %s cannot be used in %s because %s.",
+					       operation, context,
+					       bpf_diag_context_constraint(ctx_kind));
+		}
+	} else {
+		bpf_diag_report_reason(env,
+				       "The operation %s cannot be used in %s.",
+				       operation, context);
+	}
+
+	bpf_diag_report_section(env, "At");
+	bpf_diag_report_source(env, insn_idx, "error",
+			       "%s is not allowed in %s", operation, context);
+
+	if (ctx_kind != BPF_DIAG_CONTEXT_NONE)
+		bpf_diag_print_history(env, &opts);
+
+	bpf_diag_report_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_report_context_still_active(struct bpf_verifier_env *env,
+					  u32 insn_idx, const char *operation,
+					  enum bpf_diag_context_kind ctx_kind,
+					  const char *context,
+					  const char *suggestion)
+{
+	u32 depth = bpf_diag_context_depth(env, ctx_kind);
+	struct bpf_diag_history_opts opts = {
+		.scope = BPF_DIAG_HISTORY_SCOPE_CONTEXT,
+		.ctx_kind = ctx_kind,
+		.ctx_depth = depth,
+	};
+	const char *depth_buf;
+
+	depth_buf = bpf_diag_scratch_buf(env, 2, NULL);
+	if (depth_buf)
+		bpf_diag_format_active_context((char *)depth_buf,
+					       BPF_DIAG_SCRATCH_STR_LEN, depth,
+					       context);
+	else
+		depth_buf = "";
+
+	bpf_diag_report_header(env, BPF_DIAG_CATEGORY_EXECUTION_CONTEXT_SAFETY,
+			       "operation is not allowed in this context");
+	bpf_diag_report_reason(env,
+			       "The operation %s cannot be used while this path is still inside %s. Leave the region before this operation.",
+			       operation, depth_buf);
+
+	bpf_diag_report_section(env, "At");
+	bpf_diag_report_source(env, insn_idx, "error",
+			       "%s is not allowed before leaving %s",
+			       operation, context);
+
+	bpf_diag_print_history(env, &opts);
+
+	bpf_diag_report_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_report_context_underflow(struct bpf_verifier_env *env,
+				       u32 insn_idx, const char *operation,
+				       enum bpf_diag_context_kind ctx_kind,
+				       const char *suggestion)
+{
+	struct bpf_diag_history_opts opts = {
+		.scope = BPF_DIAG_HISTORY_SCOPE_CONTEXT,
+		.ctx_kind = ctx_kind,
+	};
+	const char *context = bpf_diag_context_name(ctx_kind);
+
+	bpf_diag_report_header(env, BPF_DIAG_CATEGORY_EXECUTION_CONTEXT_SAFETY,
+			       "unmatched context exit");
+	bpf_diag_report_reason(env,
+			       "The operation %s tries to leave %s, but this path has no active %s to leave. The current depth is 0.",
+			       operation, context, context);
+
+	bpf_diag_report_section(env, "At");
+	bpf_diag_report_source(env, insn_idx, "error",
+			       "%s has no matching enter on this path",
+			       operation);
+
+	bpf_diag_print_history(env, &opts);
+
+	bpf_diag_report_suggestion(env, "%s", suggestion);
+}
+
 void bpf_diag_report_invalid_deref(struct bpf_verifier_env *env, u32 insn_idx,
 				   int regno, const char *reg_name,
 				   const char *type_name,
diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
index 07d06d366f22..4611d94e7a18 100644
--- a/kernel/bpf/diagnostics.h
+++ b/kernel/bpf/diagnostics.h
@@ -202,6 +202,20 @@ void bpf_diag_report_call_type(struct bpf_verifier_env *env, u32 insn_idx,
 			       int argno, int regno, int stack_arg_slot,
 			       const char *call_name, const char *arg_name,
 			       const char *reason, const char *suggestion);
+void bpf_diag_report_execution_context(struct bpf_verifier_env *env,
+				       u32 insn_idx, const char *operation,
+				       enum bpf_diag_context_kind ctx_kind,
+				       const char *context,
+				       const char *suggestion);
+void bpf_diag_report_context_still_active(struct bpf_verifier_env *env,
+					  u32 insn_idx, const char *operation,
+					  enum bpf_diag_context_kind ctx_kind,
+					  const char *context,
+					  const char *suggestion);
+void bpf_diag_report_context_underflow(struct bpf_verifier_env *env,
+				       u32 insn_idx, const char *operation,
+				       enum bpf_diag_context_kind ctx_kind,
+				       const char *suggestion);
 void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx,
 			    bool cond_true);
 void bpf_diag_record_reg_mod(struct bpf_verifier_env *env, u32 insn_idx,
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index fdbf92bffc17..3174e12bea9a 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -215,6 +215,10 @@ static int ref_set_non_owning(struct bpf_verifier_env *env,
 static bool is_trusted_reg(struct bpf_verifier_env *env, const struct bpf_reg_state *reg);
 static inline bool in_sleepable_context(struct bpf_verifier_env *env);
 static const char *non_sleepable_context_description(struct bpf_verifier_env *env);
+static enum bpf_diag_context_kind
+non_sleepable_context_kind(struct bpf_verifier_env *env);
+static const char *
+non_sleepable_context_diag_description(struct bpf_verifier_env *env);
 static void scalar32_min_max_add(struct bpf_reg_state *dst_reg, struct bpf_reg_state *src_reg);
 static void scalar_min_max_add(struct bpf_reg_state *dst_reg, struct bpf_reg_state *src_reg);
 
@@ -9914,17 +9918,38 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 	if (err == -EFAULT)
 		return err;
 	if (bpf_subprog_is_global(env, subprog)) {
+		const char *context;
 		const char *sub_name = subprog_name(env, subprog);
+		const char *operation;
 
 		if (env->cur_state->active_locks) {
 			verbose(env, "global function calls are not allowed while holding a lock,\n"
 				     "use static function instead\n");
+			operation = bpf_diag_scratch_printf(env,
+							    1,
+							    "global function %s()",
+							    sub_name);
+			bpf_diag_report_execution_context(env, *insn_idx,
+							  operation,
+							  BPF_DIAG_CONTEXT_LOCK,
+							  "lock region",
+							  "Release the lock before calling the global function, or use a static function instead.");
 			return -EINVAL;
 		}
 
 		if (env->subprog_info[subprog].might_sleep && !in_sleepable_context(env)) {
 			verbose(env, "sleepable global function %s() called in %s\n",
 				sub_name, non_sleepable_context_description(env));
+			context = non_sleepable_context_diag_description(env);
+			operation = bpf_diag_scratch_printf(env,
+							    1,
+							    "sleepable global function %s()",
+							    sub_name);
+			bpf_diag_report_execution_context(env, *insn_idx,
+							  operation,
+							  non_sleepable_context_kind(env),
+							  context,
+							  "Move the call outside the critical section, or use a non-sleepable function.");
 			return -EINVAL;
 		}
 
@@ -10527,6 +10552,10 @@ static int check_resource_leak(struct bpf_verifier_env *env, bool exception_exit
 
 	if (check_lock && env->cur_state->active_locks) {
 		verbose(env, "%s cannot be used inside bpf_spin_lock-ed region\n", prefix);
+		bpf_diag_report_context_still_active(env, env->insn_idx, prefix,
+						     BPF_DIAG_CONTEXT_LOCK,
+						     "lock region",
+						     "Release the BPF spin lock before this operation on every path.");
 		return -EINVAL;
 	}
 
@@ -10538,16 +10567,28 @@ static int check_resource_leak(struct bpf_verifier_env *env, bool exception_exit
 
 	if (check_lock && env->cur_state->active_irq_id) {
 		verbose(env, "%s cannot be used inside bpf_local_irq_save-ed region\n", prefix);
+		bpf_diag_report_context_still_active(env, env->insn_idx, prefix,
+						     BPF_DIAG_CONTEXT_IRQ,
+						     "IRQ-disabled region",
+						     "Restore the saved IRQ state before this operation on every path.");
 		return -EINVAL;
 	}
 
 	if (check_lock && env->cur_state->active_rcu_locks) {
 		verbose(env, "%s cannot be used inside bpf_rcu_read_lock-ed region\n", prefix);
+		bpf_diag_report_context_still_active(env, env->insn_idx, prefix,
+						     BPF_DIAG_CONTEXT_RCU,
+						     "RCU read lock region",
+						     "Call bpf_rcu_read_unlock() before this operation on every path.");
 		return -EINVAL;
 	}
 
 	if (check_lock && env->cur_state->active_preempt_locks) {
 		verbose(env, "%s cannot be used inside bpf_preempt_disable-ed region\n", prefix);
+		bpf_diag_report_context_still_active(env, env->insn_idx, prefix,
+						     BPF_DIAG_CONTEXT_PREEMPT,
+						     "non-preemptible region",
+						     "Call bpf_preempt_enable() before this operation on every path.");
 		return -EINVAL;
 	}
 
@@ -10701,6 +10742,38 @@ static const char *non_sleepable_context_description(struct bpf_verifier_env *en
 	return "non-sleepable prog";
 }
 
+static enum bpf_diag_context_kind
+non_sleepable_context_kind(struct bpf_verifier_env *env)
+{
+	if (env->cur_state->active_rcu_locks)
+		return BPF_DIAG_CONTEXT_RCU;
+	if (env->cur_state->active_preempt_locks)
+		return BPF_DIAG_CONTEXT_PREEMPT;
+	if (env->cur_state->active_irq_id)
+		return BPF_DIAG_CONTEXT_IRQ;
+	if (env->cur_state->active_locks)
+		return BPF_DIAG_CONTEXT_LOCK;
+	return BPF_DIAG_CONTEXT_NONE;
+}
+
+static const char *
+non_sleepable_context_diag_description(struct bpf_verifier_env *env)
+{
+	switch (non_sleepable_context_kind(env)) {
+	case BPF_DIAG_CONTEXT_RCU:
+		return "RCU read lock region";
+	case BPF_DIAG_CONTEXT_PREEMPT:
+		return "non-preemptible region";
+	case BPF_DIAG_CONTEXT_IRQ:
+		return "IRQ-disabled region";
+	case BPF_DIAG_CONTEXT_LOCK:
+		return "lock region";
+	case BPF_DIAG_CONTEXT_NONE:
+	default:
+		return "non-sleepable program";
+	}
+}
+
 static int release_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
 		       bool convert_rcu, bool release_dynptr)
 {
@@ -10730,6 +10803,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
 	struct bpf_reg_state old_r0;
 	struct bpf_reg_state *regs;
 	struct bpf_call_arg_meta meta;
+	const char *operation;
 	int insn_idx = *insn_idx_p;
 	bool changes_data;
 	int i, err, func_id;
@@ -10778,6 +10852,15 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
 	if (fn->might_sleep && !in_sleepable_context(env)) {
 		verbose(env, "sleepable helper %s#%d in %s\n", func_id_name(func_id), func_id,
 			non_sleepable_context_description(env));
+		operation = bpf_diag_scratch_printf(env,
+						    1,
+						    "sleepable helper %s#%d",
+						    func_id_name(func_id),
+						    func_id);
+		bpf_diag_report_execution_context(env, insn_idx, operation,
+						  non_sleepable_context_kind(env),
+						  non_sleepable_context_diag_description(env),
+						  "Move the helper call outside the critical section, or use a non-sleepable helper.");
 		return -EINVAL;
 	}
 
@@ -13615,6 +13698,7 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 	struct bpf_kfunc_call_arg_meta meta;
 	struct bpf_insn_aux_data *insn_aux;
 	struct bpf_reg_state old_r0;
+	const char *operation;
 	int err, insn_idx = *insn_idx_p;
 	const struct btf_param *args;
 	u32 i, nargs, ptr_type_id;
@@ -13674,6 +13758,14 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 	sleepable = bpf_is_kfunc_sleepable(&meta);
 	if (sleepable && !in_sleepable(env)) {
 		verbose(env, "program must be sleepable to call sleepable kfunc %s\n", func_name);
+		operation = bpf_diag_scratch_printf(env,
+						    1,
+						    "sleepable kfunc %s",
+						    func_name);
+		bpf_diag_report_execution_context(env, insn_idx, operation,
+						  BPF_DIAG_CONTEXT_NONE,
+						  "non-sleepable program",
+						  "Mark the program sleepable if the program type allows it, or use a non-sleepable kfunc.");
 		return -EACCES;
 	}
 
@@ -13749,6 +13841,10 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 	} else if (rcu_unlock) {
 		if (env->cur_state->active_rcu_locks == 0) {
 			verbose(env, "unmatched rcu read unlock (kernel function %s)\n", func_name);
+			bpf_diag_report_context_underflow(env, insn_idx,
+							  func_name,
+							  BPF_DIAG_CONTEXT_RCU,
+							  "Remove the extra bpf_rcu_read_unlock() call, or ensure this path first enters an RCU read lock region.");
 			return -EINVAL;
 		}
 		env->cur_state->active_rcu_locks--;
@@ -13764,6 +13860,10 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 	} else if (preempt_enable) {
 		if (env->cur_state->active_preempt_locks == 0) {
 			verbose(env, "unmatched attempt to enable preemption (kernel function %s)\n", func_name);
+			bpf_diag_report_context_underflow(env, insn_idx,
+							  func_name,
+							  BPF_DIAG_CONTEXT_PREEMPT,
+							  "Remove the extra bpf_preempt_enable() call, or ensure this path first disables preemption.");
 			return -EINVAL;
 		}
 		env->cur_state->active_preempt_locks--;
@@ -13775,6 +13875,14 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 	if (sleepable && !in_sleepable_context(env)) {
 		verbose(env, "kernel func %s is sleepable within %s\n",
 			func_name, non_sleepable_context_description(env));
+		operation = bpf_diag_scratch_printf(env,
+						    1,
+						    "sleepable kfunc %s",
+						    func_name);
+		bpf_diag_report_execution_context(env, insn_idx, operation,
+						  non_sleepable_context_kind(env),
+						  non_sleepable_context_diag_description(env),
+						  "Move the kfunc call outside the critical section, or use a non-sleepable kfunc.");
 		return -EACCES;
 	}
 
@@ -18121,6 +18229,12 @@ static int do_check_insn(struct bpf_verifier_env *env, bool *do_print_state)
 				     (insn->off != 0 || !kfunc_spin_allowed(insn->imm)))) {
 					verbose(env,
 						"function calls are not allowed while holding a lock\n");
+					bpf_diag_report_context_still_active(env,
+									     env->insn_idx,
+									     "function call",
+									     BPF_DIAG_CONTEXT_LOCK,
+									     "lock region",
+									     "Release the BPF spin lock before making this call, or move the call outside the locked region.");
 					return -EINVAL;
 				}
 			}
-- 
2.53.0


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

* [PATCH bpf-next v2 13/17] bpf: Report Program Structure CFG errors
  2026-06-19 20:59 [PATCH bpf-next v2 00/17] Redesign Verification Errors Kumar Kartikeya Dwivedi
                   ` (11 preceding siblings ...)
  2026-06-19 20:59 ` [PATCH bpf-next v2 12/17] bpf: Report Execution Context Safety errors Kumar Kartikeya Dwivedi
@ 2026-06-19 20:59 ` Kumar Kartikeya Dwivedi
  2026-06-19 20:59 ` [PATCH bpf-next v2 14/17] bpf: Report Policy helper and kfunc errors Kumar Kartikeya Dwivedi
                   ` (3 subsequent siblings)
  16 siblings, 0 replies; 34+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-06-19 20:59 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team

Augment selected subprogram CFG validation failures with Program Structure
reports. These errors are structural rather than path-dependent, so the report
focuses on source and instruction context instead of causal history.

Cover jumps that leave the current subprogram, subprograms whose last
instruction can fall through into the next subprogram, and recursive bpf2bpf
call graph edges.

Format long jump-range reasons directly in diagnostics.c, and keep the
fallthrough suggestion aligned with the verifier check by suggesting exit or
explicit jumps.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 kernel/bpf/cfg.c         | 39 +++++++++++++++++++++++++++++++++++++++
 kernel/bpf/diagnostics.c | 21 +++++++++++++++++++++
 kernel/bpf/diagnostics.h |  5 +++++
 kernel/bpf/verifier.c    | 16 ++++++++++++++++
 4 files changed, 81 insertions(+)

diff --git a/kernel/bpf/cfg.c b/kernel/bpf/cfg.c
index 26d37066465f..032b3dc56f2e 100644
--- a/kernel/bpf/cfg.c
+++ b/kernel/bpf/cfg.c
@@ -5,6 +5,8 @@
 #include <linux/filter.h>
 #include <linux/sort.h>
 
+#include "diagnostics.h"
+
 #define verbose(env, fmt, args...) bpf_verifier_log_write(env, fmt, ##args)
 
 /* non-recursive DFS pseudo code
@@ -113,6 +115,11 @@ static int push_insn(int t, int w, int e, struct bpf_verifier_env *env)
 	if (w < 0 || w >= env->prog->len) {
 		verbose_linfo(env, t, "%d: ", t);
 		verbose(env, "jump out of range from insn %d to %d\n", t, w);
+		bpf_diag_report_program_structure(env, t,
+						  "jump out of range",
+						  "Keep branch targets inside the program.",
+						  "Instruction %d jumps to instruction %d, but the program only contains instructions 0 through %d.",
+						  t, w, env->prog->len - 1);
 		return -EINVAL;
 	}
 
@@ -136,6 +143,11 @@ static int push_insn(int t, int w, int e, struct bpf_verifier_env *env)
 		verbose_linfo(env, t, "%d: ", t);
 		verbose_linfo(env, w, "%d: ", w);
 		verbose(env, "back-edge from insn %d to %d\n", t, w);
+		bpf_diag_report_program_structure(env, t,
+						  "back-edge is not allowed",
+						  "Rewrite the control flow as a verifier-supported bounded loop, or load with privileges that allow this back-edge.",
+						  "Instruction %d branches back to instruction %d. This program is being rejected without the privilege needed for this back-edge.",
+						  t, w);
 		return -EINVAL;
 	} else if (insn_state[w] == EXPLORED) {
 		/* forward- or cross-edge */
@@ -316,6 +328,11 @@ static struct bpf_iarray *jt_from_subprog(struct bpf_verifier_env *env,
 
 	if (!jt) {
 		verbose(env, "no jump tables found for subprog starting at %u\n", subprog_start);
+		bpf_diag_report_program_structure(env, subprog_start,
+						  "missing jump table",
+						  "Provide a jump table whose entries target this subprogram.",
+						  "No jump table was found for the subprogram that starts at instruction %u.",
+						  subprog_start);
 		return ERR_PTR(-EINVAL);
 	}
 
@@ -343,6 +360,12 @@ create_jt(int t, struct bpf_verifier_env *env)
 		if (jt->items[i] < subprog_start || jt->items[i] >= subprog_end) {
 			verbose(env, "jump table for insn %d points outside of the subprog [%u,%u]\n",
 					t, subprog_start, subprog_end);
+			bpf_diag_report_program_structure(env, t,
+							  "jump table target out of range",
+							  "Keep every jump-table target inside the same subprogram.",
+							  "The jump table for instruction %d points outside subprogram range [%u,%u).",
+							  t, subprog_start,
+							  subprog_end);
 			kvfree(jt);
 			return ERR_PTR(-EINVAL);
 		}
@@ -374,6 +397,12 @@ static int visit_gotox_insn(int t, struct bpf_verifier_env *env)
 		w = jt->items[i];
 		if (w < 0 || w >= env->prog->len) {
 			verbose(env, "indirect jump out of range from insn %d to %d\n", t, w);
+			bpf_diag_report_program_structure(env, t,
+							  "indirect jump out of range",
+							  "Keep indirect jump targets inside the program.",
+							  "Instruction %d can jump indirectly to instruction %d, but the program only contains instructions 0 through %d.",
+							  t, w,
+							  env->prog->len - 1);
 			return -EINVAL;
 		}
 
@@ -624,12 +653,22 @@ int bpf_check_cfg(struct bpf_verifier_env *env)
 
 		if (insn_state[i] != EXPLORED) {
 			verbose(env, "unreachable insn %d\n", i);
+			bpf_diag_report_program_structure(env, i,
+							  "unreachable instruction",
+							  "Remove the unreachable instruction or add valid control flow that reaches it.",
+							  "Instruction %d is not reachable from the program entry point.",
+							  i);
 			ret = -EINVAL;
 			goto err_free;
 		}
 		if (bpf_is_ldimm64(insn)) {
 			if (insn_state[i + 1] != 0) {
 				verbose(env, "jump into the middle of ldimm64 insn %d\n", i);
+				bpf_diag_report_program_structure(env, i,
+								  "jump into ldimm64 immediate",
+								  "Target the first instruction of the ldimm64 pair, or restructure the jump target.",
+								  "Control flow reaches the second half of the ldimm64 instruction pair that starts at instruction %d.",
+								  i);
 				ret = -EINVAL;
 				goto err_free;
 			}
diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
index 7c903e502973..d6893b2626c4 100644
--- a/kernel/bpf/diagnostics.c
+++ b/kernel/bpf/diagnostics.c
@@ -1118,6 +1118,27 @@ void bpf_diag_report_context_underflow(struct bpf_verifier_env *env,
 	bpf_diag_report_suggestion(env, "%s", suggestion);
 }
 
+void bpf_diag_report_program_structure(struct bpf_verifier_env *env,
+				       u32 insn_idx, const char *problem,
+				       const char *suggestion,
+				       const char *reason_fmt, ...)
+{
+	va_list args;
+
+	bpf_diag_report_header(env, BPF_DIAG_CATEGORY_PROGRAM_STRUCTURE,
+			       problem);
+	bpf_diag_report_section(env, "Reason");
+
+	va_start(args, reason_fmt);
+	bpf_diag_vprint_indented(env, reason_fmt, args);
+	va_end(args);
+
+	bpf_diag_report_section(env, "At");
+	bpf_diag_report_source(env, insn_idx, "error", "%s", problem);
+
+	bpf_diag_report_suggestion(env, "%s", suggestion);
+}
+
 void bpf_diag_report_invalid_deref(struct bpf_verifier_env *env, u32 insn_idx,
 				   int regno, const char *reg_name,
 				   const char *type_name,
diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
index 4611d94e7a18..b881ccaf6deb 100644
--- a/kernel/bpf/diagnostics.h
+++ b/kernel/bpf/diagnostics.h
@@ -216,6 +216,11 @@ void bpf_diag_report_context_underflow(struct bpf_verifier_env *env,
 				       u32 insn_idx, const char *operation,
 				       enum bpf_diag_context_kind ctx_kind,
 				       const char *suggestion);
+void bpf_diag_report_program_structure(struct bpf_verifier_env *env,
+				       u32 insn_idx, const char *problem,
+				       const char *suggestion,
+				       const char *reason_fmt, ...)
+		__printf(5, 6);
 void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx,
 			    bool cond_true);
 void bpf_diag_record_reg_mod(struct bpf_verifier_env *env, u32 insn_idx,
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 3174e12bea9a..e923366c6fdb 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -2938,6 +2938,13 @@ static int check_subprogs(struct bpf_verifier_env *env)
 		off = i + bpf_jmp_offset(&insn[i]) + 1;
 		if (off < subprog_start || off >= subprog_end) {
 			verbose(env, "jump out of range from insn %d to %d\n", i, off);
+			bpf_diag_report_program_structure(env, i,
+							  "jump out of range",
+							  "Keep branch targets within the same subprogram, or use an explicit subprogram call.",
+							  "Instruction %d jumps to instruction %d, but subprogram %d only contains instructions %d through %d. A branch target must stay inside the same subprogram.",
+							  i, off, cur_subprog,
+							  subprog_start,
+							  subprog_end - 1);
 			return -EINVAL;
 		}
 next:
@@ -2950,6 +2957,11 @@ static int check_subprogs(struct bpf_verifier_env *env)
 			    code != (BPF_JMP32 | BPF_JA) &&
 			    code != (BPF_JMP | BPF_JA)) {
 				verbose(env, "last insn is not an exit or jmp\n");
+				bpf_diag_report_program_structure(env, i,
+								  "subprogram can fall through",
+								  "End each subprogram with an exit or an explicit jump that keeps control flow inside the subprogram.",
+								  "Subprogram %d reaches its last instruction %d without an exit or jump, so control could continue into the next subprogram.",
+								  cur_subprog, i);
 				return -EINVAL;
 			}
 			subprog_start = subprog_end;
@@ -3022,6 +3034,10 @@ static int sort_subprogs_topo(struct bpf_verifier_env *env)
 					verbose(env, "recursive call from %s() to %s()\n",
 						subprog_name(env, cur),
 						subprog_name(env, callee));
+					bpf_diag_report_program_structure(env, idx,
+									  "recursive subprogram call",
+									  "Rewrite the recursion as an explicit bounded loop, or split the logic so subprogram calls do not form a cycle.",
+									  "This bpf2bpf call would make the subprogram call graph recursive. The verifier requires a finite, acyclic call graph so it can bound stack depth and analysis.");
 					ret = -EINVAL;
 					goto out;
 				}
-- 
2.53.0


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

* [PATCH bpf-next v2 14/17] bpf: Report Policy helper and kfunc errors
  2026-06-19 20:59 [PATCH bpf-next v2 00/17] Redesign Verification Errors Kumar Kartikeya Dwivedi
                   ` (12 preceding siblings ...)
  2026-06-19 20:59 ` [PATCH bpf-next v2 13/17] bpf: Report Program Structure CFG errors Kumar Kartikeya Dwivedi
@ 2026-06-19 20:59 ` Kumar Kartikeya Dwivedi
  2026-06-19 20:59 ` [PATCH bpf-next v2 15/17] bpf: Report Verifier Limit errors Kumar Kartikeya Dwivedi
                   ` (2 subsequent siblings)
  16 siblings, 0 replies; 34+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-06-19 20:59 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team

Augment selected helper and kfunc allowability failures with Policy reports.
These reports explain which requested operation is forbidden and why, without
adding path history for non-path-dependent policy checks.

Cover unprivileged bpf2bpf and kfunc use, helper program-type restrictions,
GPL-only helpers, helper-specific allow callbacks, kfunc allowability, and
destructive kfunc capability checks.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 kernel/bpf/diagnostics.c | 16 +++++++++++++++
 kernel/bpf/diagnostics.h |  3 +++
 kernel/bpf/verifier.c    | 44 +++++++++++++++++++++++++++++++++++++++-
 3 files changed, 62 insertions(+), 1 deletion(-)

diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
index d6893b2626c4..f199a6eeea54 100644
--- a/kernel/bpf/diagnostics.c
+++ b/kernel/bpf/diagnostics.c
@@ -1139,6 +1139,22 @@ void bpf_diag_report_program_structure(struct bpf_verifier_env *env,
 	bpf_diag_report_suggestion(env, "%s", suggestion);
 }
 
+void bpf_diag_report_policy(struct bpf_verifier_env *env, u32 insn_idx,
+			    const char *operation, const char *reason,
+			    const char *suggestion)
+{
+	bpf_diag_report_header(env, BPF_DIAG_CATEGORY_POLICY,
+			       "operation is not allowed");
+	bpf_diag_report_reason(env, "The operation %s is not allowed: %s.",
+			       operation, reason);
+
+	bpf_diag_report_section(env, "At");
+	bpf_diag_report_source(env, insn_idx, "error",
+			       "policy check failed for %s", operation);
+
+	bpf_diag_report_suggestion(env, "%s", suggestion);
+}
+
 void bpf_diag_report_invalid_deref(struct bpf_verifier_env *env, u32 insn_idx,
 				   int regno, const char *reg_name,
 				   const char *type_name,
diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
index b881ccaf6deb..99f82292a740 100644
--- a/kernel/bpf/diagnostics.h
+++ b/kernel/bpf/diagnostics.h
@@ -221,6 +221,9 @@ void bpf_diag_report_program_structure(struct bpf_verifier_env *env,
 				       const char *suggestion,
 				       const char *reason_fmt, ...)
 		__printf(5, 6);
+void bpf_diag_report_policy(struct bpf_verifier_env *env, u32 insn_idx,
+			    const char *operation, const char *reason,
+			    const char *suggestion);
 void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx,
 			    bool cond_true);
 void bpf_diag_record_reg_mod(struct bpf_verifier_env *env, u32 insn_idx,
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index e923366c6fdb..7938c51eb454 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -2860,6 +2860,10 @@ static int add_subprog_and_kfunc(struct bpf_verifier_env *env)
 
 		if (!env->bpf_capable) {
 			verbose(env, "loading/calling other bpf or kernel functions are allowed for CAP_BPF and CAP_SYS_ADMIN\n");
+			bpf_diag_report_policy(env, i,
+					       "bpf-to-bpf or kernel function call",
+					       "loading or calling other BPF or kernel functions requires CAP_BPF or CAP_SYS_ADMIN",
+					       "Load this program with the required capability, or avoid bpf-to-bpf and kernel function calls in unprivileged programs.");
 			return -EPERM;
 		}
 
@@ -10835,17 +10839,41 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
 	if (err) {
 		verbose(env, "program of this type cannot use helper %s#%d\n",
 			func_id_name(func_id), func_id);
+		operation = bpf_diag_scratch_printf(env,
+						    1,
+						    "helper %s#%d",
+						    func_id_name(func_id),
+						    func_id);
+		bpf_diag_report_policy(env, insn_idx, operation,
+				       "this program type does not allow the helper",
+				       "Use a helper allowed for this program type, or move the logic to a compatible program type.");
 		return err;
 	}
 
 	/* eBPF programs must be GPL compatible to use GPL-ed functions */
 	if (!env->prog->gpl_compatible && fn->gpl_only) {
 		verbose(env, "cannot call GPL-restricted function from non-GPL compatible program\n");
+		operation = bpf_diag_scratch_printf(env,
+						    1,
+						    "helper %s#%d",
+						    func_id_name(func_id),
+						    func_id);
+		bpf_diag_report_policy(env, insn_idx, operation,
+				       "this helper is restricted to GPL-compatible programs",
+				       "Use a GPL-compatible license, or replace the helper with one that is available to non-GPL programs.");
 		return -EINVAL;
 	}
 
 	if (fn->allowed && !fn->allowed(env->prog)) {
 		verbose(env, "helper call is not allowed in probe\n");
+		operation = bpf_diag_scratch_printf(env,
+						    1,
+						    "helper %s#%d",
+						    func_id_name(func_id),
+						    func_id);
+		bpf_diag_report_policy(env, insn_idx, operation,
+				       "the helper-specific policy callback rejected this program",
+				       "Use the helper only from an allowed attach point or program configuration.");
 		return -EINVAL;
 	}
 
@@ -13726,8 +13754,15 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 		return 0;
 
 	err = bpf_fetch_kfunc_arg_meta(env, insn->imm, insn->off, &meta);
-	if (err == -EACCES && meta.func_name)
+	if (err == -EACCES && meta.func_name) {
 		verbose(env, "calling kernel function %s is not allowed\n", meta.func_name);
+		operation = bpf_diag_scratch_printf(env,
+						    1,
+						    "kfunc %s", meta.func_name);
+		bpf_diag_report_policy(env, insn_idx, operation,
+				       "this program cannot call the kfunc",
+				       "Use a kfunc allowed for this program type and attach point, or change the program context.");
+	}
 	if (err)
 		return err;
 	desc_btf = meta.btf;
@@ -13768,6 +13803,13 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 
 	if (is_kfunc_destructive(&meta) && !capable(CAP_SYS_BOOT)) {
 		verbose(env, "destructive kfunc calls require CAP_SYS_BOOT capability\n");
+		operation = bpf_diag_scratch_printf(env,
+						    1,
+						    "destructive kfunc %s",
+						    meta.func_name);
+		bpf_diag_report_policy(env, insn_idx, operation,
+				       "destructive kfuncs require CAP_SYS_BOOT",
+				       "Load the program with CAP_SYS_BOOT, or avoid destructive kfuncs.");
 		return -EACCES;
 	}
 
-- 
2.53.0


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

* [PATCH bpf-next v2 15/17] bpf: Report Verifier Limit errors
  2026-06-19 20:59 [PATCH bpf-next v2 00/17] Redesign Verification Errors Kumar Kartikeya Dwivedi
                   ` (13 preceding siblings ...)
  2026-06-19 20:59 ` [PATCH bpf-next v2 14/17] bpf: Report Policy helper and kfunc errors Kumar Kartikeya Dwivedi
@ 2026-06-19 20:59 ` Kumar Kartikeya Dwivedi
  2026-06-19 20:59 ` [PATCH bpf-next v2 16/17] bpf: Report Verifier Internal errors Kumar Kartikeya Dwivedi
  2026-06-19 20:59 ` [PATCH bpf-next v2 17/17] bpf: Gate verifier diagnostics on log level Kumar Kartikeya Dwivedi
  16 siblings, 0 replies; 34+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-06-19 20:59 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team

Augment selected verifier limit failures with Verifier Limit reports. These
reports focus on the limit that was exceeded and the observed value or
condition, rather than causal branch history.

Cover tail-call stack constraints, per-subprogram stack depth, combined call
stack depth, static and runtime bpf2bpf call-frame depth, processed-instruction
complexity, and liveness analysis complexity.

Format reason text in diagnostics.c and allocate call-chain descriptions only
on failure paths, preserving useful call-chain context without adding large
local buffers to verifier frames.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 kernel/bpf/diagnostics.c |  40 ++++++++++++++
 kernel/bpf/diagnostics.h |   4 ++
 kernel/bpf/liveness.c    |   6 +++
 kernel/bpf/verifier.c    | 114 ++++++++++++++++++++++++++++++++++++++-
 4 files changed, 163 insertions(+), 1 deletion(-)

diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
index f199a6eeea54..18217f5f709a 100644
--- a/kernel/bpf/diagnostics.c
+++ b/kernel/bpf/diagnostics.c
@@ -1155,6 +1155,46 @@ void bpf_diag_report_policy(struct bpf_verifier_env *env, u32 insn_idx,
 	bpf_diag_report_suggestion(env, "%s", suggestion);
 }
 
+void bpf_diag_report_limit(struct bpf_verifier_env *env, u32 insn_idx,
+			   const char *limit, const char *suggestion,
+			   const char *reason_fmt, ...)
+{
+	char *reason, *text;
+	va_list args;
+
+	bpf_diag_report_header(env, BPF_DIAG_CATEGORY_VERIFIER_LIMIT,
+			       "limit exceeded");
+	bpf_diag_report_section(env, "Reason");
+
+	va_start(args, reason_fmt);
+	reason = kvasprintf(GFP_KERNEL_ACCOUNT, reason_fmt, args);
+	va_end(args);
+	if (!reason) {
+		bpf_diag_write(env, "%s<failed to allocate diagnostic text>\n",
+			       BPF_DIAG_TEXT_INDENT);
+		goto source;
+	}
+
+	text = kasprintf(GFP_KERNEL_ACCOUNT, "The %s limit was exceeded: %s.",
+			 limit, reason);
+	kfree(reason);
+	if (!text) {
+		bpf_diag_write(env, "%s<failed to allocate diagnostic text>\n",
+			       BPF_DIAG_TEXT_INDENT);
+		goto source;
+	}
+
+	bpf_diag_print_wrapped_text(env, text);
+	kfree(text);
+
+source:
+	bpf_diag_report_section(env, "At");
+	bpf_diag_report_source(env, insn_idx, "error",
+			       "limit exceeded: %s", limit);
+
+	bpf_diag_report_suggestion(env, "%s", suggestion);
+}
+
 void bpf_diag_report_invalid_deref(struct bpf_verifier_env *env, u32 insn_idx,
 				   int regno, const char *reg_name,
 				   const char *type_name,
diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
index 99f82292a740..559c0169062c 100644
--- a/kernel/bpf/diagnostics.h
+++ b/kernel/bpf/diagnostics.h
@@ -224,6 +224,10 @@ void bpf_diag_report_program_structure(struct bpf_verifier_env *env,
 void bpf_diag_report_policy(struct bpf_verifier_env *env, u32 insn_idx,
 			    const char *operation, const char *reason,
 			    const char *suggestion);
+void bpf_diag_report_limit(struct bpf_verifier_env *env, u32 insn_idx,
+			   const char *limit, const char *suggestion,
+			   const char *reason_fmt, ...)
+		__printf(5, 6);
 void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx,
 			    bool cond_true);
 void bpf_diag_record_reg_mod(struct bpf_verifier_env *env, u32 insn_idx,
diff --git a/kernel/bpf/liveness.c b/kernel/bpf/liveness.c
index 0aadfbae0acc..288d47b6a408 100644
--- a/kernel/bpf/liveness.c
+++ b/kernel/bpf/liveness.c
@@ -8,6 +8,8 @@
 #include <linux/slab.h>
 #include <linux/sort.h>
 
+#include "diagnostics.h"
+
 #define verbose(env, fmt, args...) bpf_verifier_log_write(env, fmt, ##args)
 
 struct per_frame_masks {
@@ -1862,6 +1864,10 @@ static int analyze_subprog(struct bpf_verifier_env *env,
 	if (++env->liveness->subprog_calls > 10000) {
 		verbose(env, "liveness analysis exceeded complexity limit (%d calls)\n",
 			env->liveness->subprog_calls);
+		bpf_diag_report_limit(env, start,
+				      "liveness analysis complexity",
+				      "Reduce the number of distinct call paths or argument patterns reaching these subprograms.",
+				      "The verifier recomputed subprogram liveness too many times while tracking stack and register reads across call paths");
 		return -E2BIG;
 	}
 
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 7938c51eb454..ec51e0c81053 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -5277,6 +5277,46 @@ struct bpf_subprog_call_depth_info {
 	int frame; /* # of consecutive static call stack frames on top of stack */
 };
 
+static char *bpf_diag_append_subprog_chain(const struct bpf_verifier_env *env,
+					   char *chain, int subprog)
+{
+	const char *prefix = chain && *chain ? " -> " : "";
+	const char *name = subprog_name(env, subprog);
+	const char *old = chain ?: "";
+	char *next;
+
+	if (name && *name)
+		next = kasprintf(GFP_KERNEL_ACCOUNT, "%s%s%s",
+				 old, prefix, name);
+	else
+		next = kasprintf(GFP_KERNEL_ACCOUNT, "%s%ssubprogram %d",
+				 old, prefix, subprog);
+	if (!next)
+		return chain;
+
+	kfree(chain);
+	return next;
+}
+
+static char *
+bpf_diag_alloc_subprog_call_chain(const struct bpf_verifier_env *env,
+				  struct bpf_subprog_call_depth_info *dinfo,
+				  int idx)
+{
+	int call_chain[MAX_CALL_FRAMES + 1];
+	int i, subprog, cnt = 0;
+	char *chain = NULL;
+
+	for (subprog = idx; subprog >= 0 && cnt < ARRAY_SIZE(call_chain);
+	     subprog = dinfo[subprog].caller)
+		call_chain[cnt++] = subprog;
+
+	for (i = cnt - 1; i >= 0; i--)
+		chain = bpf_diag_append_subprog_chain(env, chain, call_chain[i]);
+
+	return chain;
+}
+
 /* starting from main bpf function walk all instructions of the function
  * and recursively walk all callees that given function can call.
  * Ignore jump and exit insns.
@@ -5319,9 +5359,17 @@ static int check_max_stack_depth_subprog(struct bpf_verifier_env *env, int idx,
 	 * of caller's stack as shown on the example above.
 	 */
 	if (idx && subprog[idx].has_tail_call && depth >= 256) {
+		char *chain = bpf_diag_alloc_subprog_call_chain(env, dinfo, idx);
+
 		verbose(env,
 			"tail_calls are not allowed when call stack of previous frames is %d bytes. Too large\n",
 			depth);
+		bpf_diag_report_limit(env, subprog[idx].start,
+				      "call stack with tail calls",
+				      "Reduce stack usage in caller frames, or avoid combining deep bpf2bpf calls with tail calls.",
+				      "Call chain %s reaches a subprogram with tail calls after caller frames already use %d bytes; tail-call paths are limited to 256 bytes in caller frames",
+				      chain ?: "the current call chain", depth);
+		kfree(chain);
 		return -EACCES;
 	}
 
@@ -5343,8 +5391,18 @@ static int check_max_stack_depth_subprog(struct bpf_verifier_env *env, int idx,
 		if (subprog_depth > env->max_stack_depth)
 			env->max_stack_depth = subprog_depth;
 		if (subprog_depth > MAX_BPF_STACK) {
+			char *chain;
+
 			verbose(env, "stack size of subprog %d is %d. Too large\n",
 				idx, subprog_depth);
+			chain = bpf_diag_alloc_subprog_call_chain(env, dinfo, idx);
+			bpf_diag_report_limit(env, subprog[idx].start,
+					      "subprogram stack depth",
+					      "Reduce stack usage in this subprogram, or move large data out of the BPF stack.",
+					      "Call chain %s reaches a subprogram that uses %d bytes of stack, exceeding the %d byte limit for one BPF stack frame",
+					      chain ?: "the current call chain",
+					      subprog_depth, MAX_BPF_STACK);
+			kfree(chain);
 			return -EACCES;
 		}
 	} else {
@@ -5358,13 +5416,25 @@ static int check_max_stack_depth_subprog(struct bpf_verifier_env *env, int idx,
 
 			verbose(env, "combined stack size of %d calls is %d. Too large\n",
 				total, depth);
+			{
+				char *chain;
+
+				chain = bpf_diag_alloc_subprog_call_chain(env, dinfo, idx);
+				bpf_diag_report_limit(env, subprog[idx].start,
+						      "combined call stack depth",
+						      "Reduce stack usage or call depth along this call chain.",
+						      "Call chain %s uses %d bytes of stack across %d nested calls, exceeding the %d byte limit",
+						      chain ?: "the current call chain",
+						      depth, total, MAX_BPF_STACK);
+				kfree(chain);
+			}
 			return -EACCES;
 		}
 	}
 continue_func:
 	subprog_end = subprog[idx + 1].start;
 	for (; i < subprog_end; i++) {
-		int next_insn, sidx;
+		int next_insn, call_insn, sidx;
 
 		if (bpf_pseudo_kfunc_call(insn + i) && !insn[i].off) {
 			bool err = false;
@@ -5415,6 +5485,7 @@ static int check_max_stack_depth_subprog(struct bpf_verifier_env *env, int idx,
 		/* push caller idx into callee's dinfo */
 		dinfo[sidx].caller = idx;
 
+		call_insn = i;
 		i = next_insn;
 
 		idx = sidx;
@@ -5426,8 +5497,18 @@ static int check_max_stack_depth_subprog(struct bpf_verifier_env *env, int idx,
 
 		frame = bpf_subprog_is_global(env, idx) ? 0 : frame + 1;
 		if (frame >= MAX_CALL_FRAMES) {
+			char *chain;
+
 			verbose(env, "the call stack of %d frames is too deep !\n",
 				frame);
+			chain = bpf_diag_alloc_subprog_call_chain(env, dinfo, idx);
+			bpf_diag_report_limit(env, call_insn,
+					      "bpf2bpf call frames",
+					      "Reduce the number of nested bpf2bpf calls on this path.",
+					      "Call chain %s reaches %d static bpf2bpf call frames, exceeding the %d frame limit",
+					      chain ?: "the current call chain",
+					      frame, MAX_CALL_FRAMES);
+			kfree(chain);
 			return -E2BIG;
 		}
 		goto process_func;
@@ -9646,6 +9727,24 @@ typedef int (*set_callee_state_fn)(struct bpf_verifier_env *env,
 				   struct bpf_func_state *callee,
 				   int insn_idx);
 
+static char *
+bpf_diag_alloc_state_call_chain(const struct bpf_verifier_env *env,
+				const struct bpf_verifier_state *state,
+				int next_subprog)
+{
+	char *chain = NULL;
+	int i;
+
+	for (i = 0; i <= state->curframe; i++)
+		chain = bpf_diag_append_subprog_chain(env, chain,
+						      state->frame[i]->subprogno);
+
+	if (next_subprog >= 0)
+		chain = bpf_diag_append_subprog_chain(env, chain, next_subprog);
+
+	return chain;
+}
+
 static int set_callee_state(struct bpf_verifier_env *env,
 			    struct bpf_func_state *caller,
 			    struct bpf_func_state *callee, int insn_idx);
@@ -9658,8 +9757,17 @@ static int setup_func_entry(struct bpf_verifier_env *env, int subprog, int calls
 	int err;
 
 	if (state->curframe + 1 >= MAX_CALL_FRAMES) {
+		char *chain;
+
 		verbose(env, "the call stack of %d frames is too deep\n",
 			state->curframe + 2);
+		chain = bpf_diag_alloc_state_call_chain(env, state, subprog);
+		bpf_diag_report_limit(env, callsite, "bpf2bpf call frames",
+				      "Reduce the number of nested bpf2bpf calls on this path.",
+				      "Call chain %s would create %d verifier call frames, exceeding the %d frame limit",
+				      chain ?: "the current call chain",
+				      state->curframe + 2, MAX_CALL_FRAMES);
+		kfree(chain);
 		return -E2BIG;
 	}
 
@@ -18370,6 +18478,10 @@ static int do_check(struct bpf_verifier_env *env)
 			verbose(env,
 				"BPF program is too large. Processed %d insn\n",
 				env->insn_processed);
+			bpf_diag_report_limit(env, env->insn_idx,
+					      "processed instruction complexity",
+					      "Simplify control flow, reduce branching, or split the program into smaller pieces.",
+					      "The verifier explored more instructions than the complexity limit allows");
 			return -E2BIG;
 		}
 
-- 
2.53.0


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

* [PATCH bpf-next v2 16/17] bpf: Report Verifier Internal errors
  2026-06-19 20:59 [PATCH bpf-next v2 00/17] Redesign Verification Errors Kumar Kartikeya Dwivedi
                   ` (14 preceding siblings ...)
  2026-06-19 20:59 ` [PATCH bpf-next v2 15/17] bpf: Report Verifier Limit errors Kumar Kartikeya Dwivedi
@ 2026-06-19 20:59 ` Kumar Kartikeya Dwivedi
  2026-06-19 20:59 ` [PATCH bpf-next v2 17/17] bpf: Gate verifier diagnostics on log level Kumar Kartikeya Dwivedi
  16 siblings, 0 replies; 34+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-06-19 20:59 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team

Augment selected existing internal-error paths with Verifier Internal Error
reports. These reports avoid suggesting source-level fixes and instead identify
the verifier invariant that failed.

Cover non-overwritten BPF_PTR_POISON helper argument types and inconsistent
kfunc graph-node argument classification.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 kernel/bpf/diagnostics.c | 16 ++++++++++++++++
 kernel/bpf/diagnostics.h |  3 +++
 kernel/bpf/verifier.c    | 12 +++++++++++-
 3 files changed, 30 insertions(+), 1 deletion(-)

diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
index 18217f5f709a..d27bd314d194 100644
--- a/kernel/bpf/diagnostics.c
+++ b/kernel/bpf/diagnostics.c
@@ -1195,6 +1195,22 @@ void bpf_diag_report_limit(struct bpf_verifier_env *env, u32 insn_idx,
 	bpf_diag_report_suggestion(env, "%s", suggestion);
 }
 
+void bpf_diag_report_internal_error(struct bpf_verifier_env *env,
+				    u32 insn_idx, const char *problem,
+				    const char *reason)
+{
+	bpf_diag_report_header(env, BPF_DIAG_CATEGORY_VERIFIER_INTERNAL_ERROR,
+			       problem);
+	bpf_diag_report_reason(env, "The verifier hit an internal error: %s",
+			       reason);
+
+	bpf_diag_report_section(env, "At");
+	bpf_diag_report_source(env, insn_idx, "error", "%s", problem);
+
+	bpf_diag_report_suggestion(env,
+				   "Report this problem to bpf@vger.kernel.org with the full verifier log and program.");
+}
+
 void bpf_diag_report_invalid_deref(struct bpf_verifier_env *env, u32 insn_idx,
 				   int regno, const char *reg_name,
 				   const char *type_name,
diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
index 559c0169062c..97cdaad7b606 100644
--- a/kernel/bpf/diagnostics.h
+++ b/kernel/bpf/diagnostics.h
@@ -228,6 +228,9 @@ void bpf_diag_report_limit(struct bpf_verifier_env *env, u32 insn_idx,
 			   const char *limit, const char *suggestion,
 			   const char *reason_fmt, ...)
 		__printf(5, 6);
+void bpf_diag_report_internal_error(struct bpf_verifier_env *env,
+				    u32 insn_idx, const char *problem,
+				    const char *reason);
 void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx,
 			    bool cond_true);
 void bpf_diag_record_reg_mod(struct bpf_verifier_env *env, u32 insn_idx,
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index ec51e0c81053..5c60fe033271 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -8481,6 +8481,9 @@ static int check_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *re
 				verbose(env, "verifier internal error:");
 				verbose(env, "%s has non-overwritten BPF_PTR_POISON type\n",
 					reg_arg_name(env, argno));
+				bpf_diag_report_internal_error(env, env->insn_idx,
+							       "poisoned helper argument type",
+							       "helper type checking reached a non-overwritten BPF_PTR_POISON expected type.");
 				return -EACCES;
 			}
 
@@ -12560,12 +12563,19 @@ static bool check_kfunc_is_graph_node_api(struct bpf_verifier_env *env,
 	default:
 		verbose(env, "verifier internal error: unexpected graph node argument type %s\n",
 			btf_field_type_name(node_field_type));
+		bpf_diag_report_internal_error(env, env->insn_idx,
+					       "unexpected graph node argument type",
+					       "the kfunc graph-node checker saw an unsupported graph node field type.");
 		return false;
 	}
 
-	if (!ret)
+	if (!ret) {
 		verbose(env, "verifier internal error: %s node arg for unknown kfunc\n",
 			btf_field_type_name(node_field_type));
+		bpf_diag_report_internal_error(env, env->insn_idx,
+					       "graph node argument for unknown kfunc",
+					       "the kfunc graph-node checker could not map this graph node argument to the called kfunc.");
+	}
 	return ret;
 }
 
-- 
2.53.0


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

* [PATCH bpf-next v2 17/17] bpf: Gate verifier diagnostics on log level
  2026-06-19 20:59 [PATCH bpf-next v2 00/17] Redesign Verification Errors Kumar Kartikeya Dwivedi
                   ` (15 preceding siblings ...)
  2026-06-19 20:59 ` [PATCH bpf-next v2 16/17] bpf: Report Verifier Internal errors Kumar Kartikeya Dwivedi
@ 2026-06-19 20:59 ` Kumar Kartikeya Dwivedi
  16 siblings, 0 replies; 34+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-06-19 20:59 UTC (permalink / raw)
  To: bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team

Verifier diagnostics collect active-path history and render richer reports for
selected verifier failures. That work is useful only when the caller requests
normal verifier log output.

Do not enable diagnostic collection or report rendering for BPF_LOG_STATS-only
loads. Stats-only loads still need the verifier's summary counters, but not
the extra path history used by diagnostic reports.

Keep enablement in the verifier environment and requested log mode so async
callback verification uses the same diagnostic policy as the main verifier
pass.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 kernel/bpf/diagnostics.c |  6 ++++++
 kernel/bpf/verifier.c    | 24 +++++++++++++++---------
 2 files changed, 21 insertions(+), 9 deletions(-)

diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
index d27bd314d194..f10654dc6af8 100644
--- a/kernel/bpf/diagnostics.c
+++ b/kernel/bpf/diagnostics.c
@@ -1162,6 +1162,9 @@ void bpf_diag_report_limit(struct bpf_verifier_env *env, u32 insn_idx,
 	char *reason, *text;
 	va_list args;
 
+	if (!bpf_diag_enabled(env))
+		return;
+
 	bpf_diag_report_header(env, BPF_DIAG_CATEGORY_VERIFIER_LIMIT,
 			       "limit exceeded");
 	bpf_diag_report_section(env, "Reason");
@@ -2422,6 +2425,9 @@ void bpf_diag_print_history(struct bpf_verifier_env *env,
 	int start_idx;
 	u32 i;
 
+	if (!bpf_diag_enabled(env))
+		return;
+
 	bpf_diag_report_section(env, "Causal path");
 
 	if (!env->diag) {
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 5c60fe033271..cdba76eb35bc 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -5359,7 +5359,9 @@ static int check_max_stack_depth_subprog(struct bpf_verifier_env *env, int idx,
 	 * of caller's stack as shown on the example above.
 	 */
 	if (idx && subprog[idx].has_tail_call && depth >= 256) {
-		char *chain = bpf_diag_alloc_subprog_call_chain(env, dinfo, idx);
+		char *chain = bpf_diag_enabled(env) ?
+			      bpf_diag_alloc_subprog_call_chain(env, dinfo, idx) :
+			      NULL;
 
 		verbose(env,
 			"tail_calls are not allowed when call stack of previous frames is %d bytes. Too large\n",
@@ -5391,11 +5393,12 @@ static int check_max_stack_depth_subprog(struct bpf_verifier_env *env, int idx,
 		if (subprog_depth > env->max_stack_depth)
 			env->max_stack_depth = subprog_depth;
 		if (subprog_depth > MAX_BPF_STACK) {
-			char *chain;
+			char *chain = NULL;
 
 			verbose(env, "stack size of subprog %d is %d. Too large\n",
 				idx, subprog_depth);
-			chain = bpf_diag_alloc_subprog_call_chain(env, dinfo, idx);
+			if (bpf_diag_enabled(env))
+				chain = bpf_diag_alloc_subprog_call_chain(env, dinfo, idx);
 			bpf_diag_report_limit(env, subprog[idx].start,
 					      "subprogram stack depth",
 					      "Reduce stack usage in this subprogram, or move large data out of the BPF stack.",
@@ -5417,9 +5420,10 @@ static int check_max_stack_depth_subprog(struct bpf_verifier_env *env, int idx,
 			verbose(env, "combined stack size of %d calls is %d. Too large\n",
 				total, depth);
 			{
-				char *chain;
+				char *chain = NULL;
 
-				chain = bpf_diag_alloc_subprog_call_chain(env, dinfo, idx);
+				if (bpf_diag_enabled(env))
+					chain = bpf_diag_alloc_subprog_call_chain(env, dinfo, idx);
 				bpf_diag_report_limit(env, subprog[idx].start,
 						      "combined call stack depth",
 						      "Reduce stack usage or call depth along this call chain.",
@@ -5497,11 +5501,12 @@ static int check_max_stack_depth_subprog(struct bpf_verifier_env *env, int idx,
 
 		frame = bpf_subprog_is_global(env, idx) ? 0 : frame + 1;
 		if (frame >= MAX_CALL_FRAMES) {
-			char *chain;
+			char *chain = NULL;
 
 			verbose(env, "the call stack of %d frames is too deep !\n",
 				frame);
-			chain = bpf_diag_alloc_subprog_call_chain(env, dinfo, idx);
+			if (bpf_diag_enabled(env))
+				chain = bpf_diag_alloc_subprog_call_chain(env, dinfo, idx);
 			bpf_diag_report_limit(env, call_insn,
 					      "bpf2bpf call frames",
 					      "Reduce the number of nested bpf2bpf calls on this path.",
@@ -9760,11 +9765,12 @@ static int setup_func_entry(struct bpf_verifier_env *env, int subprog, int calls
 	int err;
 
 	if (state->curframe + 1 >= MAX_CALL_FRAMES) {
-		char *chain;
+		char *chain = NULL;
 
 		verbose(env, "the call stack of %d frames is too deep\n",
 			state->curframe + 2);
-		chain = bpf_diag_alloc_state_call_chain(env, state, subprog);
+		if (bpf_diag_enabled(env))
+			chain = bpf_diag_alloc_state_call_chain(env, state, subprog);
 		bpf_diag_report_limit(env, callsite, "bpf2bpf call frames",
 				      "Reduce the number of nested bpf2bpf calls on this path.",
 				      "Call chain %s would create %d verifier call frames, exceeding the %d frame limit",
-- 
2.53.0


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

* Re: [PATCH bpf-next v2 01/17] bpf: Add verifier diagnostics report helpers
  2026-06-19 20:59 ` [PATCH bpf-next v2 01/17] bpf: Add verifier diagnostics report helpers Kumar Kartikeya Dwivedi
@ 2026-06-19 21:09   ` sashiko-bot
  0 siblings, 0 replies; 34+ messages in thread
From: sashiko-bot @ 2026-06-19 21:09 UTC (permalink / raw)
  To: Kumar Kartikeya Dwivedi; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Code and commit message mismatch regarding scratch storage implementation.
- [Low] Unused static functions break the build under -Werror.
--

commit e4ecd576e6d1cc63d9f51109086546f1b4b2955b
Author: Kumar Kartikeya Dwivedi <memxor@gmail.com>

bpf: Add verifier diagnostics report helpers

This commit adds a small diagnostics renderer for verifier reports and wires
it into the BPF build. It provides helpers to emit a common text structure
including a failure header and reusable report sections.

> Wrap report prose at 100 columns so Reason and Suggestion text stays readable
> without changing source or instruction gutters. Keep reusable formatting
> scratch storage in the environment-owned diagnostics object.

[Severity: Medium]
The commit message states that formatting scratch storage is kept in the
environment-owned diagnostics object. However, looking at the patch,
bpf_diag_vprint_indented() uses kvasprintf() to dynamically allocate memory
instead of using an environment-owned scratch buffer.

It looks like struct bpf_diag_scratch is introduced in a later patch in this
series ("bpf: Add source and instruction diagnostic context"). Could the
commit message be updated to match the current patch's implementation?

> diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
> new file mode 100644
> index 0000000000000..a18fd5aa395d6
> --- /dev/null
> +++ b/kernel/bpf/diagnostics.c
[ ... ]
> +static void bpf_diag_report_section(struct bpf_verifier_env *env,
> +				    const char *title)
> +{
> +	if (!bpf_diag_enabled(env))
> +		return;
> +
> +	bpf_diag_log(env, "\n%s:\n", title);
> +}
> +
> +static void bpf_diag_report_reason(struct bpf_verifier_env *env,
> +				   const char *fmt, ...)
> +{
> +	va_list args;
> +
> +	if (!bpf_diag_enabled(env))
> +		return;
> +
> +	bpf_diag_report_section(env, "Reason");
> +
> +	va_start(args, fmt);
> +	bpf_diag_vprint_indented(env, fmt, args);
> +	va_end(args);
> +}
> +
> +static void bpf_diag_report_suggestion(struct bpf_verifier_env *env,
> +				       const char *fmt, ...)
> +{

[Severity: Low]
Are bpf_diag_report_reason(), bpf_diag_report_suggestion(), and
bpf_diag_report_section() used anywhere in this specific patch?

Since they are defined as static but not called by any exported functions in
diagnostics.c in this commit, does this trigger a unused-function compiler
warning that breaks the build when CONFIG_WERROR is enabled?

While they are heavily utilized starting in the subsequent patches (e.g.,
"bpf: Report Verifier Internal errors"), would it be better to introduce them
in the patch where they are first used to ensure every individual patch in the
series compiles and preserves bisectability?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260619205934.1312876-1-memxor@gmail.com?part=1

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

* Re: [PATCH bpf-next v2 10/17] bpf: Report Resource Lifetime reference leaks
  2026-06-19 20:59 ` [PATCH bpf-next v2 10/17] bpf: Report Resource Lifetime reference leaks Kumar Kartikeya Dwivedi
@ 2026-06-19 21:12   ` sashiko-bot
  2026-06-19 23:42   ` Alexei Starovoitov
  1 sibling, 0 replies; 34+ messages in thread
From: sashiko-bot @ 2026-06-19 21:12 UTC (permalink / raw)
  To: Kumar Kartikeya Dwivedi; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Diagnostic history for nested IRQ-disabled regions prints the wrong context.
--

commit 3353d537a1807034f6c2c245d23ed71ed9b90936
Author: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Subject: bpf: Report Resource Lifetime reference leaks

Augment selected Resource Lifetime Safety failures with structured diagnostics
while preserving the existing verifier messages. Report unreleased references
from check_reference_leak() using reference-scoped diagnostic history, and add
state reports for dynptr, iterator, lock, and IRQ-flag lifetime misuse.

> diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
> index 933540eb105bf..e9c58f84ec89e 100644
> --- a/kernel/bpf/diagnostics.c
> +++ b/kernel/bpf/diagnostics.c
[ ... ]
> @@ -1702,6 +1702,71 @@ void bpf_diag_report_mem_bounds(struct bpf_verifier_env *env, u32 insn_idx,
[ ... ]
> +void bpf_diag_report_irq_resource_state(struct bpf_verifier_env *env,
> +					u32 insn_idx, const char *problem,
> +					const char *reason,
> +					const char *suggestion,
> +					u32 depth)
> +{
> +	struct bpf_diag_history_opts opts = {
> +		.scope = BPF_DIAG_HISTORY_SCOPE_CONTEXT,
> +		.ctx_kind = BPF_DIAG_CONTEXT_IRQ,
> +		.ctx_depth = depth,
> +	};
> +
> +	bpf_diag_report_header(env, BPF_DIAG_CATEGORY_RESOURCE_LIFETIME_SAFETY,
> +			       problem);
> +	bpf_diag_report_reason(env, "%s", reason);
> +
> +	bpf_diag_report_section(env, "At");
> +	bpf_diag_report_source(env, insn_idx, "error", "%s", problem);
> +
> +	if (depth)
> +		bpf_diag_print_history(env, &opts);

[Severity: Medium]
Will this diagnostic history print the correct context for nested
IRQ-disabled regions?

In acquire_irq_state(), the depth parameter passed to bpf_diag_record_context()
is hardcoded to 1 regardless of whether bpf_local_irq_save() calls are nested:

kernel/bpf/verifier.c:acquire_irq_state() {
	...
	state->active_irq_id = s->id;
	bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_IRQ, true, 1);
	return s->id;
}

Because BPF allows nested IRQ-disabled regions, a sequence of nested saves
will all emit enter events with depth 1. When an inner region is properly
restored, its leave event is also recorded as depth 1.

If a diagnostic report is triggered later while still in the outer IRQ region,
bpf_diag_history_context_start_idx() searches backwards, skips the leave event
(as it only stops on leave if depth is 0), and incorrectly matches the enter
event of the already closed inner region.

Does the verifier need to track the actual nesting level instead of hardcoding 1
so that bpf_diag_history_context_start_idx() finds the currently active outer
region?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260619205934.1312876-1-memxor@gmail.com?part=10

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

* Re: [PATCH bpf-next v2 07/17] bpf: Track verifier context diagnostic events
  2026-06-19 20:59 ` [PATCH bpf-next v2 07/17] bpf: Track verifier context " Kumar Kartikeya Dwivedi
@ 2026-06-19 21:13   ` sashiko-bot
  2026-06-19 21:19     ` Kumar Kartikeya Dwivedi
  2026-06-19 21:46   ` bot+bpf-ci
  1 sibling, 1 reply; 34+ messages in thread
From: sashiko-bot @ 2026-06-19 21:13 UTC (permalink / raw)
  To: Kumar Kartikeya Dwivedi; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Hardcoded depth for IRQ contexts breaks nested diagnostic history anchoring.
--

commit 86418a115dc1ef24cac1a623674bfd700c69c32e
Author: Kumar Kartikeya Dwivedi <memxor@gmail.com>

bpf: Track verifier context diagnostic events

Record verifier context transitions in the diagnostic history to anchor
causal paths for lock, IRQ, RCU, and preempt regions. The depth of the
context is used to ensure nested regions correctly anchor at the outermost
active region.

> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 93941deb2cd8c..e584dec04b34d 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
[ ... ]
> @@ -1473,6 +1475,7 @@ static int acquire_irq_state(struct bpf_verifier_env *env, int insn_idx)
>  	s->id = ++env->id_gen;
>  
>  	state->active_irq_id = s->id;
> +	bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_IRQ, true, 1);

[Severity: Medium]
The commit message mentions using context depth to anchor nested regions at
the outer active region. Since BPF allows nested IRQ disables (for example,
via multiple bpf_local_irq_save calls), does hardcoding the depth to 1 here
break that behavior?

If a program nests IRQ contexts, multiple enter events will be recorded with
a depth of 1. When bpf_diag_history_context_start_idx() searches backwards
for an event with a depth of 1, won't it stop at the innermost IRQ enter
event rather than the outermost one?

>  	return s->id;
>  }

[ ... ]
> @@ -1552,6 +1561,9 @@ static int release_irq_state(struct bpf_verifier_state *state, int id)
>  		if (state->refs[i].id == id) {
>  			release_reference_state(state, i);
>  			state->active_irq_id = prev_id;
> +			bpf_diag_record_context(env, env->insn_idx,
> +						BPF_DIAG_CONTEXT_IRQ, false,
> +						state->active_irq_id ? 1 : 0);

[Severity: Medium]
Similarly, won't this hardcode the exit depth to 1 if there are still outer
nested IRQ contexts active? Should this calculate the actual number of
nested IRQ contexts instead?

>  			return 0;
>  		} else {
>  			prev_id = state->refs[i].id;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260619205934.1312876-1-memxor@gmail.com?part=7

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

* Re: [PATCH bpf-next v2 05/17] bpf: Track verifier register diagnostic events
  2026-06-19 20:59 ` [PATCH bpf-next v2 05/17] bpf: Track verifier register diagnostic events Kumar Kartikeya Dwivedi
@ 2026-06-19 21:18   ` sashiko-bot
  2026-06-19 23:35   ` Alexei Starovoitov
  1 sibling, 0 replies; 34+ messages in thread
From: sashiko-bot @ 2026-06-19 21:18 UTC (permalink / raw)
  To: Kumar Kartikeya Dwivedi; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Medium] Stack read origin checking evaluates the source register state after it has already been overwritten, failing for operations where source and destination are the same register.
- [Medium] The backwards lineage tracker improperly aliases helper returns, kfunc returns, and memory loads as ALU operations, leading to corrupted diagnostic histories.
- [Medium] Unrelated register modifications are printed while tracking a value through a spilled stack slot.
- [Medium] Silent invalidation of the primary first slot of a dynptr during reference release.
- [Medium] Helper and kfunc calls silently invalidate caller-saved registers (R1-R5) without recording diagnostic events.
--

commit fcdc417a6c906cddea98626436d412d03ec67211
Author: Kumar Kartikeya Dwivedi <memxor@gmail.com>

bpf: Track verifier register diagnostic events

Record material register and outgoing stack argument changes so diagnostics
can explain how a value reached its current type, bounds, or unreadable
state. Store old and new register types, scalar ranges, tnum value and mask,
map and BTF type identity in the diagnostic event stream.

> diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
> index fa5a25b314a0a..f51b2860c11dc 100644
> --- a/kernel/bpf/diagnostics.c
> +++ b/kernel/bpf/diagnostics.c
[ ... ]
> +static bool bpf_diag_reg_event_keeps_lineage(const struct bpf_diag_history_event *event)
> +{
> +	if (event->reg.reason != BPF_DIAG_REG_MOD_WRITE)
> +		return false;
> +
> +	switch (event->reg.opcode) {
> +	case BPF_ADD:
> +	case BPF_SUB:
> +	case BPF_MUL:
> +	case BPF_OR:
> +	case BPF_AND:
> +	case BPF_LSH:
> +	case BPF_RSH:

[Severity: Medium]
Is it possible for memory loads or helper returns to inadvertently match these
ALU opcodes? For example, check_load_mem() passes BPF_OP(insn->code), which
strips the class and could result in 0x60 (BPF_LSH) or 0x70 (BPF_RSH). Also,
helper or kfunc returns pass 0, matching BPF_ADD. Could this cause independent
assignments to be incorrectly treated as lineage-preserving operations?

[ ... ]
> +static bool
> +bpf_diag_history_event_visible(const struct bpf_diag_history_event *event,
> +			       u32 idx,
> +			       const struct bpf_diag_history_filter *filter)
> +{
> +	const struct bpf_diag_history_opts *opts = filter->opts;
> +
> +	if (!opts || opts->scope == BPF_DIAG_HISTORY_SCOPE_ALL)
> +		return true;
> +
> +	switch (event->kind) {
> +	case BPF_DIAG_HISTORY_BRANCH:
> +		return true;
> +	case BPF_DIAG_HISTORY_REG_MOD:
> +		return opts->scope == BPF_DIAG_HISTORY_SCOPE_REG &&
> +		       event->reg.dst_reg == opts->regno &&
> +		       event->reg.frameno == opts->frameno;

[Severity: Medium]
Does this miss checking if the event index is bounded by stack_until_idx?
If we are tracking a value through a spilled stack slot, wouldn't this
unconditionally print any unrelated modifications to the register that
occurred between the stack spill and the stack fill?

> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index ca4bba1634186..fedabb6bb515f 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
[ ... ]
> @@ -6385,9 +6481,32 @@ static int check_load_mem(struct bpf_verifier_env *env, struct bpf_insn *insn,
>  	err = check_mem_access(env, env->insn_idx, regs + insn->src_reg, argno_from_reg(insn->src_reg), insn->off,
>  			       BPF_SIZE(insn->code), BPF_READ, insn->dst_reg,
>  			       strict_alignment_once, is_ldsx);
> +	size = bpf_size_to_bytes(BPF_SIZE(insn->code));
> +	stack_origin = !err &&
> +		       bpf_diag_stack_read_origin(env, regs + insn->src_reg,
> +						  insn->off, size,
> +						  &stack_frameno, &stack_spi);

[Severity: Medium]
Will this correctly track stack reads if the source and destination registers
are the same (e.g., LDX r1, [r1+0])? The call to check_mem_access() updates
regs[insn->dst_reg] with the loaded value's type. If src_reg and dst_reg are
the same, evaluating the source register afterwards in
bpf_diag_stack_read_origin() would see the new state instead of PTR_TO_STACK,
potentially missing the stack fill event.

[ ... ]
> @@ -8986,6 +9159,11 @@ static int release_reference(struct bpf_verifier_env *env, int id)
[ ... ]
>  			if (!stack || stack->slot_type[BPF_REG_SIZE - 1] == STACK_SPILL)
>  				mark_reg_invalid(env, reg);
>  			else if (stack->slot_type[BPF_REG_SIZE - 1] == STACK_DYNPTR)
>  				invalidate_dynptr(env, stack);
> +			if (regno >= 0)
> +				bpf_diag_record_reg_invalidate(env,

[Severity: Medium]
When a dynptr is invalidated here, does the first slot silently miss its
diagnostic event? The loop processes slots upward. For a dynptr, the second
slot is encountered first, and invalidate_dynptr() scrubs both slots. When
the iteration reaches the primary first slot, its state has already been
overwritten, so the ID check might fail and it gets skipped, leaving
users without a REF_RELEASE event for the primary dynptr pointer.

[ ... ]
> @@ -10603,6 +10829,10 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
>  	if (err)
>  		return err;
>  
> +	bpf_diag_record_reg_mod(env, insn_idx, env->cur_state->curframe,
> +				BPF_REG_0, false, 0, 0, &old_r0,
> +				&regs[BPF_REG_0]);
> +
>  	err = check_map_func_compatibility(env, meta.map.ptr, func_id);

[Severity: Medium]
What happens to the caller-saved registers (R1-R5) that were scrubbed to
NOT_INIT during this helper call? It looks like we only record a diagnostic
event for the return value (R0). If a program later tries to read one of the
scrubbed registers, will the backwards trace miss this invalidation point and
show an unrelated history?

[ ... ]
> @@ -13282,6 +13514,10 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
>  			return err;
>  	}
>  
> +	bpf_diag_record_reg_mod(env, insn_idx, env->cur_state->curframe,
> +				BPF_REG_0, false, 0, 0, &old_r0,
> +				&regs[BPF_REG_0]);
> +
>  	if (meta.func_id == special_kfunc_list[KF_bpf_session_cookie])

[Severity: Medium]
Similar to the helper call above, does this miss recording diagnostic events
for the caller-saved registers (R1-R5) that are invalidated by the kfunc?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260619205934.1312876-1-memxor@gmail.com?part=5

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

* Re: [PATCH bpf-next v2 12/17] bpf: Report Execution Context Safety errors
  2026-06-19 20:59 ` [PATCH bpf-next v2 12/17] bpf: Report Execution Context Safety errors Kumar Kartikeya Dwivedi
@ 2026-06-19 21:19   ` sashiko-bot
  2026-06-19 23:44   ` Alexei Starovoitov
  1 sibling, 0 replies; 34+ messages in thread
From: sashiko-bot @ 2026-06-19 21:19 UTC (permalink / raw)
  To: Kumar Kartikeya Dwivedi; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [Critical] Integer overflow and out-of-bounds write in `bpf_bprintf_prepare` due to `snprintf` return value mishandling.
--

commit 9da2a659fbd6ab09232d9dfa84f7ad227b09b2a1
Author: Kumar Kartikeya Dwivedi <memxor@gmail.com>

bpf: Report Execution Context Safety errors

This commit augments the BPF verifier to report Execution Context Safety
errors. It provides source context, path history, and suggestions for
sleepability and critical-section failures.

While reviewing the BPF codebase, I noticed a critical pre-existing issue in
kernel/bpf/helpers.c within bpf_bprintf_prepare().

> --- a/kernel/bpf/helpers.c
> +++ b/kernel/bpf/helpers.c
> @@ -818, ... @@ int bpf_bprintf_prepare(const char *fmt, u32 fmt_size, const u64 *raw_args,
>  			u32 num_args, struct bpf_bprintf_data *data)
>  {

[ ... ]

>  			if (fmt[i + 1] == 'B') {
>  				if (tmp_buf)  {
>  					err = snprintf(tmp_buf,
>  						       (tmp_buf_end - tmp_buf),
>  						       "%pB",
>  						       (void *)(long)raw_args[num_spec]);
>  					tmp_buf += (err + 1);

[Severity: Critical]
This is a pre-existing issue, but does this handle the snprintf return value
safely?

The snprintf function returns the number of characters that would have been
written if the buffer was large enough, not the actual number of characters
written. If the output is truncated because the remaining buffer space is
small, adding the return value to tmp_buf could advance the pointer past
tmp_buf_end.

[ ... ]

>  			/* hack: bstr_printf expects IP addresses to be
>  			 * pre-formatted as strings, ironically, the easiest way
>  			 * to do that is to call snprintf.
>  			 */
>  			ip_spec[2] = fmt[i - 1];
>  			ip_spec[3] = fmt[i];
>  			err = snprintf(tmp_buf, tmp_buf_end - tmp_buf,
>  				       ip_spec, &cur_ip);
>  
>  			tmp_buf += err + 1;

[Severity: Critical]
This is a pre-existing issue, but the same snprintf return value behavior
applies here. Could this also advance tmp_buf past tmp_buf_end if the output
is truncated?

[ ... ]

>  		} else if (fmt[i] == 's') {
>  			fmt_ptype = fmt[i];
>  fmt_str:
>  			if (fmt[i + 1] != 0 &&
>  			    !isspace(fmt[i + 1]) &&
>  			    !ispunct(fmt[i + 1])) {
>  				err = -EINVAL;
>  				goto out;
>  			}
>  
>  			if (!tmp_buf)
>  				goto nocopy_fmt;
>  
>  			if (tmp_buf_end == tmp_buf) {
>  				err = -ENOSPC;
>  				goto out;
>  			}
>  
>  			unsafe_ptr = (char *)(long)raw_args[num_spec];
>  			err = bpf_trace_copy_string(tmp_buf, unsafe_ptr,
>  						    fmt_ptype,
>  						    tmp_buf_end - tmp_buf);

[Severity: Critical]
This is a pre-existing issue, but can this bypass the out-of-space check?

If tmp_buf was advanced past tmp_buf_end by a previous truncated snprintf
call, the exact equality check (tmp_buf_end == tmp_buf) will evaluate to
false.

This would cause the size parameter (tmp_buf_end - tmp_buf) passed to
bpf_trace_copy_string to be negative. Since the size parameter is typically
treated as an unsigned value, this could result in a massive out-of-bounds
write. Could we use a greater-than-or-equal check or track the remaining
length more safely?

>  			if (err < 0) {
>  				tmp_buf[0] = '\0';
>  				err = 1;
>  			}
>  
>  			tmp_buf += err;
>  			num_spec++;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260619205934.1312876-1-memxor@gmail.com?part=12

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

* Re: [PATCH bpf-next v2 07/17] bpf: Track verifier context diagnostic events
  2026-06-19 21:13   ` sashiko-bot
@ 2026-06-19 21:19     ` Kumar Kartikeya Dwivedi
  0 siblings, 0 replies; 34+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-06-19 21:19 UTC (permalink / raw)
  To: sashiko-reviews, Kumar Kartikeya Dwivedi; +Cc: bpf

On Fri Jun 19, 2026 at 11:13 PM CEST, sashiko-bot wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] Hardcoded depth for IRQ contexts breaks nested diagnostic history anchoring.
> --
>
> commit 86418a115dc1ef24cac1a623674bfd700c69c32e
> Author: Kumar Kartikeya Dwivedi <memxor@gmail.com>
>
> bpf: Track verifier context diagnostic events
>
> Record verifier context transitions in the diagnostic history to anchor
> causal paths for lock, IRQ, RCU, and preempt regions. The depth of the
> context is used to ensure nested regions correctly anchor at the outermost
> active region.
>
>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>> index 93941deb2cd8c..e584dec04b34d 100644
>> --- a/kernel/bpf/verifier.c
>> +++ b/kernel/bpf/verifier.c
> [ ... ]
>> @@ -1473,6 +1475,7 @@ static int acquire_irq_state(struct bpf_verifier_env *env, int insn_idx)
>>  	s->id = ++env->id_gen;
>>
>>  	state->active_irq_id = s->id;
>> +	bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_IRQ, true, 1);
>
> [Severity: Medium]
> The commit message mentions using context depth to anchor nested regions at
> the outer active region. Since BPF allows nested IRQ disables (for example,
> via multiple bpf_local_irq_save calls), does hardcoding the depth to 1 here
> break that behavior?
>
> If a program nests IRQ contexts, multiple enter events will be recorded with
> a depth of 1. When bpf_diag_history_context_start_idx() searches backwards
> for an event with a depth of 1, won't it stop at the innermost IRQ enter
> event rather than the outermost one?
>

Good catch in both replies; will fix in v3.

>>  	return s->id;
>>  }
>
> [ ... ]
>> @@ -1552,6 +1561,9 @@ static int release_irq_state(struct bpf_verifier_state *state, int id)
>>  		if (state->refs[i].id == id) {
>>  			release_reference_state(state, i);
>>  			state->active_irq_id = prev_id;
>> +			bpf_diag_record_context(env, env->insn_idx,
>> +						BPF_DIAG_CONTEXT_IRQ, false,
>> +						state->active_irq_id ? 1 : 0);
>
> [Severity: Medium]
> Similarly, won't this hardcode the exit depth to 1 if there are still outer
> nested IRQ contexts active? Should this calculate the actual number of
> nested IRQ contexts instead?
>
>>  			return 0;
>>  		} else {
>>  			prev_id = state->refs[i].id;


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

* Re: [PATCH bpf-next v2 03/17] bpf: Add verifier diagnostic event log
  2026-06-19 20:59 ` [PATCH bpf-next v2 03/17] bpf: Add verifier diagnostic event log Kumar Kartikeya Dwivedi
@ 2026-06-19 21:46   ` bot+bpf-ci
  0 siblings, 0 replies; 34+ messages in thread
From: bot+bpf-ci @ 2026-06-19 21:46 UTC (permalink / raw)
  To: memxor, bpf
  Cc: ast, andrii, daniel, eddyz87, emil, kkd, kernel-team, ast, andrii,
	daniel, martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 2355 bytes --]

> diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
> index 534a7bc58781..fa5a25b314a0 100644
> --- a/kernel/bpf/diagnostics.c
> +++ b/kernel/bpf/diagnostics.c

[ ... ]

> @@ -704,3 +782,53 @@ void bpf_diag_report_source(struct bpf_verifier_env *env, u32 insn_idx,
>  out_free_msg:
>  	kfree(msg);
>  }
> +
> +void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx,
> +			    bool cond_true)
> +{
> +	struct bpf_diag_history_event event = {
> +		.insn_idx = insn_idx,
> +		.kind = BPF_DIAG_HISTORY_BRANCH,
> +		.branch.cond_true = cond_true,
> +	};
> +
> +	bpf_diag_append_history(env, &event);
> +}

A review comment on an earlier posting noted that this recorded branch
event can be recovered from the regular jump history, and asked whether a
dedicated branch event is needed at all.

This version still adds the dedicated bpf_diag_record_branch() event and
the branch renderer in bpf_diag_print_history() below.

Could the existing jump history be reused here instead of recording a
separate branch event, or is there something the jump history does not
capture that this needs?

> +
> +void bpf_diag_print_history(struct bpf_verifier_env *env)
> +{
> +	const struct bpf_diag_history_event *event;
> +	const struct bpf_diag_log *log;
> +	bool printed = false;
> +	u32 i;
> +
> +	bpf_diag_report_section(env, "Causal path");
> +
> +	if (!env->diag) {
> +		bpf_diag_write(env, "  no recorded diagnostic events on this path\n");
> +		return;
> +	}
> +	log = &env->diag->log;
> +
> +	for (i = 0; i < log->cnt; i++) {
> +		event = bpf_diag_history_event(log, i);
> +
> +		switch (event->kind) {
> +		case BPF_DIAG_HISTORY_BRANCH:
> +			bpf_diag_report_source(env, event->insn_idx, "branch",
> +					       "explored as %s, goto %s",
> +					       event->branch.cond_true ? "true" :
> +					       "false",
> +					       event->branch.cond_true ? "followed" :
> +					       "not followed");
> +			printed = true;
> +			break;
> +		default:
> +			break;
> +		}
> +	}
> +
> +	if (!printed)
> +		bpf_diag_write(env,
> +			       "  no retained diagnostic events on this path\n");
> +}


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/27848712882

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

* Re: [PATCH bpf-next v2 02/17] bpf: Add source and instruction diagnostic context
  2026-06-19 20:59 ` [PATCH bpf-next v2 02/17] bpf: Add source and instruction diagnostic context Kumar Kartikeya Dwivedi
@ 2026-06-19 21:46   ` bot+bpf-ci
  0 siblings, 0 replies; 34+ messages in thread
From: bot+bpf-ci @ 2026-06-19 21:46 UTC (permalink / raw)
  To: memxor, bpf
  Cc: ast, andrii, daniel, eddyz87, emil, kkd, kernel-team, ast, andrii,
	daniel, martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 2286 bytes --]

> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index 15ae7c43f594..6f08bf383c11 100644
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c
> @@ -8255,6 +8255,17 @@ int btf_type_snprintf_show(const struct btf *btf, u32 type_id, void *obj,
>  	return ssnprintf.len;
>  }
>
> +int btf_type_snprintf_show_name(const struct btf *btf, u32 type_id,
> +				char *buf, int len)
> +{
> +	struct btf_show show = {
> +		.btf = btf,
> +		.state.type_id = type_id,
> +	};
> +
> +	return snprintf(buf, len, "%s", btf_show_name(&show));
> +}

btf_type_snprintf_show_name() is added here and declared globally in
include/linux/btf.h, but I cannot find any caller in the tree, and a
forward search over the rest of the series did not turn up one either.

Is btf_type_snprintf_show_name() consumed somewhere later in the series,
or is it added ahead of a caller that did not land?

> diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
> index a18fd5aa395d..534a7bc58781 100644
> --- a/kernel/bpf/diagnostics.c
> +++ b/kernel/bpf/diagnostics.c

[ ... ]

> @@ -38,7 +177,6 @@ static void bpf_diag_log(struct bpf_verifier_env *env, const char *fmt, ...)
>  	bpf_verifier_vlog(&env->log, fmt, args);
>  	va_end(args);
>  }
> -
>  static void bpf_diag_print_wrapped_prefixed(struct bpf_verifier_env *env,

This isn't a bug, but this drops the blank line between the end of
bpf_diag_log() and the following function, so the two now abut with no
separator unlike the other function pairs in the file.  Was that
intentional, or could the blank line be kept to match the surrounding
functions?

> @@ -103,6 +241,287 @@ static void bpf_diag_vprint_indented(struct bpf_verifier_env *env,
>  	kfree(buf);
>  }
>
> +static int bpf_diag_line_width(unsigned int line)
> +{
> +	int width = 1;
> +
> +	while (line >= 10) {
> +		line /= 10;
> +		width++;
> +	}
> +
> +	return width;
> +}

This isn't a bug, but bpf_diag_line_width() is a hand-rolled base-10
digit counter.  There is already an int_log.h:intlog10() that does this;
could that be used here instead?


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/27848712882

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

* Re: [PATCH bpf-next v2 09/17] bpf: Report Memory Safety bounds errors
  2026-06-19 20:59 ` [PATCH bpf-next v2 09/17] bpf: Report Memory Safety bounds errors Kumar Kartikeya Dwivedi
@ 2026-06-19 21:46   ` bot+bpf-ci
  2026-06-19 23:40   ` Alexei Starovoitov
  1 sibling, 0 replies; 34+ messages in thread
From: bot+bpf-ci @ 2026-06-19 21:46 UTC (permalink / raw)
  To: memxor, bpf
  Cc: ast, andrii, daniel, eddyz87, emil, kkd, kernel-team, ast, andrii,
	daniel, martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 3463 bytes --]

> diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
> index d6e858cd39f2..fea7731d431c 100644
> --- a/kernel/bpf/diagnostics.h
> +++ b/kernel/bpf/diagnostics.h
> @@ -124,6 +124,13 @@ enum bpf_diag_invalid_deref_kind {
>  	BPF_DIAG_DEREF_INVALID_PTR,
>  };
>
> +enum bpf_diag_mem_bounds_kind {
> +	BPF_DIAG_MEM_NEGATIVE_MIN,
> +	BPF_DIAG_MEM_MIN_OUT_OF_RANGE,
> +	BPF_DIAG_MEM_UNBOUNDED,
> +	BPF_DIAG_MEM_MAX_OUT_OF_RANGE,
> +};
> +

This isn't a bug, but Eduard Zingerman suggested in his review of v1
that the enum might not be necessary. His comment was: "Nit: here as
well, I'm not sure the 'enum' pays for itself." This is a minor
stylistic concern, but it was not addressed in v2.

> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 2c5f24528071..af04709c5178 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c

[ ... ]

> @@ -3896,9 +3915,12 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
>  					if (type == STACK_POISON) {
>  						verbose(env, "reading from stack off %d+%d size %d, slot poisoned by dead code elimination\n",
>  							off, i, size);
> +						return -EFAULT;
>  					} else {
>  						verbose(env, "invalid read from stack off %d+%d size %d\n",
>  							off, i, size);
> +						bpf_diag_report_stack_read_uninit(env, off, i,
> +										  size);
>  					}
>  					return -EACCES;
>  				}

[ ... ]

> @@ -3951,9 +3973,12 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
>  			if (type == STACK_POISON) {
>  				verbose(env, "reading from stack off %d+%d size %d, slot poisoned by dead code elimination\n",
>  					off, i, size);
> +				return -EFAULT;
>  			} else {
>  				verbose(env, "invalid read from stack off %d+%d size %d\n",
>  					off, i, size);
> +				bpf_diag_report_stack_read_uninit(env, off, i,
> +								  size);
>  			}
>  			return -EACCES;
>  		}

The STACK_POISON case now returns -EFAULT instead of falling through to
the existing -EACCES. This appears to be an undocumented behavioral
change unrelated to the diagnostic feature.

The commit message states this patch augments failures "while preserving
the existing terse verifier messages for compatibility," and no
diagnostic report is emitted in the STACK_POISON branch (the new
bpf_diag_report_stack_read_uninit() call is added only in the else
branch).

This return code change has a concrete consequence. In do_check(),
error_recoverable_with_nospec(err) && state->speculative decides whether
a failed instruction on a speculative path can be salvaged by inserting
a nospec barrier.

error_recoverable_with_nospec() returns true for -EPERM/-EACCES/-EINVAL
but not for -EFAULT. So a poisoned-slot read that occurs on a
speculative path used to be recoverable (return -EACCES, insert nospec,
accept) and is now treated as fatal (return -EFAULT, reject).

A program that previously verified could now be rejected.

This also makes check_stack_read_fixed_off inconsistent with
check_stack_range_initialized(), which handles the identical "slot
poisoned by dead code elimination" condition and still returns -EACCES.

Should the STACK_POISON case continue returning -EACCES to preserve the
existing error recovery behavior?


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/27848712882

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

* Re: [PATCH bpf-next v2 07/17] bpf: Track verifier context diagnostic events
  2026-06-19 20:59 ` [PATCH bpf-next v2 07/17] bpf: Track verifier context " Kumar Kartikeya Dwivedi
  2026-06-19 21:13   ` sashiko-bot
@ 2026-06-19 21:46   ` bot+bpf-ci
  1 sibling, 0 replies; 34+ messages in thread
From: bot+bpf-ci @ 2026-06-19 21:46 UTC (permalink / raw)
  To: memxor, bpf
  Cc: ast, andrii, daniel, eddyz87, emil, kkd, kernel-team, ast, andrii,
	daniel, martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 1391 bytes --]

> diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
> index 58cc7c18cf98..d6a9a2315f54 100644
> --- a/kernel/bpf/diagnostics.c
> +++ b/kernel/bpf/diagnostics.c
> @@ -1041,6 +1041,53 @@ void bpf_diag_record_ref_release(struct bpf_verifier_env *env, u32 insn_idx,
> +void bpf_diag_record_context(struct bpf_verifier_env *env, u32 insn_idx,
> +			     enum bpf_diag_context_kind ctx_kind, bool enter,
> +			     u32 depth)
> +{
> +	/* Keep leave events so context rendering can stop at a depth-zero exit
> +	 * and show nested-region depth accurately for the active path.
> +	 */

This isn't a bug, but would it be worth moving the comment text onto the
line after /* to match the multi-line comment style used elsewhere in the
BPF tree?  The rest of diagnostics.c keeps its multi-line comments with the
opening /* on its own line.

> +	struct bpf_diag_history_event event = {
> +		.insn_idx = insn_idx,
> +		.kind = BPF_DIAG_HISTORY_CONTEXT,
> +		.ctx.kind = ctx_kind,
> +		.ctx.enter = enter,
> +		.ctx.depth = depth,
> +	};
> +
> +	if (ctx_kind == BPF_DIAG_CONTEXT_NONE)
> +		return;
> +
> +	bpf_diag_append_history(env, &event);
> +}


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/27848712882

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

* Re: [PATCH bpf-next v2 04/17] bpf: Prune verifier diagnostics on backtracking
  2026-06-19 20:59 ` [PATCH bpf-next v2 04/17] bpf: Prune verifier diagnostics on backtracking Kumar Kartikeya Dwivedi
@ 2026-06-19 21:46   ` bot+bpf-ci
  0 siblings, 0 replies; 34+ messages in thread
From: bot+bpf-ci @ 2026-06-19 21:46 UTC (permalink / raw)
  To: memxor, bpf
  Cc: ast, andrii, daniel, eddyz87, emil, kkd, kernel-team, ast, andrii,
	daniel, martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 3390 bytes --]

> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index e81fdb0e22ae..ca4bba163418 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -192,6 +192,9 @@ struct bpf_verifier_stack_elem {
>  	struct bpf_verifier_stack_elem *next;
>  	/* length of verifier log at the time this state was pushed on stack */
>  	u32 log_pos;
> +	u64 diag_log_pos;
> +	bool diag_branch_valid;
> +	bool diag_branch_cond_true;
>  };

[ ... ]

> @@ -1786,6 +1794,21 @@ static struct bpf_verifier_state *push_stack(struct bpf_verifier_env *env,
>  	return &elem->st;
>  }
>
> +static struct bpf_verifier_state *
> +push_stack_with_branch_diag(struct bpf_verifier_env *env, int insn_idx,
> +			    int prev_insn_idx, bool speculative,
> +			    bool cond_true)
> +{
> +	struct bpf_verifier_state *st;
> +
> +	st = push_stack(env, insn_idx, prev_insn_idx, speculative);
> +	if (!IS_ERR(st)) {
> +		env->head->diag_branch_valid = true;
> +		env->head->diag_branch_cond_true = cond_true;
> +	}
> +	return st;
> +}

[ ... ]

> @@ -16060,7 +16086,8 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
>  			return err;
>  	}
>
> -	other_branch = push_stack(env, *insn_idx + insn->off + 1, *insn_idx, false);
> +	other_branch = push_stack_with_branch_diag(env, *insn_idx + insn->off + 1,
> +						   *insn_idx, false, true);
>  	if (IS_ERR(other_branch))
>  		return PTR_ERR(other_branch);
>  	other_branch_regs = other_branch->frame[other_branch->curframe]->regs;
> @@ -16074,6 +16101,7 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
>  	other_branch_regs[insn->dst_reg] = env->true_reg1;
>  	if (BPF_SRC(insn->code) == BPF_X)
>  		other_branch_regs[insn->src_reg] = env->true_reg2;
> +	bpf_diag_record_branch(env, *insn_idx, false);

[ ... ]

> @@ -1720,6 +1723,10 @@ static int pop_stack(struct bpf_verifier_env *env, int *prev_insn_idx,
>  	}
>  	if (pop_log)
>  		bpf_vlog_reset(&env->log, head->log_pos);
> +	bpf_diag_event_log_reset(env, head->diag_log_pos);
> +	if (activate_diag_branch && head->diag_branch_valid)
> +		bpf_diag_record_branch(env, head->prev_insn_idx,
> +				       head->diag_branch_cond_true);

This isn't a bug, but could the changelog be more precise about how the
queued branch outcome is recorded?

The message says "append the queued outcome before push_stack() so the
saved diagnostic position includes it, then reset back to the active
path before continuing," but the code records the queued outcome later
when the state is popped.

In check_cond_jmp_op(), push_stack_with_branch_diag() captures the
diagnostic position and stores diag_branch_cond_true = true on the stack
element. The queued outcome is recorded in pop_stack() when
activate_diag_branch is true:

    if (activate_diag_branch && head->diag_branch_valid)
        bpf_diag_record_branch(env, head->prev_insn_idx,
                               head->diag_branch_cond_true);

The active path's false outcome is recorded after push_stack_with_branch_diag()
via bpf_diag_record_branch(env, *insn_idx, false).

Does the description match an earlier design, or could it be updated to
reflect the push-then-record-at-pop approach?


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/27848712882

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

* Re: [PATCH bpf-next v2 11/17] bpf: Report Call Type Safety argument errors
  2026-06-19 20:59 ` [PATCH bpf-next v2 11/17] bpf: Report Call Type Safety argument errors Kumar Kartikeya Dwivedi
@ 2026-06-19 21:47   ` bot+bpf-ci
  0 siblings, 0 replies; 34+ messages in thread
From: bot+bpf-ci @ 2026-06-19 21:47 UTC (permalink / raw)
  To: memxor, bpf
  Cc: ast, andrii, daniel, eddyz87, emil, kkd, kernel-team, ast, andrii,
	daniel, martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 2686 bytes --]

> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index db151e6b8949..fdbf92bffc17 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c

[ ... ]

> @@ -12587..-12662 @@ BTF_ID trusted/rcu, ctx, alloc obj

[ ... ]

> @@ -12587,13 +12735,35 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
>  	case KF_ARG_PTR_TO_BTF_ID:
>  		if (!is_trusted_reg(env, reg)) {
>  			if (!is_kfunc_rcu(meta)) {
> +				const char *expected_type;
> +
> +				expected_type = diag_btf_type_name(env, btf,
> +								   ref_id);
>  				verbose(env, "%s must be referenced or trusted\n",
>  					reg_arg_name(env, argno));

[ ... ]

>  		}
>  		if (!is_rcu_reg(reg)) {
> +			const char *expected_type;
> +
> +			expected_type = diag_btf_type_name(env, btf,
> +							   ref_id);
>  			verbose(env, "%s must be a rcu pointer\n",
>  				reg_arg_name(env, argno));

[ ... ]

> @@ -12831..-12946 @@ MEM/MEM_SIZE/refcounted/const str/irq

[ ... ]

> @@ -12831,8 +13015,18 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
>  			return -EINVAL;
>  		}
>  		ret = check_mem_reg(env, reg, argno, type_size);
> -		if (ret < 0)
> +		if (ret < 0) {
> +			const char *expected_type;
> +
> +			expected_type = diag_btf_type_name(env, btf,
> +							   ref_id);
>  			diag_call_arg_fmt(env, insn_idx, argno, func_name,

[ ... ]

> @@ -12880,8 +13091,16 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
>  		break;
>  	case KF_ARG_PTR_TO_REFCOUNTED_KPTR:
>  		if (!type_is_ptr_alloc_obj(reg->type)) {
> +			const char *expected_type;
> +
> +			expected_type = diag_btf_type_name(env, btf,
> +							   ref_id);
>  			verbose(env, "%s is neither owning or non-owning ref\n",
>  				reg_arg_name(env, argno));

This isn't a bug, but would it be worth using a consistent helper name
for the same computation?

In check_kfunc_args, the NULL-pointer and release blocks
(around lines 12650-12680) call the raw helper like this:

    expected_type = bpf_diag_format_btf_type_scratch(env, 1, btf, ref_id);

while the trusted/rcu, MEM, refcounted-kptr, and const-str blocks
(CHANGE-2/CHANGE-3) call a thin wrapper:

    expected_type = diag_btf_type_name(env, btf, ref_id);

Since diag_btf_type_name() is defined as exactly that raw call with
the same slot and parameters, could all these blocks use the wrapper
so the same expected-type lookup reads the same way?


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/27848712882

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

* Re: [PATCH bpf-next v2 05/17] bpf: Track verifier register diagnostic events
  2026-06-19 20:59 ` [PATCH bpf-next v2 05/17] bpf: Track verifier register diagnostic events Kumar Kartikeya Dwivedi
  2026-06-19 21:18   ` sashiko-bot
@ 2026-06-19 23:35   ` Alexei Starovoitov
  1 sibling, 0 replies; 34+ messages in thread
From: Alexei Starovoitov @ 2026-06-19 23:35 UTC (permalink / raw)
  To: Kumar Kartikeya Dwivedi, bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team

On Fri Jun 19, 2026 at 1:59 PM PDT, Kumar Kartikeya Dwivedi wrote:
> @@ -10179,6 +10403,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
>  	const struct bpf_func_proto *fn = NULL;
>  	enum bpf_return_type ret_type;
>  	enum bpf_type_flag ret_flag;
> +	struct bpf_reg_state old_r0;
>  	struct bpf_reg_state *regs;
>  	struct bpf_call_arg_meta meta;
>  	int insn_idx = *insn_idx_p;
> @@ -10253,6 +10478,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
>  		return err;
>  
>  	regs = cur_regs(env);
> +	old_r0 = regs[BPF_REG_0];

This adds extra 120 bytes to stack.
Please use scratch reg in env.

>  
>  	/* Mark slots with STACK_MISC in case of raw mode, stack offset
>  	 * is inferred from register state.
> @@ -10603,6 +10829,10 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
>  	if (err)
>  		return err;
>  
> +	bpf_diag_record_reg_mod(env, insn_idx, env->cur_state->curframe,
> +				BPF_REG_0, false, 0, 0, &old_r0,
> +				&regs[BPF_REG_0]);
> +
>  	err = check_map_func_compatibility(env, meta.map.ptr, func_id);
>  	if (err)
>  		return err;
> @@ -12918,6 +13148,7 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
>  	const struct btf_type *t, *ptr_type;
>  	struct bpf_kfunc_call_arg_meta meta;
>  	struct bpf_insn_aux_data *insn_aux;
> +	struct bpf_reg_state old_r0;

same thing. Let's avoid stack increase.

>  	int err, insn_idx = *insn_idx_p;
>  	const struct btf_param *args;
>  	u32 i, nargs, ptr_type_id;
> @@ -13114,6 +13345,7 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
>  		}
>  	}
>  
> +	old_r0 = regs[BPF_REG_0];
>  	for (i = 0; i < CALLER_SAVED_REGS; i++) {
>  		u32 regno = caller_saved[i];
>  
> @@ -13282,6 +13514,10 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
>  			return err;
>  	}
>  
> +	bpf_diag_record_reg_mod(env, insn_idx, env->cur_state->curframe,
> +				BPF_REG_0, false, 0, 0, &old_r0,
> +				&regs[BPF_REG_0]);
> +
>  	if (meta.func_id == special_kfunc_list[KF_bpf_session_cookie])
>  		env->prog->call_session_cookie = true;
>  
> @@ -14915,10 +15151,17 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
>  /* check validity of 32-bit and 64-bit arithmetic operations */
>  static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
>  {
> +	struct bpf_func_state *state = cur_func(env);
>  	struct bpf_reg_state *regs = cur_regs(env);
> +	struct bpf_reg_state old_dst = {};

and here

>  	u8 opcode = BPF_OP(insn->code);
> +	bool have_old_dst;
>  	int err;
>  
> +	have_old_dst = insn->dst_reg < MAX_BPF_REG;
> +	if (have_old_dst)
> +		old_dst = regs[insn->dst_reg];
> +


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

* Re: [PATCH bpf-next v2 09/17] bpf: Report Memory Safety bounds errors
  2026-06-19 20:59 ` [PATCH bpf-next v2 09/17] bpf: Report Memory Safety bounds errors Kumar Kartikeya Dwivedi
  2026-06-19 21:46   ` bot+bpf-ci
@ 2026-06-19 23:40   ` Alexei Starovoitov
  1 sibling, 0 replies; 34+ messages in thread
From: Alexei Starovoitov @ 2026-06-19 23:40 UTC (permalink / raw)
  To: Kumar Kartikeya Dwivedi, bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team

On Fri Jun 19, 2026 at 1:59 PM PDT, Kumar Kartikeya Dwivedi wrote:
> +
> +static void bpf_diag_format_access_offset(struct bpf_verifier_env *env,
> +					  char *buf, size_t size, int off,
> +					  const struct bpf_reg_state *reg)
> +{
> +	struct bpf_diag_scratch *scratch = bpf_diag_scratch(env);
> +	struct bpf_diag_reg_fmt *fmt;
> +	char *start;
> +
> +	if (tnum_is_const(reg->var_off)) {
> +		start = bpf_diag_scratch_buf(env, 2,
> +					     NULL);

unnecessary line wrap.

> +		if (!start) {
> +			scnprintf(buf, size, "constant");
> +			return;
> +		}
> +		bpf_diag_format_s64_sum(start, BPF_DIAG_SCRATCH_STR_LEN,
> +					(s64)reg->var_off.value, off);
> +		scnprintf(buf, size, "constant %s", start);
> +		return;
> +	}
> +
> +	if (tnum_is_unknown(reg->var_off) &&
> +	    bpf_diag_cnum64_unknown(reg->r64)) {

also

> +		scnprintf(buf, size, "unknown");
> +		return;
> +	}
> +
> +	fmt = &scratch->reg_fmt;
> +	memset(fmt, 0, sizeof(*fmt));
> +
> +	bpf_diag_format_scalar_range(fmt, fmt->range, sizeof(fmt->range),
> +				     reg->r64);

also doesn't need to wrap. Up to 100 chars is ok.

> +	if (off)
> +		scnprintf(buf, size,
> +			  "variable: known bits %#llx, unknown mask %#llx, plus fixed offset %d; %s",
> +			  (u64)reg->var_off.value, reg->var_off.mask, off,
> +			  fmt->range);
> +	else
> +		scnprintf(buf, size,
> +			  "variable: known bits %#llx, unknown mask %#llx; %s",
> +			  (u64)reg->var_off.value, reg->var_off.mask,
> +			  fmt->range);
> +}
> +
> +static u64 bpf_diag_mem_max_start(const struct bpf_reg_state *reg, int off)
> +{
> +	/* A negative fixed offset can clamp the maximum start to zero when
> +	 * the unsigned variable maximum is smaller than -off.
> +	 */
> +	if (off < 0 && reg_umax(reg) < (u64)-off)
> +		return 0;
> +	return reg_umax(reg) + off;
> +}
> +
> +void bpf_diag_report_mem_bounds(struct bpf_verifier_env *env, u32 insn_idx,
> +				int regno, const char *reg_name,
> +				const char *type_name,
> +				enum bpf_diag_mem_bounds_kind kind,
> +				int off, int size, u32 mem_size,
> +				const struct bpf_reg_state *reg)
> +{
> +	struct bpf_diag_history_opts opts = {
> +		.scope = BPF_DIAG_HISTORY_SCOPE_REG,
> +		.frameno = bpf_diag_current_frameno(env),
> +		.regno = regno,
> +	};
> +	char *offset_desc, *proof, *start;
> +	u64 max_start, max_end;
> +
> +	if (!bpf_diag_enabled(env))
> +		return;
> +
> +	offset_desc = bpf_diag_scratch_buf(env, 0, NULL);
> +	proof = bpf_diag_scratch_buf(env, 1, NULL);
> +	start = bpf_diag_scratch_buf(env, 2, NULL);
> +	if (!offset_desc || !proof || !start)
> +		return;

can they be NULL?

> +
> +	switch (kind) {
> +	case BPF_DIAG_MEM_NEGATIVE_MIN:
> +		bpf_diag_format_s64_sum(start, BPF_DIAG_SCRATCH_STR_LEN, reg_smin(reg),
> +					off);

no line wrap pls

> +		scnprintf(proof, BPF_DIAG_SCRATCH_STR_LEN,
> +			  "the smallest possible access starts at %s, below 0",
> +			  start);
> +		break;
> +	case BPF_DIAG_MEM_MIN_OUT_OF_RANGE:
> +		bpf_diag_format_s64_sum(start, BPF_DIAG_SCRATCH_STR_LEN, reg_smin(reg),
> +					off);

same

> +		scnprintf(proof, BPF_DIAG_SCRATCH_STR_LEN,
> +			  "the smallest possible access starts at %s, outside object_size %u",
> +			  start, mem_size);
> +		break;
> +	case BPF_DIAG_MEM_UNBOUNDED:
> +		scnprintf(proof, BPF_DIAG_SCRATCH_STR_LEN,
> +			  "%s has unsigned maximum %llu, which exceeds BPF_MAX_VAR_OFF %u",
> +			  reg_name, reg_umax(reg), BPF_MAX_VAR_OFF);
> +		break;
> +	case BPF_DIAG_MEM_MAX_OUT_OF_RANGE:
> +	default:
> +		max_start = bpf_diag_mem_max_start(reg, off);
> +		max_end = max_start + size;
> +		scnprintf(proof, BPF_DIAG_SCRATCH_STR_LEN,
> +			  "the largest possible access ends at %llu: start %llu + access_size %d, beyond object_size %u",
> +			  max_end, max_start, size, mem_size);
> +		break;
> +	}
> +
> +	bpf_diag_format_access_offset(env, offset_desc, BPF_DIAG_SCRATCH_STR_LEN,
> +				      off, reg);

same

> +
> +	bpf_diag_report_header(env, BPF_DIAG_CATEGORY_MEMORY_SAFETY,
> +			       "access outside bounds");
> +	bpf_diag_report_reason(env,
> +			       "The verifier cannot prove offset + access_size <= object_size. Here, %s. %s is %s; offset is %s; access_size is %d; object_size is %u.",
> +			       proof, reg_name, type_name, offset_desc, size,
> +			       mem_size);
> +
> +	bpf_diag_report_section(env, "At");
> +	bpf_diag_report_source(env, insn_idx, "error",
> +			       "access may be outside object bounds");

same

> +
> +	if (regno >= 0)
> +		bpf_diag_print_history(env, &opts);
> +
> +	bpf_diag_report_suggestion(env,
> +				   "Add or adjust a bounds check that proves offset + access_size stays within the object.");
> +}
> +
>  static void bpf_diag_format_var_offset(struct bpf_diag_reg_fmt *fmt,
>  				       char *buf, size_t size,
>  				       const struct bpf_diag_reg_snapshot *snapshot)
> diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
> index d6e858cd39f2..fea7731d431c 100644
> --- a/kernel/bpf/diagnostics.h
> +++ b/kernel/bpf/diagnostics.h
> @@ -124,6 +124,13 @@ enum bpf_diag_invalid_deref_kind {
>  	BPF_DIAG_DEREF_INVALID_PTR,
>  };
>  
> +enum bpf_diag_mem_bounds_kind {
> +	BPF_DIAG_MEM_NEGATIVE_MIN,
> +	BPF_DIAG_MEM_MIN_OUT_OF_RANGE,
> +	BPF_DIAG_MEM_UNBOUNDED,
> +	BPF_DIAG_MEM_MAX_OUT_OF_RANGE,
> +};
> +
>  struct bpf_diag_history_opts {
>  	enum bpf_diag_history_scope scope;
>  	u32 frameno;
> @@ -171,6 +178,15 @@ void bpf_diag_report_stack_arg_uninit(struct bpf_verifier_env *env,
>  				      u32 insn_idx, int nargs,
>  				      int stack_arg_slot,
>  				      const char *callee_name);
> +void bpf_diag_report_memory(struct bpf_verifier_env *env, u32 insn_idx,
> +			    const char *problem, const char *reason,
> +			    const char *suggestion);
> +void bpf_diag_report_mem_bounds(struct bpf_verifier_env *env, u32 insn_idx,
> +				int regno, const char *reg_name,
> +				const char *type_name,
> +				enum bpf_diag_mem_bounds_kind kind,
> +				int off, int size, u32 mem_size,
> +				const struct bpf_reg_state *reg);
>  void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx,
>  			    bool cond_true);
>  void bpf_diag_record_reg_mod(struct bpf_verifier_env *env, u32 insn_idx,
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 2c5f24528071..af04709c5178 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -3527,6 +3527,13 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
>  	    !bpf_is_spilled_scalar_reg(&state->stack[spi]) &&
>  	    size != BPF_REG_SIZE) {
>  		verbose(env, "attempt to corrupt spilled pointer on stack\n");
> +		bpf_diag_report_memory(env, insn_idx,
> +				       "stack spill corruption",
> +				       bpf_diag_scratch_printf(env,
> +							       0,
> +							       "This store writes %d bytes at stack offset %d into a stack slot that currently holds a spilled pointer. Partial writes to spilled pointers are rejected because they can corrupt pointer metadata and leak kernel pointers.",
> +							       size, off),
> +				       "Write the full 8-byte spilled pointer slot, or use a separate stack slot for scalar data before overwriting only part of it.");

but these are way too long.
introduce const chart *fmt = "This store.."
at the top of the function?

>  		return -EACCES;
>  	}
>  
> @@ -3811,6 +3818,18 @@ static void mark_reg_stack_read(struct bpf_verifier_env *env,
>  	}
>  }
>  
> +static void bpf_diag_report_stack_read_uninit(struct bpf_verifier_env *env,
> +					      int off, int i, int size)
> +{
> +	bpf_diag_report_memory(env, env->insn_idx,
> +			       "uninitialized stack read",
> +			       bpf_diag_scratch_printf(env,
> +						       0,
> +						       "This rejected read uses %d bytes at stack offset %d, but byte %d in that range is uninitialized on this path. Programs with sufficient privilege can be allowed to read uninitialized stack bytes, but this program is being rejected without that allowance.",
> +						       size, off, i),
> +			       "Initialize every byte in the stack range before reading it, adjust the offset and size so the read covers only initialized bytes, or load with the privilege needed for uninitialized stack reads.");

also way too long.

> +}
> +
>  /* Read the stack at 'off' and put the results into the register indicated by
>   * 'dst_regno'. It handles reg filling if the addressed stack slot is a
>   * spilled reg.
> @@ -3896,9 +3915,12 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
>  					if (type == STACK_POISON) {
>  						verbose(env, "reading from stack off %d+%d size %d, slot poisoned by dead code elimination\n",
>  							off, i, size);
> +						return -EFAULT;
>  					} else {
>  						verbose(env, "invalid read from stack off %d+%d size %d\n",
>  							off, i, size);
> +						bpf_diag_report_stack_read_uninit(env, off, i,
> +										  size);
>  					}
>  					return -EACCES;
>  				}
> @@ -3951,9 +3973,12 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
>  			if (type == STACK_POISON) {
>  				verbose(env, "reading from stack off %d+%d size %d, slot poisoned by dead code elimination\n",
>  					off, i, size);
> +				return -EFAULT;
>  			} else {
>  				verbose(env, "invalid read from stack off %d+%d size %d\n",
>  					off, i, size);
> +				bpf_diag_report_stack_read_uninit(env, off, i,
> +								  size);
>  			}
>  			return -EACCES;
>  		}
> @@ -4045,6 +4070,13 @@ static int check_stack_read(struct bpf_verifier_env *env,
>  		tnum_strn(tn_buf, sizeof(tn_buf), reg->var_off);
>  		verbose(env, "variable offset stack pointer cannot be passed into helper function; var_off=%s off=%d size=%d\n",
>  			tn_buf, off, size);
> +		bpf_diag_report_memory(env, env->insn_idx,
> +				       "variable stack access",
> +				       bpf_diag_scratch_printf(env,
> +							       0,
> +							       "The helper would access the stack through variable offset %s plus fixed offset %d and size %d. Helper stack memory arguments require a constant stack offset and a precise initialized range.",
> +							       tn_buf, off, size),
> +				       "Use a fixed stack offset for helper memory arguments, or copy the needed bytes into a fixed stack slot first.");

and here too. Pls find a way to reduce indent of bpf_diag_report_memory( ...bpf_diag_scratch_printf
I'd think bpf_diag_scratch_printf() can be done first and result passed into bpf_diag_report_memory()


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

* Re: [PATCH bpf-next v2 10/17] bpf: Report Resource Lifetime reference leaks
  2026-06-19 20:59 ` [PATCH bpf-next v2 10/17] bpf: Report Resource Lifetime reference leaks Kumar Kartikeya Dwivedi
  2026-06-19 21:12   ` sashiko-bot
@ 2026-06-19 23:42   ` Alexei Starovoitov
  1 sibling, 0 replies; 34+ messages in thread
From: Alexei Starovoitov @ 2026-06-19 23:42 UTC (permalink / raw)
  To: Kumar Kartikeya Dwivedi, bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team

On Fri Jun 19, 2026 at 1:59 PM PDT, Kumar Kartikeya Dwivedi wrote:
> +		bpf_diag_report_irq_resource_state(env, env->insn_idx,
> +						   "IRQ flag restore mismatch",
> +						   bpf_diag_scratch_printf(env,
> +									   0,
> +									   "This IRQ flag was saved by %s IRQ kfuncs, but the restore call belongs to the %s IRQ kfunc family. Save and restore operations must use the same family.",
> +									   flag_kfunc,
> +									   used_kfunc),
> +						   "Restore the flag with the matching IRQ restore kfunc for the save operation that created it.",
> +						   env->cur_state->active_irq_id ? 1 : 0);

same concern as in the previous patch.

>  		return -EINVAL;
>  	}
>  
> @@ -1137,6 +1150,11 @@ static int unmark_stack_slot_irq_flag(struct bpf_verifier_env *env, struct bpf_r
>  
>  		verbose(env, "cannot restore irq state out of order, expected id=%d acquired at insn_idx=%d\n",
>  			env->cur_state->active_irq_id, insn_idx);
> +		bpf_diag_report_irq_resource_state(env, env->insn_idx,
> +						   "IRQ flag restore out of order",
> +						   "IRQ-disabled regions must be restored in last-in, first-out order, but this restore does not match the currently active IRQ flag.",
> +						   "Restore nested IRQ flags in the reverse order they were saved.",
> +						   env->cur_state->active_irq_id ? 1 : 0);
>  		return err;
>  	}
>  
> @@ -7258,11 +7276,19 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state
>  			if (find_lock_state(env->cur_state, REF_TYPE_LOCK, 0, NULL)) {
>  				verbose(env,
>  					"Locking two bpf_spin_locks are not allowed\n");
> +				bpf_diag_report_resource_state(env, env->insn_idx,
> +							       "nested spin lock",
> +							       "This path already holds a bpf_spin_lock. The verifier allows only one regular BPF spin lock at a time.",
> +							       "Unlock the current bpf_spin_lock before taking another one.");
>  				return -EINVAL;
>  			}
>  		} else if (is_res_lock && cur->active_locks) {
>  			if (find_lock_state(env->cur_state, REF_TYPE_RES_LOCK | REF_TYPE_RES_LOCK_IRQ, reg->id, ptr)) {
>  				verbose(env, "Acquiring the same lock again, AA deadlock detected\n");
> +				bpf_diag_report_resource_state(env, env->insn_idx,
> +							       "recursive resource spin lock",
> +							       "This path already holds the same resource spin lock. Taking it again would deadlock.",
> +							       "Avoid reacquiring the same resource spin lock before it is unlocked.");
>  				return -EINVAL;
>  			}
>  		}
> @@ -7289,6 +7315,10 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state
>  
>  		if (!cur->active_locks) {
>  			verbose(env, "%s_unlock without taking a lock\n", lock_str);
> +			bpf_diag_report_resource_state(env, env->insn_idx,
> +						       "unlock without lock",
> +						       "This unlock operation has no matching active lock on the current path.",
> +						       "Take the matching lock before this unlock, or remove the unmatched unlock path.");
>  			return -EINVAL;
>  		}
>  
> @@ -7300,14 +7330,26 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state
>  			type = REF_TYPE_LOCK;
>  		if (!find_lock_state(cur, type, reg->id, ptr)) {
>  			verbose(env, "%s_unlock of different lock\n", lock_str);
> +			bpf_diag_report_resource_state(env, env->insn_idx,
> +						       "unlock of different lock",
> +						       "This unlock does not match any active lock with the same tracked identity on the current path.",
> +						       "Unlock the same lock object that was most recently acquired.");
>  			return -EINVAL;
>  		}
>  		if (reg->id != cur->active_lock_id || ptr != cur->active_lock_ptr) {
>  			verbose(env, "%s_unlock cannot be out of order\n", lock_str);
> +			bpf_diag_report_resource_state(env, env->insn_idx,
> +						       "unlock out of order",
> +						       "Locks must be released in last-in, first-out order, but this unlock does not match the currently active lock.",
> +						       "Release nested locks in the reverse order they were acquired.");
>  			return -EINVAL;
>  		}
>  		if (release_lock_state(env, type, reg->id, ptr)) {
>  			verbose(env, "%s_unlock of different lock\n", lock_str);
> +			bpf_diag_report_resource_state(env, env->insn_idx,
> +						       "unlock of different lock",
> +						       "The verifier could not release a lock state matching this unlock operation.",
> +						       "Pass the same lock object and lock kind that were used for the matching lock operation.");
>  			return -EINVAL;
>  		}
>  
> @@ -7473,6 +7515,10 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
>  		verbose(env,
>  			"%s expected pointer to stack or const struct bpf_dynptr\n",
>  			reg_arg_name(env, argno));
> +		bpf_diag_report_resource_state(env, insn_idx,
> +					       "invalid dynptr argument",
> +					       "A dynptr argument must be a pointer to a dynptr stack slot or a verifier-provided const struct bpf_dynptr.",
> +					       "Pass the address of a stack dynptr object, or use a const dynptr pointer returned by the verifier-supported path.");
>  		return -EINVAL;
>  	}
>  
> @@ -7495,6 +7541,10 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
>  
>  		if (!is_dynptr_reg_valid_uninit(env, reg)) {
>  			verbose(env, "Dynptr has to be an uninitialized dynptr\n");
> +			bpf_diag_report_resource_state(env, insn_idx,
> +						       "dynptr is already initialized",
> +						       "This kfunc constructs a dynptr and requires an uninitialized dynptr stack slot, but the selected slot already holds dynptr state.",
> +						       "Use a fresh stack dynptr slot, or release/destroy the existing dynptr before reusing the slot.");
>  			return -EINVAL;
>  		}
>  
> @@ -7511,12 +7561,20 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
>  		/* For the reg->type == PTR_TO_STACK case, bpf_dynptr is never const */
>  		if (reg->type == CONST_PTR_TO_DYNPTR && (arg_type & OBJ_RELEASE)) {
>  			verbose(env, "CONST_PTR_TO_DYNPTR cannot be released\n");
> +			bpf_diag_report_resource_state(env, insn_idx,
> +						       "const dynptr release",
> +						       "This release operation was given a const dynptr. Const dynptr values are verifier-provided views and cannot be released by the program.",
> +						       "Release only mutable dynptrs that the program initialized or reserved.");
>  			return -EINVAL;
>  		}
>  
>  		if (!is_dynptr_reg_valid_init(env, reg)) {
>  			verbose(env, "Expected an initialized dynptr as %s\n",
>  				reg_arg_name(env, argno));
> +			bpf_diag_report_resource_state(env, insn_idx,
> +						       "uninitialized dynptr use",
> +						       "This operation requires an initialized dynptr, but the stack slot does not currently hold a valid dynptr on this path.",
> +						       "Initialize the dynptr on every path before this call, and avoid overwriting or releasing it before this use.");
>  			return -EINVAL;
>  		}
>  
> @@ -7526,6 +7584,10 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
>  				"Expected a dynptr of type %s as %s\n",
>  				dynptr_type_str(arg_to_dynptr_type(arg_type)),
>  				reg_arg_name(env, argno));
> +			bpf_diag_report_resource_state(env, insn_idx,
> +						       "wrong dynptr type",
> +						       "The dynptr is initialized, but it was created for a different backing object type than this operation accepts.",
> +						       "Use a dynptr constructor that matches this operation, or call an operation that accepts the dynptr's current type.");
>  			return -EINVAL;
>  		}
>  
> @@ -7594,6 +7656,10 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
>  	if (reg->type != PTR_TO_STACK) {
>  		verbose(env, "%s expected pointer to an iterator on stack\n",
>  			reg_arg_name(env, argno));
> +		bpf_diag_report_resource_state(env, insn_idx,
> +					       "invalid iterator argument",
> +					       "Iterator state must live in verifier-tracked stack memory, but this argument is not a stack pointer.",
> +					       "Pass the address of a stack iterator object for iterator new, next, and destroy calls.");
>  		return -EINVAL;
>  	}
>  
> @@ -7607,6 +7673,10 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
>  	if (btf_id < 0) {
>  		verbose(env, "expected valid iter pointer as %s\n",
>  			reg_arg_name(env, argno));
> +		bpf_diag_report_resource_state(env, insn_idx,
> +					       "invalid iterator type",
> +					       "The kfunc expects a recognized iterator state pointer, but this argument does not match a valid iterator type.",
> +					       "Pass the exact iterator state type expected by this kfunc.");
>  		return -EINVAL;
>  	}
>  	t = btf_type_by_id(meta->btf, btf_id);
> @@ -7617,6 +7687,10 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
>  		if (!is_iter_reg_valid_uninit(env, reg, nr_slots)) {
>  			verbose(env, "expected uninitialized iter_%s as %s\n",
>  				iter_type_str(meta->btf, btf_id), reg_arg_name(env, argno));
> +			bpf_diag_report_resource_state(env, insn_idx,
> +						       "iterator is already initialized",
> +						       "Iterator creation requires an uninitialized iterator stack object, but this stack range already contains iterator state.",
> +						       "Use a fresh iterator stack slot, or destroy the existing iterator before reusing the slot.");

Maybe shorten bpf_diag_report_resource_state() name to bpf_diag() ?


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

* Re: [PATCH bpf-next v2 12/17] bpf: Report Execution Context Safety errors
  2026-06-19 20:59 ` [PATCH bpf-next v2 12/17] bpf: Report Execution Context Safety errors Kumar Kartikeya Dwivedi
  2026-06-19 21:19   ` sashiko-bot
@ 2026-06-19 23:44   ` Alexei Starovoitov
  1 sibling, 0 replies; 34+ messages in thread
From: Alexei Starovoitov @ 2026-06-19 23:44 UTC (permalink / raw)
  To: Kumar Kartikeya Dwivedi, bpf
  Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
	Eduard Zingerman, Emil Tsalapatis, kkd, kernel-team

On Fri Jun 19, 2026 at 1:59 PM PDT, Kumar Kartikeya Dwivedi wrote:
> Augment selected sleepability and critical-section failures with Execution
> Context Safety reports. Keep the existing verifier messages and add source
> context, path history, and suggestions tied to the active context.
>
> Use the context history recorded earlier to anchor causal paths to lock, IRQ,
> RCU, and preempt regions instead of unrelated register updates.
>
> Cover global calls while holding a lock, sleepable global function calls,
> sleepable helpers, sleepable kfunc calls from disallowed contexts, operations
> that exit while a context is still active, and unmatched context exits.
>
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
> ---
>  kernel/bpf/diagnostics.c | 165 +++++++++++++++++++++++++++++++++++++++
>  kernel/bpf/diagnostics.h |  14 ++++
>  kernel/bpf/verifier.c    | 114 +++++++++++++++++++++++++++
>  3 files changed, 293 insertions(+)
>
> diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
> index 19e72b07afc1..7c903e502973 100644
> --- a/kernel/bpf/diagnostics.c
> +++ b/kernel/bpf/diagnostics.c
> @@ -844,6 +844,7 @@ static u32 bpf_diag_current_frameno(const struct bpf_verifier_env *env)
>  }
>  
>  static int bpf_diag_stack_argno(u8 slot);
> +static const char *bpf_diag_context_name(enum bpf_diag_context_kind kind);
>  
>  void bpf_diag_report_register_type(struct bpf_verifier_env *env,
>  				   u32 insn_idx, int regno,
> @@ -953,6 +954,170 @@ void bpf_diag_report_call_type(struct bpf_verifier_env *env, u32 insn_idx,
>  	bpf_diag_report_suggestion(env, "%s", suggestion);
>  }
>  
> +static const char *bpf_diag_context_constraint(enum bpf_diag_context_kind kind)
> +{
> +	switch (kind) {
> +	case BPF_DIAG_CONTEXT_RCU:
> +		return "RCU read-side critical sections cannot call operations that may sleep";
> +	case BPF_DIAG_CONTEXT_PREEMPT:
> +		return "preemption-disabled code cannot call operations that may sleep";
> +	case BPF_DIAG_CONTEXT_IRQ:
> +		return "IRQ-disabled code cannot call operations that may sleep";
> +	case BPF_DIAG_CONTEXT_LOCK:
> +		return "code holding a BPF spin lock cannot call operations that may sleep";
> +	case BPF_DIAG_CONTEXT_NONE:
> +	default:
> +		return NULL;
> +	}
> +}
> +
> +static void bpf_diag_format_active_context(char *buf, size_t size, u32 depth,
> +					   const char *context)
> +{
> +	if (depth == 1)
> +		scnprintf(buf, size, "an active %s (depth 1)", context);
> +	else
> +		scnprintf(buf, size, "%u active %ss (depth %u)", depth,
> +			  context, depth);
> +}
> +
> +static u32 bpf_diag_context_depth(struct bpf_verifier_env *env,
> +				  enum bpf_diag_context_kind kind)
> +{
> +	switch (kind) {
> +	case BPF_DIAG_CONTEXT_RCU:
> +		return env->cur_state->active_rcu_locks;
> +	case BPF_DIAG_CONTEXT_PREEMPT:
> +		return env->cur_state->active_preempt_locks;
> +	case BPF_DIAG_CONTEXT_IRQ:
> +		return env->cur_state->active_irq_id ? 1 : 0;
> +	case BPF_DIAG_CONTEXT_LOCK:
> +		return env->cur_state->active_locks;
> +	case BPF_DIAG_CONTEXT_NONE:
> +	default:
> +		return 0;
> +	}
> +}
> +
> +void bpf_diag_report_execution_context(struct bpf_verifier_env *env,
> +				       u32 insn_idx, const char *operation,
> +				       enum bpf_diag_context_kind ctx_kind,
> +				       const char *context,
> +				       const char *suggestion)
> +{
> +	u32 depth = bpf_diag_context_depth(env, ctx_kind);
> +	struct bpf_diag_history_opts opts = {
> +		.scope = BPF_DIAG_HISTORY_SCOPE_CONTEXT,
> +		.ctx_kind = ctx_kind,
> +		.ctx_depth = depth,
> +	};
> +	const char *depth_buf;
> +
> +	bpf_diag_report_header(env, BPF_DIAG_CATEGORY_EXECUTION_CONTEXT_SAFETY,
> +			       "operation is not allowed in this context");
> +	if (bpf_diag_context_constraint(ctx_kind)) {
> +		if (depth) {
> +			depth_buf = bpf_diag_scratch_buf(env,
> +							 2,
> +							 NULL);
> +			if (depth_buf)
> +				bpf_diag_format_active_context((char *)depth_buf,
> +							       BPF_DIAG_SCRATCH_STR_LEN,
> +							       depth, context);
> +			else
> +				depth_buf = "";
> +			bpf_diag_report_reason(env,
> +					       "The operation %s cannot be used in %s because %s. This path is still inside %s.",
> +					       operation, context,
> +					       bpf_diag_context_constraint(ctx_kind),
> +					       depth_buf);
> +		} else {
> +			bpf_diag_report_reason(env,
> +					       "The operation %s cannot be used in %s because %s.",
> +					       operation, context,
> +					       bpf_diag_context_constraint(ctx_kind));
> +		}
> +	} else {
> +		bpf_diag_report_reason(env,
> +				       "The operation %s cannot be used in %s.",
> +				       operation, context);
> +	}
> +
> +	bpf_diag_report_section(env, "At");
> +	bpf_diag_report_source(env, insn_idx, "error",
> +			       "%s is not allowed in %s", operation, context);
> +
> +	if (ctx_kind != BPF_DIAG_CONTEXT_NONE)
> +		bpf_diag_print_history(env, &opts);
> +
> +	bpf_diag_report_suggestion(env, "%s", suggestion);
> +}
> +
> +void bpf_diag_report_context_still_active(struct bpf_verifier_env *env,
> +					  u32 insn_idx, const char *operation,
> +					  enum bpf_diag_context_kind ctx_kind,
> +					  const char *context,
> +					  const char *suggestion)
> +{
> +	u32 depth = bpf_diag_context_depth(env, ctx_kind);
> +	struct bpf_diag_history_opts opts = {
> +		.scope = BPF_DIAG_HISTORY_SCOPE_CONTEXT,
> +		.ctx_kind = ctx_kind,
> +		.ctx_depth = depth,
> +	};
> +	const char *depth_buf;
> +
> +	depth_buf = bpf_diag_scratch_buf(env, 2, NULL);
> +	if (depth_buf)
> +		bpf_diag_format_active_context((char *)depth_buf,
> +					       BPF_DIAG_SCRATCH_STR_LEN, depth,
> +					       context);
> +	else
> +		depth_buf = "";
> +
> +	bpf_diag_report_header(env, BPF_DIAG_CATEGORY_EXECUTION_CONTEXT_SAFETY,
> +			       "operation is not allowed in this context");
> +	bpf_diag_report_reason(env,
> +			       "The operation %s cannot be used while this path is still inside %s. Leave the region before this operation.",
> +			       operation, depth_buf);
> +
> +	bpf_diag_report_section(env, "At");
> +	bpf_diag_report_source(env, insn_idx, "error",
> +			       "%s is not allowed before leaving %s",
> +			       operation, context);
> +
> +	bpf_diag_print_history(env, &opts);
> +
> +	bpf_diag_report_suggestion(env, "%s", suggestion);
> +}
> +
> +void bpf_diag_report_context_underflow(struct bpf_verifier_env *env,
> +				       u32 insn_idx, const char *operation,
> +				       enum bpf_diag_context_kind ctx_kind,
> +				       const char *suggestion)
> +{
> +	struct bpf_diag_history_opts opts = {
> +		.scope = BPF_DIAG_HISTORY_SCOPE_CONTEXT,
> +		.ctx_kind = ctx_kind,
> +	};
> +	const char *context = bpf_diag_context_name(ctx_kind);
> +
> +	bpf_diag_report_header(env, BPF_DIAG_CATEGORY_EXECUTION_CONTEXT_SAFETY,
> +			       "unmatched context exit");
> +	bpf_diag_report_reason(env,
> +			       "The operation %s tries to leave %s, but this path has no active %s to leave. The current depth is 0.",
> +			       operation, context, context);
> +
> +	bpf_diag_report_section(env, "At");
> +	bpf_diag_report_source(env, insn_idx, "error",
> +			       "%s has no matching enter on this path",
> +			       operation);
> +
> +	bpf_diag_print_history(env, &opts);
> +
> +	bpf_diag_report_suggestion(env, "%s", suggestion);
> +}
> +
>  void bpf_diag_report_invalid_deref(struct bpf_verifier_env *env, u32 insn_idx,
>  				   int regno, const char *reg_name,
>  				   const char *type_name,
> diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
> index 07d06d366f22..4611d94e7a18 100644
> --- a/kernel/bpf/diagnostics.h
> +++ b/kernel/bpf/diagnostics.h
> @@ -202,6 +202,20 @@ void bpf_diag_report_call_type(struct bpf_verifier_env *env, u32 insn_idx,
>  			       int argno, int regno, int stack_arg_slot,
>  			       const char *call_name, const char *arg_name,
>  			       const char *reason, const char *suggestion);
> +void bpf_diag_report_execution_context(struct bpf_verifier_env *env,
> +				       u32 insn_idx, const char *operation,
> +				       enum bpf_diag_context_kind ctx_kind,
> +				       const char *context,
> +				       const char *suggestion);
> +void bpf_diag_report_context_still_active(struct bpf_verifier_env *env,
> +					  u32 insn_idx, const char *operation,
> +					  enum bpf_diag_context_kind ctx_kind,
> +					  const char *context,
> +					  const char *suggestion);
> +void bpf_diag_report_context_underflow(struct bpf_verifier_env *env,
> +				       u32 insn_idx, const char *operation,
> +				       enum bpf_diag_context_kind ctx_kind,
> +				       const char *suggestion);

The names are overly long which pushes indent way to far to the right.
Maybe one bpf_diag() helper with enum that multiplexes
"execution context" vs "context still active" vs ...


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

end of thread, other threads:[~2026-06-19 23:44 UTC | newest]

Thread overview: 34+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-19 20:59 [PATCH bpf-next v2 00/17] Redesign Verification Errors Kumar Kartikeya Dwivedi
2026-06-19 20:59 ` [PATCH bpf-next v2 01/17] bpf: Add verifier diagnostics report helpers Kumar Kartikeya Dwivedi
2026-06-19 21:09   ` sashiko-bot
2026-06-19 20:59 ` [PATCH bpf-next v2 02/17] bpf: Add source and instruction diagnostic context Kumar Kartikeya Dwivedi
2026-06-19 21:46   ` bot+bpf-ci
2026-06-19 20:59 ` [PATCH bpf-next v2 03/17] bpf: Add verifier diagnostic event log Kumar Kartikeya Dwivedi
2026-06-19 21:46   ` bot+bpf-ci
2026-06-19 20:59 ` [PATCH bpf-next v2 04/17] bpf: Prune verifier diagnostics on backtracking Kumar Kartikeya Dwivedi
2026-06-19 21:46   ` bot+bpf-ci
2026-06-19 20:59 ` [PATCH bpf-next v2 05/17] bpf: Track verifier register diagnostic events Kumar Kartikeya Dwivedi
2026-06-19 21:18   ` sashiko-bot
2026-06-19 23:35   ` Alexei Starovoitov
2026-06-19 20:59 ` [PATCH bpf-next v2 06/17] bpf: Track verifier reference " Kumar Kartikeya Dwivedi
2026-06-19 20:59 ` [PATCH bpf-next v2 07/17] bpf: Track verifier context " Kumar Kartikeya Dwivedi
2026-06-19 21:13   ` sashiko-bot
2026-06-19 21:19     ` Kumar Kartikeya Dwivedi
2026-06-19 21:46   ` bot+bpf-ci
2026-06-19 20:59 ` [PATCH bpf-next v2 08/17] bpf: Report Register Type Safety errors Kumar Kartikeya Dwivedi
2026-06-19 20:59 ` [PATCH bpf-next v2 09/17] bpf: Report Memory Safety bounds errors Kumar Kartikeya Dwivedi
2026-06-19 21:46   ` bot+bpf-ci
2026-06-19 23:40   ` Alexei Starovoitov
2026-06-19 20:59 ` [PATCH bpf-next v2 10/17] bpf: Report Resource Lifetime reference leaks Kumar Kartikeya Dwivedi
2026-06-19 21:12   ` sashiko-bot
2026-06-19 23:42   ` Alexei Starovoitov
2026-06-19 20:59 ` [PATCH bpf-next v2 11/17] bpf: Report Call Type Safety argument errors Kumar Kartikeya Dwivedi
2026-06-19 21:47   ` bot+bpf-ci
2026-06-19 20:59 ` [PATCH bpf-next v2 12/17] bpf: Report Execution Context Safety errors Kumar Kartikeya Dwivedi
2026-06-19 21:19   ` sashiko-bot
2026-06-19 23:44   ` Alexei Starovoitov
2026-06-19 20:59 ` [PATCH bpf-next v2 13/17] bpf: Report Program Structure CFG errors Kumar Kartikeya Dwivedi
2026-06-19 20:59 ` [PATCH bpf-next v2 14/17] bpf: Report Policy helper and kfunc errors Kumar Kartikeya Dwivedi
2026-06-19 20:59 ` [PATCH bpf-next v2 15/17] bpf: Report Verifier Limit errors Kumar Kartikeya Dwivedi
2026-06-19 20:59 ` [PATCH bpf-next v2 16/17] bpf: Report Verifier Internal errors Kumar Kartikeya Dwivedi
2026-06-19 20:59 ` [PATCH bpf-next v2 17/17] bpf: Gate verifier diagnostics on log level Kumar Kartikeya Dwivedi

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox