AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/10/22 03:52 flow patch-triage
2m Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "BPF_JIT"
  ],
  "FocusSymbols": [
    "emit_hmul_aux",
    "adjust_scalar_min_max_vals",
    "scalar_min_max_uhmul",
    "scalar_min_max_shmul",
    "check_alu_fields"
  ],
  "KMSANReasoning": "The patch series introduces BPF support for unsigned and signed 64-bit high-half multiplication instructions (UHMUL and SHMUL). It adds instruction definitions to the BPF specification, handles them in the BPF interpreter and disassembler, adds scalar constant-folding and arena-pointer rejection in the BPF verifier, and implements JIT support across architectures (x86_64, arm64, riscv64) while returning -EOPNOTSUPP on unsupported architectures.\n\nRegarding memory safety:\n1. No kernel structures or buffers are copied to user space (no copy_to_user, put_user, netlink skb attributes, ioctls, or info-leaks).\n2. All variables in the new math helpers (bpf_uhmul64, bpf_shmul64), interpreter routines, and JIT emission functions are fully initialized.\n3. The BPF verifier treats UHMUL/SHMUL on non-constant registers by setting them to unknown (__mark_reg_unknown) and marks known constants via ___mark_reg_known; arena pointer operations are rejected. No uninitialized register state or buffer length confusion is created.\n4. Any potential issues with JIT code generation, register clobbering, or verifier scalar tracking are either logic bugs, execution failures, or memory corruption/out-of-bounds accesses, which are covered by standard kernel testing and KASAN.\n\nBecause the patch does not introduce or expose uninitialized memory reads or information leaks, a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch introduces two new 64-bit BPF ALU multiplication instructions (UHMUL for unsigned high-half multiplication and SHMUL for signed high-half multiplication). The changes implement verifier validation, scalar range tracking, x86_64 JIT compilation, and interpreter support. These are core BPF subsystem components directly reachable from userspace via the bpf() syscall and warrant fuzzing for verifier correctness, range tracking bugs, and JIT code generation issues.",
  "WorthFuzzing": true
}

1/1 2026/10/22 03:52 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit b4427bda51c386dff85db43d5a27629e6188444f\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Thu Oct 22 03:52:31 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/bpf/standardization/instruction-set.rst b/Documentation/bpf/standardization/instruction-set.rst\nindex 39c74611752b6..6f3cb6bcaa1a6 100644\n--- a/Documentation/bpf/standardization/instruction-set.rst\n+++ b/Documentation/bpf/standardization/instruction-set.rst\n@@ -142,8 +142,9 @@ This document defines the following conformance groups:\n * atomic32: includes 32-bit atomic operation instructions (see `Atomic operations`_).\n * atomic64: includes atomic32, plus 64-bit atomic operation instructions.\n * divmul32: includes 32-bit division, multiplication, and modulo instructions.\n-* divmul64: includes divmul32, plus 64-bit division, multiplication,\n+* divmul64: includes divmul32, plus 64-bit division, low-half multiplication,\n   and modulo instructions.\n+* divmul64v2: includes divmul64, plus high-half multiplication instructions.\n * packet: deprecated packet access instructions.\n \n Instruction encoding\n@@ -330,6 +331,8 @@ register.\n   ADD    0x0    0        dst += src\n   SUB    0x1    0        dst -= src\n   MUL    0x2    0        dst \\*= src\n+  UHMUL  0x2    1        ``dst uh*= src`` (unsigned high half, ``ALU64`` only)\n+  SHMUL  0x2    2        ``dst sh*= src`` (signed high half, ``ALU64`` only)\n   DIV    0x3    0        dst = (src != 0) ? (dst / src) : 0\n   SDIV   0x3    1        dst = (src == 0) ? 0 : ((src == -1 \u0026\u0026 dst == LLONG_MIN) ? LLONG_MIN : (dst s/ src))\n   OR     0x4    0        dst \\|= src\n@@ -379,13 +382,33 @@ where '(u32)' indicates that the upper 32 bits are zeroed.\n \n   dst = dst ^ imm\n \n-Note that most arithmetic instructions have 'offset' set to 0. Only three instructions\n-(``SDIV``, ``SMOD``, ``MOVSX``) have a non-zero 'offset'.\n+Note that most arithmetic instructions have 'offset' set to 0. The following\n+instructions may have a non-zero 'offset':\n+\n+* ``SDIV`` and ``SMOD``: ``offset == 1`` selects the signed variant.\n+* ``MOVSX``: ``offset`` selects the source operand width.\n+* ``MUL``: ``offset`` selects the multiplication variant (see below).\n+\n+Division, low-half multiplication (``MUL`` with ``offset == 0``), and\n+modulo operations for ``ALU`` are part of the \"divmul32\" conformance\n+group, and their ``ALU64`` variants are part of the \"divmul64\"\n+conformance group.\n+\n+For ``ALU64`` multiplication, ``offset == 1`` selects unsigned\n+high-half multiplication (``UHMUL``), and ``offset == 2`` selects\n+signed high-half multiplication (``SHMUL``). Both instructions belong\n+to the \"divmul64v2\" conformance group.\n+\n+For ``X`` mode, ``src64`` is the 64-bit value in ``src_reg``. For\n+``K`` mode, ``src64`` is ``imm`` sign-extended from 32 to 64 bits.\n+The operations are::\n+\n+  UHMUL: dst = (u64)(((u128)dst * (u128)(u64)src64) \u003e\u003e 64)\n+  SHMUL: dst = (u64)(((s128)(s64)dst * (s128)(s64)src64) \u003e\u003e 64)\n+\n+For ``ALU`` multiplication, only ``offset == 0`` is valid. For ``ALU64``\n+multiplication, offset values other than 0, 1, and 2 are reserved.\n \n-Division, multiplication, and modulo operations for ``ALU`` are part\n-of the \"divmul32\" conformance group, and division, multiplication, and\n-modulo operations for ``ALU64`` are part of the \"divmul64\" conformance\n-group.\n The division and modulo operations support both unsigned and signed flavors.\n \n For unsigned operations (``DIV`` and ``MOD``), for ``ALU``,\ndiff --git a/arch/arc/net/bpf_jit_core.c b/arch/arc/net/bpf_jit_core.c\nindex 639a2736f029e..78b1819cee5a6 100644\n--- a/arch/arc/net/bpf_jit_core.c\n+++ b/arch/arc/net/bpf_jit_core.c\n@@ -734,6 +734,9 @@ static int handle_insn(struct jit_context *ctx, u32 idx)\n \tu8  len = 0;\n \tint ret = 0;\n \n+\tif (bpf_insn_is_hmul(insn))\n+\t\treturn -EOPNOTSUPP;\n+\n \tswitch (code) {\n \t/* dst += src (32-bit) */\n \tcase BPF_ALU | BPF_ADD | BPF_X:\ndiff --git a/arch/arm/net/bpf_jit_32.c b/arch/arm/net/bpf_jit_32.c\nindex 9ede81afbc50e..aa466fc79d964 100644\n--- a/arch/arm/net/bpf_jit_32.c\n+++ b/arch/arm/net/bpf_jit_32.c\n@@ -1611,6 +1611,9 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx)\n } while (0)\n #define check_imm24(imm) check_imm(24, imm)\n \n+\tif (bpf_insn_is_hmul(insn))\n+\t\tgoto notyet;\n+\n \tswitch (code) {\n \t/* ALU operations */\n \ndiff --git a/arch/arm64/include/asm/insn.h b/arch/arm64/include/asm/insn.h\nindex 1ce75a4426387..f38738fa54a27 100644\n--- a/arch/arm64/include/asm/insn.h\n+++ b/arch/arm64/include/asm/insn.h\n@@ -247,6 +247,8 @@ enum aarch64_insn_data2_type {\n enum aarch64_insn_data3_type {\n \tAARCH64_INSN_DATA3_MADD,\n \tAARCH64_INSN_DATA3_MSUB,\n+\tAARCH64_INSN_DATA3_SMULH,\n+\tAARCH64_INSN_DATA3_UMULH,\n };\n \n enum aarch64_insn_logic_type {\n@@ -396,6 +398,8 @@ __AARCH64_INSN_FUNCS(sub_ext,\t0x7FE00000, 0x4B200000)\n __AARCH64_INSN_FUNCS(subs_ext,\t0x7FE00000, 0x6B200000)\n __AARCH64_INSN_FUNCS(madd,\t0x7FE08000, 0x1B000000)\n __AARCH64_INSN_FUNCS(msub,\t0x7FE08000, 0x1B008000)\n+__AARCH64_INSN_FUNCS(smulh,\t0x7FE0FC00, 0x1B407C00)\n+__AARCH64_INSN_FUNCS(umulh,\t0x7FE0FC00, 0x1BC07C00)\n __AARCH64_INSN_FUNCS(udiv,\t0x7FE0FC00, 0x1AC00800)\n __AARCH64_INSN_FUNCS(sdiv,\t0x7FE0FC00, 0x1AC00C00)\n __AARCH64_INSN_FUNCS(lslv,\t0x7FE0FC00, 0x1AC02000)\ndiff --git a/arch/arm64/lib/insn.c b/arch/arm64/lib/insn.c\nindex e70ac02385153..dc483d1b8bd54 100644\n--- a/arch/arm64/lib/insn.c\n+++ b/arch/arm64/lib/insn.c\n@@ -1155,6 +1155,12 @@ u32 aarch64_insn_gen_data3(enum aarch64_insn_register dst,\n \tcase AARCH64_INSN_DATA3_MSUB:\n \t\tinsn = aarch64_insn_get_msub_value();\n \t\tbreak;\n+\tcase AARCH64_INSN_DATA3_SMULH:\n+\t\tinsn = aarch64_insn_get_smulh_value();\n+\t\tbreak;\n+\tcase AARCH64_INSN_DATA3_UMULH:\n+\t\tinsn = aarch64_insn_get_umulh_value();\n+\t\tbreak;\n \tdefault:\n \t\tpr_err(\"%s: unknown data3 encoding %d\\n\", __func__, type);\n \t\treturn AARCH64_BREAK_FAULT;\ndiff --git a/arch/arm64/net/bpf_jit.h b/arch/arm64/net/bpf_jit.h\nindex b2fe6e6dcf447..70fc329c661db 100644\n--- a/arch/arm64/net/bpf_jit.h\n+++ b/arch/arm64/net/bpf_jit.h\n@@ -281,6 +281,12 @@\n \tA64_VARIANT(sf), AARCH64_INSN_DATA3_MSUB)\n /* Rd = Rn * Rm */\n #define A64_MUL(sf, Rd, Rn, Rm) A64_MADD(sf, Rd, A64_ZR, Rn, Rm)\n+/* Rd = high 64 bits of signed Rn * Rm */\n+#define A64_SMULH(Rd, Rn, Rm) aarch64_insn_gen_data3(Rd, A64_ZR, Rn, Rm, \\\n+\tAARCH64_INSN_VARIANT_64BIT, AARCH64_INSN_DATA3_SMULH)\n+/* Rd = high 64 bits of unsigned Rn * Rm */\n+#define A64_UMULH(Rd, Rn, Rm) aarch64_insn_gen_data3(Rd, A64_ZR, Rn, Rm, \\\n+\tAARCH64_INSN_VARIANT_64BIT, AARCH64_INSN_DATA3_UMULH)\n \n /* Logical (shifted register) */\n #define A64_LOGIC_SREG(sf, Rd, Rn, Rm, type) \\\ndiff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c\nindex 326e9e1dea0a7..a9989cd5098cc 100644\n--- a/arch/arm64/net/bpf_jit_comp.c\n+++ b/arch/arm64/net/bpf_jit_comp.c\n@@ -144,6 +144,17 @@ static inline void emit_a64_mov_i(const int is64, const int reg,\n \t}\n }\n \n+static inline void emit_a64_mul(const bool is64, const u8 dst, const u8 src,\n+\t\t\t\tconst s16 off, struct jit_ctx *ctx)\n+{\n+\tif (is64 \u0026\u0026 off == BPF_UHMUL)\n+\t\temit(A64_UMULH(dst, dst, src), ctx);\n+\telse if (is64 \u0026\u0026 off == BPF_SHMUL)\n+\t\temit(A64_SMULH(dst, dst, src), ctx);\n+\telse\n+\t\temit(A64_MUL(is64, dst, dst, src), ctx);\n+}\n+\n static int i64_i16_blocks(const u64 val, bool inverse)\n {\n \treturn (((val \u003e\u003e  0) \u0026 0xffff) != (inverse ? 0xffff : 0x0000)) +\n@@ -1531,7 +1542,7 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn\n \t\tbreak;\n \tcase BPF_ALU | BPF_MUL | BPF_X:\n \tcase BPF_ALU64 | BPF_MUL | BPF_X:\n-\t\temit(A64_MUL(is64, dst, dst, src), ctx);\n+\t\temit_a64_mul(is64, dst, src, off, ctx);\n \t\tbreak;\n \tcase BPF_ALU | BPF_DIV | BPF_X:\n \tcase BPF_ALU64 | BPF_DIV | BPF_X:\n@@ -1660,7 +1671,7 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn\n \tcase BPF_ALU | BPF_MUL | BPF_K:\n \tcase BPF_ALU64 | BPF_MUL | BPF_K:\n \t\temit_a64_mov_i(is64, tmp, imm, ctx);\n-\t\temit(A64_MUL(is64, dst, dst, tmp), ctx);\n+\t\temit_a64_mul(is64, dst, tmp, off, ctx);\n \t\tbreak;\n \tcase BPF_ALU | BPF_DIV | BPF_K:\n \tcase BPF_ALU64 | BPF_DIV | BPF_K:\ndiff --git a/arch/loongarch/net/bpf_jit.c b/arch/loongarch/net/bpf_jit.c\nindex 9393e4b0db8e1..71bd518ceaf32 100644\n--- a/arch/loongarch/net/bpf_jit.c\n+++ b/arch/loongarch/net/bpf_jit.c\n@@ -709,6 +709,9 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext\n \tconst s32 imm = insn-\u003eimm;\n \tconst bool is32 = BPF_CLASS(insn-\u003ecode) == BPF_ALU || BPF_CLASS(insn-\u003ecode) == BPF_JMP32;\n \n+\tif (bpf_insn_is_hmul(insn))\n+\t\treturn -EOPNOTSUPP;\n+\n \tswitch (code) {\n \t/* dst = src */\n \tcase BPF_ALU | BPF_MOV | BPF_X:\ndiff --git a/arch/mips/net/bpf_jit_comp32.c b/arch/mips/net/bpf_jit_comp32.c\nindex 84d9c7036e4f0..4a8bedddf61be 100644\n--- a/arch/mips/net/bpf_jit_comp32.c\n+++ b/arch/mips/net/bpf_jit_comp32.c\n@@ -1544,6 +1544,9 @@ int build_insn(const struct bpf_insn *insn, struct jit_context *ctx)\n \ts32 val, rel;\n \tu8 alu, jmp;\n \n+\tif (bpf_insn_is_hmul(insn))\n+\t\tgoto notyet;\n+\n \tswitch (code) {\n \t/* ALU operations */\n \t/* dst = imm */\ndiff --git a/arch/mips/net/bpf_jit_comp64.c b/arch/mips/net/bpf_jit_comp64.c\nindex 994589c3de46c..75202cac31f47 100644\n--- a/arch/mips/net/bpf_jit_comp64.c\n+++ b/arch/mips/net/bpf_jit_comp64.c\n@@ -719,6 +719,9 @@ int build_insn(const struct bpf_insn *insn, struct jit_context *ctx)\n \ts32 val, rel;\n \tu8 alu, jmp;\n \n+\tif (bpf_insn_is_hmul(insn))\n+\t\tgoto notyet;\n+\n \tswitch (code) {\n \t/* ALU operations */\n \t/* dst = imm */\ndiff --git a/arch/parisc/net/bpf_jit_comp32.c b/arch/parisc/net/bpf_jit_comp32.c\nindex eea0a34a3bfee..294eee24f2d9d 100644\n--- a/arch/parisc/net/bpf_jit_comp32.c\n+++ b/arch/parisc/net/bpf_jit_comp32.c\n@@ -1142,6 +1142,9 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct hppa_jit_context *ctx,\n \t\tBPF_CLASS(code), code, (code \u0026 BPF_ALU64) ? 1:0, BPF_SIZE(code),\n \t\tBPF_OP(code), insn-\u003esrc_reg, insn-\u003edst_reg);\n \n+\tif (bpf_insn_is_hmul(insn))\n+\t\treturn -EOPNOTSUPP;\n+\n \tswitch (code) {\n \t/* dst = src */\n \tcase BPF_ALU64 | BPF_MOV | BPF_X:\ndiff --git a/arch/parisc/net/bpf_jit_comp64.c b/arch/parisc/net/bpf_jit_comp64.c\nindex c326fa737ec8c..319bd28f1e5b6 100644\n--- a/arch/parisc/net/bpf_jit_comp64.c\n+++ b/arch/parisc/net/bpf_jit_comp64.c\n@@ -610,6 +610,9 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct hppa_jit_context *ctx,\n \n \tinit_regs(\u0026rd, \u0026rs, insn, ctx);\n \n+\tif (bpf_insn_is_hmul(insn))\n+\t\treturn -EOPNOTSUPP;\n+\n \tswitch (code) {\n \t/* dst = src */\n \tcase BPF_ALU | BPF_MOV | BPF_X:\ndiff --git a/arch/powerpc/net/bpf_jit_comp32.c b/arch/powerpc/net/bpf_jit_comp32.c\nindex bfdc50740da8e..6bfc97a029d73 100644\n--- a/arch/powerpc/net/bpf_jit_comp32.c\n+++ b/arch/powerpc/net/bpf_jit_comp32.c\n@@ -349,6 +349,9 @@ int bpf_jit_build_body(struct bpf_prog *fp, u32 *image, u32 *fimage, struct code\n \t\tu32 true_cond;\n \t\tu32 tmp_idx;\n \n+\t\tif (bpf_insn_is_hmul(\u0026insn[i]))\n+\t\t\treturn -EOPNOTSUPP;\n+\n \t\tif (i \u0026\u0026 (BPF_CLASS(code) == BPF_ALU64 || BPF_CLASS(code) == BPF_ALU) \u0026\u0026\n \t\t    (BPF_CLASS(prevcode) == BPF_ALU64 || BPF_CLASS(prevcode) == BPF_ALU) \u0026\u0026\n \t\t    BPF_OP(prevcode) == BPF_MOV \u0026\u0026 BPF_SRC(prevcode) == BPF_X \u0026\u0026\ndiff --git a/arch/powerpc/net/bpf_jit_comp64.c b/arch/powerpc/net/bpf_jit_comp64.c\nindex fc9db691e8203..192c96e0ed34a 100644\n--- a/arch/powerpc/net/bpf_jit_comp64.c\n+++ b/arch/powerpc/net/bpf_jit_comp64.c\n@@ -992,6 +992,9 @@ int bpf_jit_build_body(struct bpf_prog *fp, u32 *image, u32 *fimage, struct code\n \t\tu32 tmp_idx;\n \t\tu32 jmp_off;\n \n+\t\tif (bpf_insn_is_hmul(\u0026insn[i]))\n+\t\t\treturn -EOPNOTSUPP;\n+\n \t\t/*\n \t\t * addrs[] maps a BPF bytecode address into a real offset from\n \t\t * the start of the body code.\ndiff --git a/arch/riscv/net/bpf_jit.h b/arch/riscv/net/bpf_jit.h\nindex 5c4f53060c918..21a190a6a6d99 100644\n--- a/arch/riscv/net/bpf_jit.h\n+++ b/arch/riscv/net/bpf_jit.h\n@@ -436,6 +436,11 @@ static inline u32 rv_mul(u8 rd, u8 rs1, u8 rs2)\n \treturn rv_r_insn(1, rs2, rs1, 0, rd, 0x33);\n }\n \n+static inline u32 rv_mulh(u8 rd, u8 rs1, u8 rs2)\n+{\n+\treturn rv_r_insn(1, rs2, rs1, 1, rd, 0x33);\n+}\n+\n static inline u32 rv_mulhu(u8 rd, u8 rs1, u8 rs2)\n {\n \treturn rv_r_insn(1, rs2, rs1, 3, rd, 0x33);\ndiff --git a/arch/riscv/net/bpf_jit_comp32.c b/arch/riscv/net/bpf_jit_comp32.c\nindex a9e0bd5cc81d2..64bc6779d25c7 100644\n--- a/arch/riscv/net/bpf_jit_comp32.c\n+++ b/arch/riscv/net/bpf_jit_comp32.c\n@@ -1014,6 +1014,9 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,\n \tconst s8 *tmp1 = bpf2rv32[TMP_REG_1];\n \tconst s8 *tmp2 = bpf2rv32[TMP_REG_2];\n \n+\tif (bpf_insn_is_hmul(insn))\n+\t\tgoto notsupported;\n+\n \tswitch (code) {\n \tcase BPF_ALU64 | BPF_MOV | BPF_X:\n \t\tif (insn-\u003eoff != 0) {\ndiff --git a/arch/riscv/net/bpf_jit_comp64.c b/arch/riscv/net/bpf_jit_comp64.c\nindex 788fcc21fd5b8..dfaf85926f204 100644\n--- a/arch/riscv/net/bpf_jit_comp64.c\n+++ b/arch/riscv/net/bpf_jit_comp64.c\n@@ -197,6 +197,19 @@ static void emit_imm(u8 rd, s64 val, struct rv_jit_context *ctx)\n \t\temit_addi(rd, rd, lower, ctx);\n }\n \n+static void emit_mul(u8 rd, u8 rs, bool is64, s16 off,\n+\t\t     struct rv_jit_context *ctx)\n+{\n+\tif (!is64)\n+\t\temit(rv_mulw(rd, rd, rs), ctx);\n+\telse if (off == BPF_UHMUL)\n+\t\temit(rv_mulhu(rd, rd, rs), ctx);\n+\telse if (off == BPF_SHMUL)\n+\t\temit(rv_mulh(rd, rd, rs), ctx);\n+\telse\n+\t\temit(rv_mul(rd, rd, rs), ctx);\n+}\n+\n static void __build_epilogue(bool is_tail_call, struct rv_jit_context *ctx)\n {\n \tint stack_adjust = ctx-\u003estack_size, store_offset = stack_adjust - 8;\n@@ -1526,7 +1539,7 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,\n \t\tbreak;\n \tcase BPF_ALU | BPF_MUL | BPF_X:\n \tcase BPF_ALU64 | BPF_MUL | BPF_X:\n-\t\temit(is64 ? rv_mul(rd, rd, rs) : rv_mulw(rd, rd, rs), ctx);\n+\t\temit_mul(rd, rs, is64, off, ctx);\n \t\tif (!is64 \u0026\u0026 !aux-\u003everifier_zext)\n \t\t\temit_zextw(rd, rd, ctx);\n \t\tbreak;\n@@ -1662,8 +1675,7 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,\n \tcase BPF_ALU | BPF_MUL | BPF_K:\n \tcase BPF_ALU64 | BPF_MUL | BPF_K:\n \t\temit_imm(RV_REG_T1, imm, ctx);\n-\t\temit(is64 ? rv_mul(rd, rd, RV_REG_T1) :\n-\t\t     rv_mulw(rd, rd, RV_REG_T1), ctx);\n+\t\temit_mul(rd, RV_REG_T1, is64, off, ctx);\n \t\tif (!is64 \u0026\u0026 !aux-\u003everifier_zext)\n \t\t\temit_zextw(rd, rd, ctx);\n \t\tbreak;\ndiff --git a/arch/s390/net/bpf_jit_comp.c b/arch/s390/net/bpf_jit_comp.c\nindex 8eaaac7028a16..35af9a32294a8 100644\n--- a/arch/s390/net/bpf_jit_comp.c\n+++ b/arch/s390/net/bpf_jit_comp.c\n@@ -993,6 +993,9 @@ static noinline int bpf_jit_insn(struct bpf_jit *jit, struct bpf_prog *fp,\n \n \tbpf_jit_probe_init(\u0026probe);\n \n+\tif (bpf_insn_is_hmul(insn))\n+\t\treturn -EOPNOTSUPP;\n+\n \tswitch (insn-\u003ecode) {\n \t/*\n \t * BPF_MOV\ndiff --git a/arch/sparc/net/bpf_jit_comp_64.c b/arch/sparc/net/bpf_jit_comp_64.c\nindex 2fa0e93751276..1154ddc515b0f 100644\n--- a/arch/sparc/net/bpf_jit_comp_64.c\n+++ b/arch/sparc/net/bpf_jit_comp_64.c\n@@ -901,6 +901,9 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx)\n \tconst s16 off = insn-\u003eoff;\n \tconst s32 imm = insn-\u003eimm;\n \n+\tif (bpf_insn_is_hmul(insn))\n+\t\treturn -EOPNOTSUPP;\n+\n \tif (insn-\u003esrc_reg == BPF_REG_FP)\n \t\tctx-\u003esaw_frame_pointer = true;\n \ndiff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c\nindex 6a2fe3d0039d3..53f38e21f7f3d 100644\n--- a/arch/x86/net/bpf_jit_comp.c\n+++ b/arch/x86/net/bpf_jit_comp.c\n@@ -1378,6 +1378,38 @@ static int emit_kasan_check(struct bpf_verifier_env *env, u8 **pprog,\n \treturn 0;\n }\n \n+/*\n+ * The one-operand x86 mul/imul forms use RAX as the implicit first\n+ * operand and store the 128-bit product in RDX:RAX.\n+ * The JIT uses AUX_REG as the explicit second operand.\n+ */\n+static void emit_hmul_aux(u8 **pprog, u32 dst_reg, bool is_signed)\n+{\n+\tu8 *prog = *pprog;\n+\tbool save_rax = dst_reg != BPF_REG_0;\n+\tbool save_rdx = dst_reg != BPF_REG_3;\n+\tu8 mul_modrm_base = is_signed ? 0xE8 : 0xE0; /* imul /5 or mul /4 */\n+\n+\tif (save_rax)\n+\t\tEMIT1(0x50); /* push rax */\n+\tif (save_rdx)\n+\t\tEMIT1(0x52); /* push rdx */\n+\n+\tif (save_rax)\n+\t\temit_mov_reg(\u0026prog, true, BPF_REG_0, dst_reg);\n+\tmaybe_emit_1mod(\u0026prog, AUX_REG, true);\n+\tEMIT2(0xF7, add_1reg(mul_modrm_base, AUX_REG));\n+\tif (save_rdx)\n+\t\temit_mov_reg(\u0026prog, true, dst_reg, BPF_REG_3);\n+\n+\tif (save_rdx)\n+\t\tEMIT1(0x5A); /* pop rdx */\n+\tif (save_rax)\n+\t\tEMIT1(0x58); /* pop rax */\n+\n+\t*pprog = prog;\n+}\n+\n /* LDX: dst_reg = *(u8*)(src_reg + off) */\n static void emit_ldx(u8 **pprog, u32 size, u32 dst_reg, u32 src_reg, int off)\n {\n@@ -2916,6 +2948,14 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *\n \n \t\tcase BPF_ALU | BPF_MUL | BPF_K:\n \t\tcase BPF_ALU64 | BPF_MUL | BPF_K:\n+\t\t\tif (bpf_insn_is_hmul(insn)) {\n+\t\t\t\t/* imul/mul use AUX_REG as src. */\n+\t\t\t\temit_mov_imm32(\u0026prog, true, AUX_REG, imm32);\n+\t\t\t\temit_hmul_aux(\u0026prog, dst_reg,\n+\t\t\t\t\t      insn-\u003eoff == BPF_SHMUL);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n \t\t\t/*\n \t\t\t * lea for 3, 5 and 9, which LLVM leaves as a\n \t\t\t * multiplication, but not for r13, which needs a\n@@ -2951,6 +2991,14 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *\n \n \t\tcase BPF_ALU | BPF_MUL | BPF_X:\n \t\tcase BPF_ALU64 | BPF_MUL | BPF_X:\n+\t\t\tif (bpf_insn_is_hmul(insn)) {\n+\t\t\t\t/* imul/mul use AUX_REG as src. */\n+\t\t\t\temit_mov_reg(\u0026prog, true, AUX_REG, src_reg);\n+\t\t\t\temit_hmul_aux(\u0026prog, dst_reg,\n+\t\t\t\t\t      insn-\u003eoff == BPF_SHMUL);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n \t\t\tmaybe_emit_mod(\u0026prog, src_reg, dst_reg,\n \t\t\t\t       BPF_CLASS(insn-\u003ecode) == BPF_ALU64);\n \ndiff --git a/arch/x86/net/bpf_jit_comp32.c b/arch/x86/net/bpf_jit_comp32.c\nindex 852baf2e4db4d..11ed65130d1e5 100644\n--- a/arch/x86/net/bpf_jit_comp32.c\n+++ b/arch/x86/net/bpf_jit_comp32.c\n@@ -1683,6 +1683,9 @@ static int do_jit(struct bpf_prog *bpf_prog, int *addrs, u8 *image,\n \t\tint ilen;\n \t\tu8 *func;\n \n+\t\tif (bpf_insn_is_hmul(insn))\n+\t\t\tgoto notyet;\n+\n \t\tswitch (code) {\n \t\t/* ALU operations */\n \t\t/* dst = src */\ndiff --git a/drivers/net/ethernet/netronome/nfp/bpf/verifier.c b/drivers/net/ethernet/netronome/nfp/bpf/verifier.c\nindex 9ddbfe9d60194..4481c8d84d280 100644\n--- a/drivers/net/ethernet/netronome/nfp/bpf/verifier.c\n+++ b/drivers/net/ethernet/netronome/nfp/bpf/verifier.c\n@@ -3,6 +3,7 @@\n \n #include \u003clinux/bpf.h\u003e\n #include \u003clinux/bpf_verifier.h\u003e\n+#include \u003clinux/filter.h\u003e\n #include \u003clinux/kernel.h\u003e\n #include \u003clinux/netdevice.h\u003e\n #include \u003clinux/pkt_cls.h\u003e\n@@ -561,6 +562,11 @@ nfp_bpf_check_alu(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta,\n \tconst struct bpf_reg_state *dreg =\n \t\tcur_regs(env) + meta-\u003einsn.dst_reg;\n \n+\tif (bpf_insn_is_hmul(\u0026meta-\u003einsn)) {\n+\t\tpr_vlog(env, \"UHMUL/SHMUL are not supported\\n\");\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n \tmeta-\u003eumin_src = min(meta-\u003eumin_src, reg_umin(sreg));\n \tmeta-\u003eumax_src = max(meta-\u003eumax_src, reg_umax(sreg));\n \tmeta-\u003eumin_dst = min(meta-\u003eumin_dst, reg_umin(dreg));\ndiff --git a/include/linux/filter.h b/include/linux/filter.h\nindex e0218e677a1dc..fa8a1d18691d6 100644\n--- a/include/linux/filter.h\n+++ b/include/linux/filter.h\n@@ -23,6 +23,7 @@\n #include \u003clinux/sockptr.h\u003e\n #include \u003clinux/static_call.h\u003e\n #include \u003clinux/u64_stats_sync.h\u003e\n+#include \u003clinux/math64.h\u003e\n \n #include \u003cnet/sch_generic.h\u003e\n \n@@ -152,6 +153,43 @@ struct ctl_table_header;\n #define BPF_ALU32_IMM(OP, DST, IMM)\t\t\t\t\\\n \tBPF_ALU32_IMM_OFF(OP, DST, IMM, 0)\n \n+static inline u64 bpf_uhmul64(u64 a, u64 b)\n+{\n+\treturn mul_u64_u64_shr(a, b, 64);\n+}\n+\n+static inline u64 bpf_shmul64(s64 a, s64 b)\n+{\n+\tu64 ua = a, ub = b;\n+\tu64 hi = bpf_uhmul64(ua, ub);\n+\n+\t/*\n+\t * Let M = 2^64, sa = (a \u003c 0), and sb = (b \u003c 0). Then:\n+\t *\n+\t * a * b = (ua - sa * M) * (ub - sb * M)\n+\t *       = ua * ub - sa * M * ub - sb * M * ua + sa * sb * M^2\n+\t *\n+\t * Therefore, signed_hi = unsigned_hi - sa * ub - sb * ua (mod M).\n+\t */\n+\tif (a \u003c 0)\n+\t\thi -= ub;\n+\tif (b \u003c 0)\n+\t\thi -= ua;\n+\treturn hi;\n+}\n+\n+static inline bool bpf_is_hmul_variant(s16 off)\n+{\n+\treturn off == BPF_UHMUL || off == BPF_SHMUL;\n+}\n+\n+static inline bool bpf_insn_is_hmul(const struct bpf_insn *insn)\n+{\n+\treturn BPF_CLASS(insn-\u003ecode) == BPF_ALU64 \u0026\u0026\n+\t       BPF_OP(insn-\u003ecode) == BPF_MUL \u0026\u0026\n+\t       bpf_is_hmul_variant(insn-\u003eoff);\n+}\n+\n /* Endianess conversion, cpu_to_{l,b}e(), {l,b}e_to_cpu() */\n \n #define BPF_ENDIAN(TYPE, DST, LEN)\t\t\t\t\\\ndiff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h\nindex f29b0aad440cb..90c30e5d790b9 100644\n--- a/include/uapi/linux/bpf.h\n+++ b/include/uapi/linux/bpf.h\n@@ -34,6 +34,12 @@\n #define BPF_FROM_LE\tBPF_TO_LE\n #define BPF_FROM_BE\tBPF_TO_BE\n \n+/* BPF_MUL off values for high-half multiplication; BPF_ALU64 only. */\n+enum bpf_mul_variant {\n+\tBPF_UHMUL = 1,\n+\tBPF_SHMUL = 2,\n+};\n+\n /* jmp encodings */\n #define BPF_JNE\t\t0x50\t/* jump != */\n #define BPF_JLT\t\t0xa0\t/* LT is unsigned, '\u003c' */\ndiff --git a/kernel/bpf/core.c b/kernel/bpf/core.c\nindex 3030beb531d99..f4f8cfa80033a 100644\n--- a/kernel/bpf/core.c\n+++ b/kernel/bpf/core.c\n@@ -1943,7 +1943,46 @@ static u64 ___bpf_prog_run(u64 *regs, const struct bpf_insn *insn)\n \tALU(AND,  \u0026)\n \tALU(OR,   |)\n \tALU(XOR,  ^)\n-\tALU(MUL,  *)\n+\t/*\n+\t * MUL needs explicit handlers because ALU64 insn-\u003eoff selects\n+\t * low-half, unsigned high-half, or signed high-half multiplication.\n+\t */\n+\tALU64_MUL_X:\n+\t\tswitch (OFF) {\n+\t\tcase 0:\n+\t\t\tDST = DST * SRC;\n+\t\t\tbreak;\n+\t\tcase BPF_UHMUL:\n+\t\t\tDST = bpf_uhmul64(DST, SRC);\n+\t\t\tbreak;\n+\t\tcase BPF_SHMUL:\n+\t\t\tDST = bpf_shmul64((s64)DST, (s64)SRC);\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tgoto default_label;\n+\t\t}\n+\t\tCONT;\n+\tALU_MUL_X:\n+\t\tDST = (u32) DST * (u32) SRC;\n+\t\tCONT;\n+\tALU64_MUL_K:\n+\t\tswitch (OFF) {\n+\t\tcase 0:\n+\t\t\tDST = DST * IMM;\n+\t\t\tbreak;\n+\t\tcase BPF_UHMUL:\n+\t\t\tDST = bpf_uhmul64(DST, (u64)IMM);\n+\t\t\tbreak;\n+\t\tcase BPF_SHMUL:\n+\t\t\tDST = bpf_shmul64((s64)DST, (s32)IMM);\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tgoto default_label;\n+\t\t}\n+\t\tCONT;\n+\tALU_MUL_K:\n+\t\tDST = (u32) DST * (u32) IMM;\n+\t\tCONT;\n \tSHT(LSH, \u003c\u003c)\n \tSHT(RSH, \u003e\u003e)\n #undef SHT\ndiff --git a/kernel/bpf/disasm.c b/kernel/bpf/disasm.c\nindex 889fc074496f1..0b1bbb27e9e4e 100644\n--- a/kernel/bpf/disasm.c\n+++ b/kernel/bpf/disasm.c\n@@ -163,6 +163,22 @@ static bool is_sdiv_smod(const struct bpf_insn *insn)\n \t       insn-\u003eoff == 1;\n }\n \n+/* Return the UHMUL or SHMUL operator string, or NULL for all other encodings. */\n+static const char *hmul_op_string(const struct bpf_insn *insn)\n+{\n+\tif (BPF_CLASS(insn-\u003ecode) != BPF_ALU64 ||\n+\t    BPF_OP(insn-\u003ecode) != BPF_MUL)\n+\t\treturn NULL;\n+\tswitch (insn-\u003eoff) {\n+\tcase BPF_UHMUL:\n+\t\treturn \"uh*=\";\n+\tcase BPF_SHMUL:\n+\t\treturn \"sh*=\";\n+\tdefault:\n+\t\treturn NULL;\n+\t}\n+}\n+\n static bool is_movsx(const struct bpf_insn *insn)\n {\n \treturn BPF_OP(insn-\u003ecode) == BPF_MOV \u0026\u0026\n@@ -215,6 +231,7 @@ void print_bpf_insn(const struct bpf_insn_cbs *cbs,\n \t\t\tverbose(cbs-\u003eprivate_data, \"(%02x) %c%d %s %s%c%d\",\n \t\t\t\tinsn-\u003ecode, class == BPF_ALU ? 'w' : 'r',\n \t\t\t\tinsn-\u003edst_reg,\n+\t\t\t\thmul_op_string(insn) ?:\n \t\t\t\tis_sdiv_smod(insn) ? bpf_alu_sign_string[BPF_OP(insn-\u003ecode) \u003e\u003e 4]\n \t\t\t\t\t\t   : bpf_alu_string[BPF_OP(insn-\u003ecode) \u003e\u003e 4],\n \t\t\t\tis_movsx(insn) ? bpf_movsx_string[(insn-\u003eoff \u003e\u003e 3) - 1] : \"\",\n@@ -224,6 +241,7 @@ void print_bpf_insn(const struct bpf_insn_cbs *cbs,\n \t\t\tverbose(cbs-\u003eprivate_data, \"(%02x) %c%d %s %d\",\n \t\t\t\tinsn-\u003ecode, class == BPF_ALU ? 'w' : 'r',\n \t\t\t\tinsn-\u003edst_reg,\n+\t\t\t\thmul_op_string(insn) ?:\n \t\t\t\tis_sdiv_smod(insn) ? bpf_alu_sign_string[BPF_OP(insn-\u003ecode) \u003e\u003e 4]\n \t\t\t\t\t\t   : bpf_alu_string[BPF_OP(insn-\u003ecode) \u003e\u003e 4],\n \t\t\t\tinsn-\u003eimm);\ndiff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c\nindex 5601d20562186..fbb9202397832 100644\n--- a/kernel/bpf/verifier.c\n+++ b/kernel/bpf/verifier.c\n@@ -16664,6 +16664,24 @@ static void scalar_min_max_mul(struct bpf_reg_state *dst_reg,\n \t\t\t\t\tcnum64_from_srange(smin, smax));\n }\n \n+static void scalar_min_max_uhmul(struct bpf_reg_state *dst_reg,\n+\t\t\t\t struct bpf_reg_state *src_reg)\n+{\n+\tu64 dst = reg_const_value(dst_reg, false);\n+\tu64 src = reg_const_value(src_reg, false);\n+\n+\t___mark_reg_known(dst_reg, bpf_uhmul64(dst, src));\n+}\n+\n+static void scalar_min_max_shmul(struct bpf_reg_state *dst_reg,\n+\t\t\t\t struct bpf_reg_state *src_reg)\n+{\n+\ts64 dst = reg_const_value(dst_reg, false);\n+\ts64 src = reg_const_value(src_reg, false);\n+\n+\t___mark_reg_known(dst_reg, bpf_shmul64(dst, src));\n+}\n+\n static void scalar32_min_max_udiv(struct bpf_reg_state *dst_reg,\n \t\t\t\t  struct bpf_reg_state *src_reg)\n {\n@@ -17364,6 +17382,25 @@ static int adjust_scalar_min_max_vals(struct bpf_verifier_env *env,\n \t\treg_step_reset(dst_reg);\n \t\tbreak;\n \tcase BPF_MUL:\n+\t\t/*\n+\t\t * Range analysis for UHMUL/SHMUL is not implemented yet.\n+\t\t * Compute an exact result only when both operands are constants.\n+\t\t * Ordinary low-half MUL continues to use the analysis below.\n+\t\t */\n+\t\tif (bpf_insn_is_hmul(insn) \u0026\u0026\n+\t\t    (!is_reg_const(dst_reg, false) ||\n+\t\t     !is_reg_const(\u0026src_reg, false))) {\n+\t\t\t__mark_reg_unknown(env, dst_reg);\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (!alu32 \u0026\u0026 insn-\u003eoff == BPF_UHMUL) {\n+\t\t\tscalar_min_max_uhmul(dst_reg, \u0026src_reg);\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (!alu32 \u0026\u0026 insn-\u003eoff == BPF_SHMUL) {\n+\t\t\tscalar_min_max_shmul(dst_reg, \u0026src_reg);\n+\t\t\tbreak;\n+\t\t}\n \t\tscalar_step_mul(dst_reg, \u0026src_reg);\n \t\tdst_reg-\u003evar_off = tnum_mul(dst_reg-\u003evar_off, src_reg.var_off);\n \t\tscalar32_min_max_mul(dst_reg, \u0026src_reg);\n@@ -17499,6 +17536,11 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,\n \t/* Case where at least one operand is an arena. */\n \tif (dst_reg-\u003etype == PTR_TO_ARENA || (src_reg \u0026\u0026 src_reg-\u003etype == PTR_TO_ARENA)) {\n \n+\t\tif (bpf_insn_is_hmul(insn)) {\n+\t\t\tverbose(env, \"UHMUL/SHMUL with arena pointers are not supported\\n\");\n+\t\t\treturn -EOPNOTSUPP;\n+\t\t}\n+\n \t\tif (dst_reg-\u003etype != PTR_TO_ARENA)\n \t\t\t*dst_reg = *src_reg;\n \n@@ -21194,6 +21236,20 @@ static int fd_array_get_map_idx(struct bpf_verifier_env *env, u32 idx)\n \treturn -EPROTO;\n }\n \n+static bool is_valid_alu_variant(const struct bpf_insn *insn)\n+{\n+\tswitch (BPF_OP(insn-\u003ecode)) {\n+\tcase BPF_MUL:\n+\t\treturn insn-\u003eoff == 0 || bpf_insn_is_hmul(insn);\n+\tcase BPF_DIV:\n+\tcase BPF_MOD:\n+\t\t/* off == 1 selects SDIV/SMOD. */\n+\t\treturn insn-\u003eoff == 0 || insn-\u003eoff == 1;\n+\tdefault:\n+\t\treturn insn-\u003eoff == 0;\n+\t}\n+}\n+\n static int check_alu_fields(struct bpf_verifier_env *env, struct bpf_insn *insn)\n {\n \tu8 class = BPF_CLASS(insn-\u003ecode);\n@@ -21249,15 +21305,9 @@ static int check_alu_fields(struct bpf_verifier_env *env, struct bpf_insn *insn)\n \tcase BPF_MUL:\n \tcase BPF_DIV:\n \tcase BPF_MOD:\n-\t\tif (BPF_SRC(insn-\u003ecode) == BPF_X) {\n-\t\t\tif (insn-\u003eimm != 0 || (insn-\u003eoff != 0 \u0026\u0026 insn-\u003eoff != 1) ||\n-\t\t\t    (insn-\u003eoff == 1 \u0026\u0026 opcode != BPF_MOD \u0026\u0026 opcode != BPF_DIV)) {\n-\t\t\t\tverbose(env, \"BPF_ALU uses reserved fields\\n\");\n-\t\t\t\treturn -EINVAL;\n-\t\t\t}\n-\t\t} else if (insn-\u003esrc_reg != BPF_REG_0 ||\n-\t\t\t   (insn-\u003eoff != 0 \u0026\u0026 insn-\u003eoff != 1) ||\n-\t\t\t   (insn-\u003eoff == 1 \u0026\u0026 opcode != BPF_MOD \u0026\u0026 opcode != BPF_DIV)) {\n+\t\tif (!is_valid_alu_variant(insn) ||\n+\t\t    (BPF_SRC(insn-\u003ecode) == BPF_X \u0026\u0026 insn-\u003eimm != 0) ||\n+\t\t    (BPF_SRC(insn-\u003ecode) == BPF_K \u0026\u0026 insn-\u003esrc_reg != BPF_REG_0)) {\n \t\t\tverbose(env, \"BPF_ALU uses reserved fields\\n\");\n \t\t\treturn -EINVAL;\n \t\t}\ndiff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h\nindex f29b0aad440cb..90c30e5d790b9 100644\n--- a/tools/include/uapi/linux/bpf.h\n+++ b/tools/include/uapi/linux/bpf.h\n@@ -34,6 +34,12 @@\n #define BPF_FROM_LE\tBPF_TO_LE\n #define BPF_FROM_BE\tBPF_TO_BE\n \n+/* BPF_MUL off values for high-half multiplication; BPF_ALU64 only. */\n+enum bpf_mul_variant {\n+\tBPF_UHMUL = 1,\n+\tBPF_SHMUL = 2,\n+};\n+\n /* jmp encodings */\n #define BPF_JNE\t\t0x50\t/* jump != */\n #define BPF_JLT\t\t0xa0\t/* LT is unsigned, '\u003c' */\ndiff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c\nindex 04740ba37e918..795c3e56c23e0 100644\n--- a/tools/testing/selftests/bpf/prog_tests/verifier.c\n+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c\n@@ -59,6 +59,7 @@\n #include \"verifier_helper_packet_access.skel.h\"\n #include \"verifier_helper_restricted.skel.h\"\n #include \"verifier_helper_value_access.skel.h\"\n+#include \"verifier_hmul.skel.h\"\n #include \"verifier_idoms.skel.h\"\n #include \"verifier_int_ptr.skel.h\"\n #include \"verifier_iterating_callbacks.skel.h\"\n@@ -251,6 +252,7 @@ void test_verifier_helper_access_var_len(void) { RUN(verifier_helper_access_var_\n void test_verifier_helper_packet_access(void) { RUN(verifier_helper_packet_access); }\n void test_verifier_helper_restricted(void)    { RUN(verifier_helper_restricted); }\n void test_verifier_helper_value_access(void)  { RUN(verifier_helper_value_access); }\n+void test_verifier_hmul(void)                 { RUN(verifier_hmul); }\n void test_verifier_idoms(void)                { RUN(verifier_idoms); }\n void test_verifier_int_ptr(void)              { RUN(verifier_int_ptr); }\n void test_verifier_iterating_callbacks(void)  { RUN(verifier_iterating_callbacks); }\ndiff --git a/tools/testing/selftests/bpf/prog_tests/verifier_hmul_arena.c b/tools/testing/selftests/bpf/prog_tests/verifier_hmul_arena.c\nnew file mode 100644\nindex 0000000000000..f9cd7b7b9781a\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/prog_tests/verifier_hmul_arena.c\n@@ -0,0 +1,19 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+#include \u003ctest_progs.h\u003e\n+#include \"verifier_hmul_arena.skel.h\"\n+\n+void test_verifier_hmul_arena(void)\n+{\n+\tint ret;\n+\n+\t/* Arena support can depend on CPU features, such as Zacas on RV64. */\n+\tret = libbpf_probe_bpf_map_type(BPF_MAP_TYPE_ARENA, NULL);\n+\tif (!ASSERT_GE(ret, 0, \"probe_arena\"))\n+\t\treturn;\n+\tif (!ret) {\n+\t\ttest__skip();\n+\t\treturn;\n+\t}\n+\tRUN_TESTS(verifier_hmul_arena);\n+}\ndiff --git a/tools/testing/selftests/bpf/progs/verifier_hmul.c b/tools/testing/selftests/bpf/progs/verifier_hmul.c\nnew file mode 100644\nindex 0000000000000..acd92355c6365\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/verifier_hmul.c\n@@ -0,0 +1,235 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+#include \u003clinux/bpf.h\u003e\n+#include \u003climits.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \"bpf_misc.h\"\n+#include \"../../../include/linux/filter.h\"\n+\n+#define BPF_UHMUL64_REG(DST, SRC) \\\n+\tBPF_RAW_INSN(BPF_ALU64 | BPF_MUL | BPF_X, DST, SRC, BPF_UHMUL, 0)\n+\n+#define BPF_UHMUL64_IMM(DST, IMM) \\\n+\tBPF_RAW_INSN(BPF_ALU64 | BPF_MUL | BPF_K, DST, 0, BPF_UHMUL, IMM)\n+\n+#define BPF_SHMUL64_REG(DST, SRC) \\\n+\tBPF_RAW_INSN(BPF_ALU64 | BPF_MUL | BPF_X, DST, SRC, BPF_SHMUL, 0)\n+\n+#define BPF_SHMUL64_IMM(DST, IMM) \\\n+\tBPF_RAW_INSN(BPF_ALU64 | BPF_MUL | BPF_K, DST, 0, BPF_SHMUL, IMM)\n+\n+/*\n+ * Inject UHMUL/SHMUL as raw instructions so these tests do not require\n+ * assembler support for the new mnemonics. All surrounding instructions use\n+ * existing BPF assembly syntax supported by the baseline selftests toolchain.\n+ */\n+\n+#if defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64) || \\\n+\t(defined(__TARGET_ARCH_riscv) \u0026\u0026 __riscv_xlen == 64)\n+\n+/*\n+ * BPF_PROG_TEST_RUN reports a 32-bit retval. Fold both halves of\n+ * (actual ^ expected) into w0 so retval == 0 checks the full 64-bit result.\n+ */\n+\n+SEC(\"socket\")\n+__description(\"UHMUL64, U64_MAX * U64_MAX, register source\")\n+__success __success_unpriv __retval(0)\n+__naked void uhmul64_max_reg(void)\n+{\n+\tasm volatile (\n+\t\"r0 = -1;\"\n+\t\"r1 = -1;\"\n+\t\".8byte %[hmul];\"\n+\t\"r2 = -2;\"\n+\t\"r0 ^= r2;\"\n+\t\"r2 = r0;\"\n+\t\"r2 \u003e\u003e= 32;\"\n+\t\"w0 |= w2;\"\n+\t\"exit;\"\n+\t:\n+\t: __imm_insn(hmul, BPF_UHMUL64_REG(BPF_REG_0, BPF_REG_1))\n+\t: __clobber_all);\n+}\n+\n+SEC(\"socket\")\n+__description(\"UHMUL64, (1ULL \u003c\u003c 63) * 2, register source\")\n+__success __success_unpriv __retval(0)\n+__naked void uhmul64_pow2_reg(void)\n+{\n+\tasm volatile (\n+\t\"r0 = 2;\"\n+\t\"r1 = %[llong_min] ll;\"\n+\t\".8byte %[hmul];\"\n+\t\"r0 = r1;\"\n+\t\"r2 = 1;\"\n+\t\"r0 ^= r2;\"\n+\t\"r2 = r0;\"\n+\t\"r2 \u003e\u003e= 32;\"\n+\t\"w0 |= w2;\"\n+\t\"exit;\"\n+\t:\n+\t: __imm_const(llong_min, LLONG_MIN),\n+\t  __imm_insn(hmul, BPF_UHMUL64_REG(BPF_REG_1, BPF_REG_0))\n+\t: __clobber_all);\n+}\n+\n+SEC(\"socket\")\n+__description(\"UHMUL64, (1ULL \u003c\u003c 63) * 2, R3 destination\")\n+__success __success_unpriv __retval(0)\n+__naked void uhmul64_r3_dst(void)\n+{\n+\tasm volatile (\n+\t\"r3 = %[llong_min] ll;\"\n+\t\"r0 = 2;\"\n+\t\".8byte %[hmul];\"\n+\t\"r2 = 1;\"\n+\t\"r3 ^= r2;\"\n+\t\"r2 = r3;\"\n+\t\"r2 \u003e\u003e= 32;\"\n+\t\"w3 |= w2;\"\n+\t\"r0 = r3;\"\n+\t\"exit;\"\n+\t:\n+\t: __imm_const(llong_min, LLONG_MIN),\n+\t  __imm_insn(hmul, BPF_UHMUL64_REG(BPF_REG_3, BPF_REG_0))\n+\t: __clobber_all);\n+}\n+\n+SEC(\"socket\")\n+__description(\"UHMUL64, (1ULL \u003c\u003c 63) * 2, immediate source\")\n+__success __success_unpriv __retval(0)\n+__naked void uhmul64_pow2_imm(void)\n+{\n+\tasm volatile (\n+\t\"r0 = %[llong_min] ll;\"\n+\t\".8byte %[hmul];\"\n+\t\"r2 = 1;\"\n+\t\"r0 ^= r2;\"\n+\t\"r2 = r0;\"\n+\t\"r2 \u003e\u003e= 32;\"\n+\t\"w0 |= w2;\"\n+\t\"exit;\"\n+\t:\n+\t: __imm_const(llong_min, LLONG_MIN),\n+\t  __imm_insn(hmul, BPF_UHMUL64_IMM(BPF_REG_0, 2))\n+\t: __clobber_all);\n+}\n+\n+SEC(\"socket\")\n+__description(\"UHMUL64, (1ULL \u003c\u003c 63) * -2, immediate source\")\n+__success __success_unpriv __retval(0)\n+__naked void uhmul64_neg_imm(void)\n+{\n+\tasm volatile (\n+\t\"r0 = %[llong_min] ll;\"\n+\t\".8byte %[hmul];\"\n+\t\"r2 = %[llong_max] ll;\"\n+\t\"r0 ^= r2;\"\n+\t\"r2 = r0;\"\n+\t\"r2 \u003e\u003e= 32;\"\n+\t\"w0 |= w2;\"\n+\t\"exit;\"\n+\t:\n+\t: __imm_const(llong_min, LLONG_MIN),\n+\t  __imm_const(llong_max, LLONG_MAX),\n+\t  __imm_insn(hmul, BPF_UHMUL64_IMM(BPF_REG_0, -2))\n+\t: __clobber_all);\n+}\n+\n+SEC(\"socket\")\n+__description(\"SHMUL64, INT64_MAX * INT64_MAX, register source\")\n+__success __success_unpriv __retval(0)\n+__naked void shmul64_max_reg(void)\n+{\n+\tasm volatile (\n+\t\"r0 = %[llong_max] ll;\"\n+\t\"r1 = %[llong_max] ll;\"\n+\t\".8byte %[hmul];\"\n+\t\"r2 = %[expected] ll;\"\n+\t\"r0 ^= r2;\"\n+\t\"r2 = r0;\"\n+\t\"r2 \u003e\u003e= 32;\"\n+\t\"w0 |= w2;\"\n+\t\"exit;\"\n+\t:\n+\t: __imm_const(expected, 0x3fffffffffffffffULL),\n+\t  __imm_const(llong_max, LLONG_MAX),\n+\t  __imm_insn(hmul, BPF_SHMUL64_REG(BPF_REG_0, BPF_REG_1))\n+\t: __clobber_all);\n+}\n+\n+SEC(\"socket\")\n+__description(\"SHMUL64, INT64_MIN * -2, register source\")\n+__success __success_unpriv __retval(0)\n+__naked void shmul64_min_neg2_reg(void)\n+{\n+\tasm volatile (\n+\t\"r0 = %[llong_min] ll;\"\n+\t\"r1 = -2;\"\n+\t\".8byte %[hmul];\"\n+\t\"r2 = 1;\"\n+\t\"r0 ^= r2;\"\n+\t\"r2 = r0;\"\n+\t\"r2 \u003e\u003e= 32;\"\n+\t\"w0 |= w2;\"\n+\t\"exit;\"\n+\t:\n+\t: __imm_const(llong_min, LLONG_MIN),\n+\t  __imm_insn(hmul, BPF_SHMUL64_REG(BPF_REG_0, BPF_REG_1))\n+\t: __clobber_all);\n+}\n+\n+SEC(\"socket\")\n+__description(\"SHMUL64, 1 * -2, immediate source\")\n+__success __success_unpriv __retval(0)\n+__naked void shmul64_neg_imm(void)\n+{\n+\tasm volatile (\n+\t\"r0 = 1;\"\n+\t\".8byte %[hmul];\"\n+\t\"r2 = -1;\"\n+\t\"r0 ^= r2;\"\n+\t\"r2 = r0;\"\n+\t\"r2 \u003e\u003e= 32;\"\n+\t\"w0 |= w2;\"\n+\t\"exit;\"\n+\t:\n+\t: __imm_insn(hmul, BPF_SHMUL64_IMM(BPF_REG_0, -2))\n+\t: __clobber_all);\n+}\n+\n+SEC(\"socket\")\n+__description(\"SHMUL64, INT64_MIN * 2, immediate source\")\n+__success __success_unpriv __retval(0)\n+__naked void shmul64_min_imm(void)\n+{\n+\tasm volatile (\n+\t\"r0 = %[llong_min] ll;\"\n+\t\".8byte %[hmul];\"\n+\t\"r2 = -1;\"\n+\t\"r0 ^= r2;\"\n+\t\"r2 = r0;\"\n+\t\"r2 \u003e\u003e= 32;\"\n+\t\"w0 |= w2;\"\n+\t\"exit;\"\n+\t:\n+\t: __imm_const(llong_min, LLONG_MIN),\n+\t  __imm_insn(hmul, BPF_SHMUL64_IMM(BPF_REG_0, 2))\n+\t: __clobber_all);\n+}\n+\n+#else\n+\n+SEC(\"socket\")\n+__description(\"UHMUL/SHMUL are not supported by this JIT, use a dummy test\")\n+__skip(\"UHMUL/SHMUL are not supported by this jit\")\n+__success\n+int dummy_test(void)\n+{\n+\treturn 0;\n+}\n+\n+#endif\n+\n+char _license[] SEC(\"license\") = \"GPL\";\ndiff --git a/tools/testing/selftests/bpf/progs/verifier_hmul_arena.c b/tools/testing/selftests/bpf/progs/verifier_hmul_arena.c\nnew file mode 100644\nindex 0000000000000..fc0ca88114d08\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/verifier_hmul_arena.c\n@@ -0,0 +1,70 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+#include \u003clinux/bpf.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \"bpf_misc.h\"\n+#include \"../../../include/linux/filter.h\"\n+\n+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)\n+\n+#define BPF_UHMUL64_IMM(DST, IMM) \\\n+\tBPF_RAW_INSN(BPF_ALU64 | BPF_MUL | BPF_K, DST, 0, BPF_UHMUL, IMM)\n+\n+#define BPF_SHMUL64_REG(DST, SRC) \\\n+\tBPF_RAW_INSN(BPF_ALU64 | BPF_MUL | BPF_X, DST, SRC, BPF_SHMUL, 0)\n+\n+struct {\n+\t__uint(type, BPF_MAP_TYPE_ARENA);\n+\t__uint(map_flags, BPF_F_MMAPABLE);\n+\t__uint(max_entries, 1);\n+} arena SEC(\".maps\");\n+\n+SEC(\"syscall\")\n+__description(\"UHMUL64 with arena pointer dst\")\n+__failure __msg(\"UHMUL/SHMUL with arena pointers are not supported\")\n+__naked void uhmul64_arena_ptr(void)\n+{\n+\tasm volatile (\n+\t\"r1 = %[arena] ll;\"\n+\t\"r0 = 1;\"\n+\t\"r0 = addr_space_cast(r0, 0x0, 0x1);\"\n+\t\".8byte %[hmul];\"\n+\t\"exit;\"\n+\t:\n+\t: __imm_addr(arena),\n+\t  __imm_insn(hmul, BPF_UHMUL64_IMM(BPF_REG_0, 2))\n+\t: __clobber_all);\n+}\n+\n+SEC(\"syscall\")\n+__description(\"SHMUL64 with arena pointer src\")\n+__failure __msg(\"UHMUL/SHMUL with arena pointers are not supported\")\n+__naked void shmul64_arena_ptr(void)\n+{\n+\tasm volatile (\n+\t\"r1 = %[arena] ll;\"\n+\t\"r1 = 1;\"\n+\t\"r1 = addr_space_cast(r1, 0x0, 0x1);\"\n+\t\"r0 = 2;\"\n+\t\".8byte %[hmul];\"\n+\t\"exit;\"\n+\t:\n+\t: __imm_addr(arena),\n+\t  __imm_insn(hmul, BPF_SHMUL64_REG(BPF_REG_0, BPF_REG_1))\n+\t: __clobber_all);\n+}\n+\n+#else\n+\n+SEC(\"socket\")\n+__description(\"HMUL arena tests require compiler support for addr_space_cast\")\n+__skip(\"compiler doesn't support addr_space_cast\")\n+__success\n+int dummy_test(void)\n+{\n+\treturn 0;\n+}\n+\n+#endif\n+\n+char _license[] SEC(\"license\") = \"GPL\";\ndiff --git a/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c b/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c\nindex 31663338866d3..209d5be551555 100644\n--- a/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c\n+++ b/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c\n@@ -367,7 +367,8 @@ __naked void flow_keys_illegal_variable_offset_alu(void)\n \n /*\n  * Offset fields of 0 and 1 are legal for BPF_{DIV,MOD} instructions.\n- * Offset fields of 0 are legal for the rest of ALU instructions.\n+ * Offset fields of 0, 1 and 2 are legal for BPF_ALU64 | BPF_MUL.\n+ * Only offset 0 is legal for BPF_ALU | BPF_MUL.\n  * Test that error is reported for illegal offsets, assuming that tests\n  * for legal offsets exist.\n  */\n@@ -377,5 +378,9 @@ DEFINE_BAD_OFFSET_TEST(bad_offset_addx, BPF_ALU64 | BPF_ADD | BPF_X, -1, 0)\n DEFINE_BAD_OFFSET_TEST(bad_offset_divx2, BPF_ALU64 | BPF_DIV | BPF_X, 2, 0)\n DEFINE_BAD_OFFSET_TEST(bad_offset_modk2, BPF_ALU64 | BPF_MOD | BPF_K, 2, 1)\n DEFINE_BAD_OFFSET_TEST(bad_offset_addx2, BPF_ALU64 | BPF_ADD | BPF_X, 1, 0)\n+DEFINE_BAD_OFFSET_TEST(bad_offset_mulx, BPF_ALU64 | BPF_MUL | BPF_X, -1, 0)\n+DEFINE_BAD_OFFSET_TEST(bad_offset_mulk, BPF_ALU64 | BPF_MUL | BPF_K, 3, 0)\n+DEFINE_BAD_OFFSET_TEST(bad_uhmul32_x, BPF_ALU | BPF_MUL | BPF_X, BPF_UHMUL, 0)\n+DEFINE_BAD_OFFSET_TEST(bad_shmul32_k, BPF_ALU | BPF_MUL | BPF_K, BPF_SHMUL, 0)\n \n char _license[] SEC(\"license\") = \"GPL\";\n"
}

2/1 2026/10/22 03:52 agent patch-evaluator
1m Model:
core

Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "BPF_JIT"
  ],
  "FocusSymbols": [
    "emit_hmul_aux",
    "adjust_scalar_min_max_vals",
    "scalar_min_max_uhmul",
    "scalar_min_max_shmul",
    "check_alu_fields"
  ],
  "Reasoning": "The patch introduces two new 64-bit BPF ALU multiplication instructions (UHMUL for unsigned high-half multiplication and SHMUL for signed high-half multiplication). The changes implement verifier validation, scalar range tracking, x86_64 JIT compilation, and interpreter support. These are core BPF subsystem components directly reachable from userspace via the bpf() syscall and warrant fuzzing for verifier correctness, range tracking bugs, and JIT code generation issues.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.

================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.

- REACHABILITY IS THE PRIMARY GATE:
  Fuzzing can only discover bugs in code that can actually execute in standard virtualized
  environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
  If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
  regardless of whether it adds assertions or complex logic.

- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
  Patch authors routinely label changes as "cleanups", "refactorings", or state
  "No functional change intended". Do NOT take these claims at face value.
  Code refactorings that rearrange logic, introduce helper functions, or alter state management
  in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
  If reachable executable code is modified or refactored, it MUST be fuzzed.

- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
  When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
  BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
  Even if the author believes the invariant always holds, fuzzing is essential to verify whether
  an unusual sequence of operations can violate it.

================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:

- Non-kernel and non-executable changes:
  * Modifications to Documentation/, comments, or spelling fixes.
  * User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
    that do not affect the compiled kernel image (vmlinux) or kernel modules.
  * Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
    that do not alter control flow or data structures.
  * Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
  * Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
    ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
  * Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
    physical PCI hot-unplug or manual sysfs driver unbinding.
  * Code paths exclusive to architectures other than the target architecture.

================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
  * Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
    BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
  * Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
  * Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
  * Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).

================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:

- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
  Do NOT list generic, ubiquitous functions called by almost every program in the corpus
  (including, but not limited to: general memory allocators and deallocators, page fault
  and trap handlers, or core synchronization primitives; this is not an exhaustive list).
  Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
  which severely dilutes fuzzing effort away from the actual changes.

- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
  List functions that specifically implement the logic being added or altered, or direct API entrypoints
  for the subsystem feature under review.

- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
  Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
  distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
  If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
  functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).

================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit b4427bda51c386dff85db43d5a27629e6188444f
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Oct 22 03:52:31 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/bpf/standardization/instruction-set.rst b/Documentation/bpf/standardization/instruction-set.rst
index 39c74611752b6..6f3cb6bcaa1a6 100644
--- a/Documentation/bpf/standardization/instruction-set.rst
+++ b/Documentation/bpf/standardization/instruction-set.rst
@@ -142,8 +142,9 @@ This document defines the following conformance groups:
 * atomic32: includes 32-bit atomic operation instructions (see `Atomic operations`_).
 * atomic64: includes atomic32, plus 64-bit atomic operation instructions.
 * divmul32: includes 32-bit division, multiplication, and modulo instructions.
-* divmul64: includes divmul32, plus 64-bit division, multiplication,
+* divmul64: includes divmul32, plus 64-bit division, low-half multiplication,
   and modulo instructions.
+* divmul64v2: includes divmul64, plus high-half multiplication instructions.
 * packet: deprecated packet access instructions.
 
 Instruction encoding
@@ -330,6 +331,8 @@ register.
   ADD    0x0    0        dst += src
   SUB    0x1    0        dst -= src
   MUL    0x2    0        dst \*= src
+  UHMUL  0x2    1        ``dst uh*= src`` (unsigned high half, ``ALU64`` only)
+  SHMUL  0x2    2        ``dst sh*= src`` (signed high half, ``ALU64`` only)
   DIV    0x3    0        dst = (src != 0) ? (dst / src) : 0
   SDIV   0x3    1        dst = (src == 0) ? 0 : ((src == -1 && dst == LLONG_MIN) ? LLONG_MIN : (dst s/ src))
   OR     0x4    0        dst \|= src
@@ -379,13 +382,33 @@ where '(u32)' indicates that the upper 32 bits are zeroed.
 
   dst = dst ^ imm
 
-Note that most arithmetic instructions have 'offset' set to 0. Only three instructions
-(``SDIV``, ``SMOD``, ``MOVSX``) have a non-zero 'offset'.
+Note that most arithmetic instructions have 'offset' set to 0. The following
+instructions may have a non-zero 'offset':
+
+* ``SDIV`` and ``SMOD``: ``offset == 1`` selects the signed variant.
+* ``MOVSX``: ``offset`` selects the source operand width.
+* ``MUL``: ``offset`` selects the multiplication variant (see below).
+
+Division, low-half multiplication (``MUL`` with ``offset == 0``), and
+modulo operations for ``ALU`` are part of the "divmul32" conformance
+group, and their ``ALU64`` variants are part of the "divmul64"
+conformance group.
+
+For ``ALU64`` multiplication, ``offset == 1`` selects unsigned
+high-half multiplication (``UHMUL``), and ``offset == 2`` selects
+signed high-half multiplication (``SHMUL``). Both instructions belong
+to the "divmul64v2" conformance group.
+
+For ``X`` mode, ``src64`` is the 64-bit value in ``src_reg``. For
+``K`` mode, ``src64`` is ``imm`` sign-extended from 32 to 64 bits.
+The operations are::
+
+  UHMUL: dst = (u64)(((u128)dst * (u128)(u64)src64) >> 64)
+  SHMUL: dst = (u64)(((s128)(s64)dst * (s128)(s64)src64) >> 64)
+
+For ``ALU`` multiplication, only ``offset == 0`` is valid. For ``ALU64``
+multiplication, offset values other than 0, 1, and 2 are reserved.
 
-Division, multiplication, and modulo operations for ``ALU`` are part
-of the "divmul32" conformance group, and division, multiplication, and
-modulo operations for ``ALU64`` are part of the "divmul64" conformance
-group.
 The division and modulo operations support both unsigned and signed flavors.
 
 For unsigned operations (``DIV`` and ``MOD``), for ``ALU``,
diff --git a/arch/arc/net/bpf_jit_core.c b/arch/arc/net/bpf_jit_core.c
index 639a2736f029e..78b1819cee5a6 100644
--- a/arch/arc/net/bpf_jit_core.c
+++ b/arch/arc/net/bpf_jit_core.c
@@ -734,6 +734,9 @@ static int handle_insn(struct jit_context *ctx, u32 idx)
 	u8  len = 0;
 	int ret = 0;
 
+	if (bpf_insn_is_hmul(insn))
+		return -EOPNOTSUPP;
+
 	switch (code) {
 	/* dst += src (32-bit) */
 	case BPF_ALU | BPF_ADD | BPF_X:
diff --git a/arch/arm/net/bpf_jit_32.c b/arch/arm/net/bpf_jit_32.c
index 9ede81afbc50e..aa466fc79d964 100644
--- a/arch/arm/net/bpf_jit_32.c
+++ b/arch/arm/net/bpf_jit_32.c
@@ -1611,6 +1611,9 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx)
 } while (0)
 #define check_imm24(imm) check_imm(24, imm)
 
+	if (bpf_insn_is_hmul(insn))
+		goto notyet;
+
 	switch (code) {
 	/* ALU operations */
 
diff --git a/arch/arm64/include/asm/insn.h b/arch/arm64/include/asm/insn.h
index 1ce75a4426387..f38738fa54a27 100644
--- a/arch/arm64/include/asm/insn.h
+++ b/arch/arm64/include/asm/insn.h
@@ -247,6 +247,8 @@ enum aarch64_insn_data2_type {
 enum aarch64_insn_data3_type {
 	AARCH64_INSN_DATA3_MADD,
 	AARCH64_INSN_DATA3_MSUB,
+	AARCH64_INSN_DATA3_SMULH,
+	AARCH64_INSN_DATA3_UMULH,
 };
 
 enum aarch64_insn_logic_type {
@@ -396,6 +398,8 @@ __AARCH64_INSN_FUNCS(sub_ext,	0x7FE00000, 0x4B200000)
 __AARCH64_INSN_FUNCS(subs_ext,	0x7FE00000, 0x6B200000)
 __AARCH64_INSN_FUNCS(madd,	0x7FE08000, 0x1B000000)
 __AARCH64_INSN_FUNCS(msub,	0x7FE08000, 0x1B008000)
+__AARCH64_INSN_FUNCS(smulh,	0x7FE0FC00, 0x1B407C00)
+__AARCH64_INSN_FUNCS(umulh,	0x7FE0FC00, 0x1BC07C00)
 __AARCH64_INSN_FUNCS(udiv,	0x7FE0FC00, 0x1AC00800)
 __AARCH64_INSN_FUNCS(sdiv,	0x7FE0FC00, 0x1AC00C00)
 __AARCH64_INSN_FUNCS(lslv,	0x7FE0FC00, 0x1AC02000)
diff --git a/arch/arm64/lib/insn.c b/arch/arm64/lib/insn.c
index e70ac02385153..dc483d1b8bd54 100644
--- a/arch/arm64/lib/insn.c
+++ b/arch/arm64/lib/insn.c
@@ -1155,6 +1155,12 @@ u32 aarch64_insn_gen_data3(enum aarch64_insn_register dst,
 	case AARCH64_INSN_DATA3_MSUB:
 		insn = aarch64_insn_get_msub_value();
 		break;
+	case AARCH64_INSN_DATA3_SMULH:
+		insn = aarch64_insn_get_smulh_value();
+		break;
+	case AARCH64_INSN_DATA3_UMULH:
+		insn = aarch64_insn_get_umulh_value();
+		break;
 	default:
 		pr_err("%s: unknown data3 encoding %d\n", __func__, type);
 		return AARCH64_BREAK_FAULT;
diff --git a/arch/arm64/net/bpf_jit.h b/arch/arm64/net/bpf_jit.h
index b2fe6e6dcf447..70fc329c661db 100644
--- a/arch/arm64/net/bpf_jit.h
+++ b/arch/arm64/net/bpf_jit.h
@@ -281,6 +281,12 @@
 	A64_VARIANT(sf), AARCH64_INSN_DATA3_MSUB)
 /* Rd = Rn * Rm */
 #define A64_MUL(sf, Rd, Rn, Rm) A64_MADD(sf, Rd, A64_ZR, Rn, Rm)
+/* Rd = high 64 bits of signed Rn * Rm */
+#define A64_SMULH(Rd, Rn, Rm) aarch64_insn_gen_data3(Rd, A64_ZR, Rn, Rm, \
+	AARCH64_INSN_VARIANT_64BIT, AARCH64_INSN_DATA3_SMULH)
+/* Rd = high 64 bits of unsigned Rn * Rm */
+#define A64_UMULH(Rd, Rn, Rm) aarch64_insn_gen_data3(Rd, A64_ZR, Rn, Rm, \
+	AARCH64_INSN_VARIANT_64BIT, AARCH64_INSN_DATA3_UMULH)
 
 /* Logical (shifted register) */
 #define A64_LOGIC_SREG(sf, Rd, Rn, Rm, type) \
diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c
index 326e9e1dea0a7..a9989cd5098cc 100644
--- a/arch/arm64/net/bpf_jit_comp.c
+++ b/arch/arm64/net/bpf_jit_comp.c
@@ -144,6 +144,17 @@ static inline void emit_a64_mov_i(const int is64, const int reg,
 	}
 }
 
+static inline void emit_a64_mul(const bool is64, const u8 dst, const u8 src,
+				const s16 off, struct jit_ctx *ctx)
+{
+	if (is64 && off == BPF_UHMUL)
+		emit(A64_UMULH(dst, dst, src), ctx);
+	else if (is64 && off == BPF_SHMUL)
+		emit(A64_SMULH(dst, dst, src), ctx);
+	else
+		emit(A64_MUL(is64, dst, dst, src), ctx);
+}
+
 static int i64_i16_blocks(const u64 val, bool inverse)
 {
 	return (((val >>  0) & 0xffff) != (inverse ? 0xffff : 0x0000)) +
@@ -1531,7 +1542,7 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn
 		break;
 	case BPF_ALU | BPF_MUL | BPF_X:
 	case BPF_ALU64 | BPF_MUL | BPF_X:
-		emit(A64_MUL(is64, dst, dst, src), ctx);
+		emit_a64_mul(is64, dst, src, off, ctx);
 		break;
 	case BPF_ALU | BPF_DIV | BPF_X:
 	case BPF_ALU64 | BPF_DIV | BPF_X:
@@ -1660,7 +1671,7 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn
 	case BPF_ALU | BPF_MUL | BPF_K:
 	case BPF_ALU64 | BPF_MUL | BPF_K:
 		emit_a64_mov_i(is64, tmp, imm, ctx);
-		emit(A64_MUL(is64, dst, dst, tmp), ctx);
+		emit_a64_mul(is64, dst, tmp, off, ctx);
 		break;
 	case BPF_ALU | BPF_DIV | BPF_K:
 	case BPF_ALU64 | BPF_DIV | BPF_K:
diff --git a/arch/loongarch/net/bpf_jit.c b/arch/loongarch/net/bpf_jit.c
index 9393e4b0db8e1..71bd518ceaf32 100644
--- a/arch/loongarch/net/bpf_jit.c
+++ b/arch/loongarch/net/bpf_jit.c
@@ -709,6 +709,9 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext
 	const s32 imm = insn->imm;
 	const bool is32 = BPF_CLASS(insn->code) == BPF_ALU || BPF_CLASS(insn->code) == BPF_JMP32;
 
+	if (bpf_insn_is_hmul(insn))
+		return -EOPNOTSUPP;
+
 	switch (code) {
 	/* dst = src */
 	case BPF_ALU | BPF_MOV | BPF_X:
diff --git a/arch/mips/net/bpf_jit_comp32.c b/arch/mips/net/bpf_jit_comp32.c
index 84d9c7036e4f0..4a8bedddf61be 100644
--- a/arch/mips/net/bpf_jit_comp32.c
+++ b/arch/mips/net/bpf_jit_comp32.c
@@ -1544,6 +1544,9 @@ int build_insn(const struct bpf_insn *insn, struct jit_context *ctx)
 	s32 val, rel;
 	u8 alu, jmp;
 
+	if (bpf_insn_is_hmul(insn))
+		goto notyet;
+
 	switch (code) {
 	/* ALU operations */
 	/* dst = imm */
diff --git a/arch/mips/net/bpf_jit_comp64.c b/arch/mips/net/bpf_jit_comp64.c
index 994589c3de46c..75202cac31f47 100644
--- a/arch/mips/net/bpf_jit_comp64.c
+++ b/arch/mips/net/bpf_jit_comp64.c
@@ -719,6 +719,9 @@ int build_insn(const struct bpf_insn *insn, struct jit_context *ctx)
 	s32 val, rel;
 	u8 alu, jmp;
 
+	if (bpf_insn_is_hmul(insn))
+		goto notyet;
+
 	switch (code) {
 	/* ALU operations */
 	/* dst = imm */
diff --git a/arch/parisc/net/bpf_jit_comp32.c b/arch/parisc/net/bpf_jit_comp32.c
index eea0a34a3bfee..294eee24f2d9d 100644
--- a/arch/parisc/net/bpf_jit_comp32.c
+++ b/arch/parisc/net/bpf_jit_comp32.c
@@ -1142,6 +1142,9 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct hppa_jit_context *ctx,
 		BPF_CLASS(code), code, (code & BPF_ALU64) ? 1:0, BPF_SIZE(code),
 		BPF_OP(code), insn->src_reg, insn->dst_reg);
 
+	if (bpf_insn_is_hmul(insn))
+		return -EOPNOTSUPP;
+
 	switch (code) {
 	/* dst = src */
 	case BPF_ALU64 | BPF_MOV | BPF_X:
diff --git a/arch/parisc/net/bpf_jit_comp64.c b/arch/parisc/net/bpf_jit_comp64.c
index c326fa737ec8c..319bd28f1e5b6 100644
--- a/arch/parisc/net/bpf_jit_comp64.c
+++ b/arch/parisc/net/bpf_jit_comp64.c
@@ -610,6 +610,9 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct hppa_jit_context *ctx,
 
 	init_regs(&rd, &rs, insn, ctx);
 
+	if (bpf_insn_is_hmul(insn))
+		return -EOPNOTSUPP;
+
 	switch (code) {
 	/* dst = src */
 	case BPF_ALU | BPF_MOV | BPF_X:
diff --git a/arch/powerpc/net/bpf_jit_comp32.c b/arch/powerpc/net/bpf_jit_comp32.c
index bfdc50740da8e..6bfc97a029d73 100644
--- a/arch/powerpc/net/bpf_jit_comp32.c
+++ b/arch/powerpc/net/bpf_jit_comp32.c
@@ -349,6 +349,9 @@ int bpf_jit_build_body(struct bpf_prog *fp, u32 *image, u32 *fimage, struct code
 		u32 true_cond;
 		u32 tmp_idx;
 
+		if (bpf_insn_is_hmul(&insn[i]))
+			return -EOPNOTSUPP;
+
 		if (i && (BPF_CLASS(code) == BPF_ALU64 || BPF_CLASS(code) == BPF_ALU) &&
 		    (BPF_CLASS(prevcode) == BPF_ALU64 || BPF_CLASS(prevcode) == BPF_ALU) &&
 		    BPF_OP(prevcode) == BPF_MOV && BPF_SRC(prevcode) == BPF_X &&
diff --git a/arch/powerpc/net/bpf_jit_comp64.c b/arch/powerpc/net/bpf_jit_comp64.c
index fc9db691e8203..192c96e0ed34a 100644
--- a/arch/powerpc/net/bpf_jit_comp64.c
+++ b/arch/powerpc/net/bpf_jit_comp64.c
@@ -992,6 +992,9 @@ int bpf_jit_build_body(struct bpf_prog *fp, u32 *image, u32 *fimage, struct code
 		u32 tmp_idx;
 		u32 jmp_off;
 
+		if (bpf_insn_is_hmul(&insn[i]))
+			return -EOPNOTSUPP;
+
 		/*
 		 * addrs[] maps a BPF bytecode address into a real offset from
 		 * the start of the body code.
diff --git a/arch/riscv/net/bpf_jit.h b/arch/riscv/net/bpf_jit.h
index 5c4f53060c918..21a190a6a6d99 100644
--- a/arch/riscv/net/bpf_jit.h
+++ b/arch/riscv/net/bpf_jit.h
@@ -436,6 +436,11 @@ static inline u32 rv_mul(u8 rd, u8 rs1, u8 rs2)
 	return rv_r_insn(1, rs2, rs1, 0, rd, 0x33);
 }
 
+static inline u32 rv_mulh(u8 rd, u8 rs1, u8 rs2)
+{
+	return rv_r_insn(1, rs2, rs1, 1, rd, 0x33);
+}
+
 static inline u32 rv_mulhu(u8 rd, u8 rs1, u8 rs2)
 {
 	return rv_r_insn(1, rs2, rs1, 3, rd, 0x33);
diff --git a/arch/riscv/net/bpf_jit_comp32.c b/arch/riscv/net/bpf_jit_comp32.c
index a9e0bd5cc81d2..64bc6779d25c7 100644
--- a/arch/riscv/net/bpf_jit_comp32.c
+++ b/arch/riscv/net/bpf_jit_comp32.c
@@ -1014,6 +1014,9 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,
 	const s8 *tmp1 = bpf2rv32[TMP_REG_1];
 	const s8 *tmp2 = bpf2rv32[TMP_REG_2];
 
+	if (bpf_insn_is_hmul(insn))
+		goto notsupported;
+
 	switch (code) {
 	case BPF_ALU64 | BPF_MOV | BPF_X:
 		if (insn->off != 0) {
diff --git a/arch/riscv/net/bpf_jit_comp64.c b/arch/riscv/net/bpf_jit_comp64.c
index 788fcc21fd5b8..dfaf85926f204 100644
--- a/arch/riscv/net/bpf_jit_comp64.c
+++ b/arch/riscv/net/bpf_jit_comp64.c
@@ -197,6 +197,19 @@ static void emit_imm(u8 rd, s64 val, struct rv_jit_context *ctx)
 		emit_addi(rd, rd, lower, ctx);
 }
 
+static void emit_mul(u8 rd, u8 rs, bool is64, s16 off,
+		     struct rv_jit_context *ctx)
+{
+	if (!is64)
+		emit(rv_mulw(rd, rd, rs), ctx);
+	else if (off == BPF_UHMUL)
+		emit(rv_mulhu(rd, rd, rs), ctx);
+	else if (off == BPF_SHMUL)
+		emit(rv_mulh(rd, rd, rs), ctx);
+	else
+		emit(rv_mul(rd, rd, rs), ctx);
+}
+
 static void __build_epilogue(bool is_tail_call, struct rv_jit_context *ctx)
 {
 	int stack_adjust = ctx->stack_size, store_offset = stack_adjust - 8;
@@ -1526,7 +1539,7 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,
 		break;
 	case BPF_ALU | BPF_MUL | BPF_X:
 	case BPF_ALU64 | BPF_MUL | BPF_X:
-		emit(is64 ? rv_mul(rd, rd, rs) : rv_mulw(rd, rd, rs), ctx);
+		emit_mul(rd, rs, is64, off, ctx);
 		if (!is64 && !aux->verifier_zext)
 			emit_zextw(rd, rd, ctx);
 		break;
@@ -1662,8 +1675,7 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,
 	case BPF_ALU | BPF_MUL | BPF_K:
 	case BPF_ALU64 | BPF_MUL | BPF_K:
 		emit_imm(RV_REG_T1, imm, ctx);
-		emit(is64 ? rv_mul(rd, rd, RV_REG_T1) :
-		     rv_mulw(rd, rd, RV_REG_T1), ctx);
+		emit_mul(rd, RV_REG_T1, is64, off, ctx);
 		if (!is64 && !aux->verifier_zext)
 			emit_zextw(rd, rd, ctx);
 		break;
diff --git a/arch/s390/net/bpf_jit_comp.c b/arch/s390/net/bpf_jit_comp.c
index 8eaaac7028a16..35af9a32294a8 100644
--- a/arch/s390/net/bpf_jit_comp.c
+++ b/arch/s390/net/bpf_jit_comp.c
@@ -993,6 +993,9 @@ static noinline int bpf_jit_insn(struct bpf_jit *jit, struct bpf_prog *fp,
 
 	bpf_jit_probe_init(&probe);
 
+	if (bpf_insn_is_hmul(insn))
+		return -EOPNOTSUPP;
+
 	switch (insn->code) {
 	/*
 	 * BPF_MOV
diff --git a/arch/sparc/net/bpf_jit_comp_64.c b/arch/sparc/net/bpf_jit_comp_64.c
index 2fa0e93751276..1154ddc515b0f 100644
--- a/arch/sparc/net/bpf_jit_comp_64.c
+++ b/arch/sparc/net/bpf_jit_comp_64.c
@@ -901,6 +901,9 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx)
 	const s16 off = insn->off;
 	const s32 imm = insn->imm;
 
+	if (bpf_insn_is_hmul(insn))
+		return -EOPNOTSUPP;
+
 	if (insn->src_reg == BPF_REG_FP)
 		ctx->saw_frame_pointer = true;
 
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 6a2fe3d0039d3..53f38e21f7f3d 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -1378,6 +1378,38 @@ static int emit_kasan_check(struct bpf_verifier_env *env, u8 **pprog,
 	return 0;
 }
 
+/*
+ * The one-operand x86 mul/imul forms use RAX as the implicit first
+ * operand and store the 128-bit product in RDX:RAX.
+ * The JIT uses AUX_REG as the explicit second operand.
+ */
+static void emit_hmul_aux(u8 **pprog, u32 dst_reg, bool is_signed)
+{
+	u8 *prog = *pprog;
+	bool save_rax = dst_reg != BPF_REG_0;
+	bool save_rdx = dst_reg != BPF_REG_3;
+	u8 mul_modrm_base = is_signed ? 0xE8 : 0xE0; /* imul /5 or mul /4 */
+
+	if (save_rax)
+		EMIT1(0x50); /* push rax */
+	if (save_rdx)
+		EMIT1(0x52); /* push rdx */
+
+	if (save_rax)
+		emit_mov_reg(&prog, true, BPF_REG_0, dst_reg);
+	maybe_emit_1mod(&prog, AUX_REG, true);
+	EMIT2(0xF7, add_1reg(mul_modrm_base, AUX_REG));
+	if (save_rdx)
+		emit_mov_reg(&prog, true, dst_reg, BPF_REG_3);
+
+	if (save_rdx)
+		EMIT1(0x5A); /* pop rdx */
+	if (save_rax)
+		EMIT1(0x58); /* pop rax */
+
+	*pprog = prog;
+}
+
 /* LDX: dst_reg = *(u8*)(src_reg + off) */
 static void emit_ldx(u8 **pprog, u32 size, u32 dst_reg, u32 src_reg, int off)
 {
@@ -2916,6 +2948,14 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
 
 		case BPF_ALU | BPF_MUL | BPF_K:
 		case BPF_ALU64 | BPF_MUL | BPF_K:
+			if (bpf_insn_is_hmul(insn)) {
+				/* imul/mul use AUX_REG as src. */
+				emit_mov_imm32(&prog, true, AUX_REG, imm32);
+				emit_hmul_aux(&prog, dst_reg,
+					      insn->off == BPF_SHMUL);
+				break;
+			}
+
 			/*
 			 * lea for 3, 5 and 9, which LLVM leaves as a
 			 * multiplication, but not for r13, which needs a
@@ -2951,6 +2991,14 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
 
 		case BPF_ALU | BPF_MUL | BPF_X:
 		case BPF_ALU64 | BPF_MUL | BPF_X:
+			if (bpf_insn_is_hmul(insn)) {
+				/* imul/mul use AUX_REG as src. */
+				emit_mov_reg(&prog, true, AUX_REG, src_reg);
+				emit_hmul_aux(&prog, dst_reg,
+					      insn->off == BPF_SHMUL);
+				break;
+			}
+
 			maybe_emit_mod(&prog, src_reg, dst_reg,
 				       BPF_CLASS(insn->code) == BPF_ALU64);
 
diff --git a/arch/x86/net/bpf_jit_comp32.c b/arch/x86/net/bpf_jit_comp32.c
index 852baf2e4db4d..11ed65130d1e5 100644
--- a/arch/x86/net/bpf_jit_comp32.c
+++ b/arch/x86/net/bpf_jit_comp32.c
@@ -1683,6 +1683,9 @@ static int do_jit(struct bpf_prog *bpf_prog, int *addrs, u8 *image,
 		int ilen;
 		u8 *func;
 
+		if (bpf_insn_is_hmul(insn))
+			goto notyet;
+
 		switch (code) {
 		/* ALU operations */
 		/* dst = src */
diff --git a/drivers/net/ethernet/netronome/nfp/bpf/verifier.c b/drivers/net/ethernet/netronome/nfp/bpf/verifier.c
index 9ddbfe9d60194..4481c8d84d280 100644
--- a/drivers/net/ethernet/netronome/nfp/bpf/verifier.c
+++ b/drivers/net/ethernet/netronome/nfp/bpf/verifier.c
@@ -3,6 +3,7 @@
 
 #include <linux/bpf.h>
 #include <linux/bpf_verifier.h>
+#include <linux/filter.h>
 #include <linux/kernel.h>
 #include <linux/netdevice.h>
 #include <linux/pkt_cls.h>
@@ -561,6 +562,11 @@ nfp_bpf_check_alu(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta,
 	const struct bpf_reg_state *dreg =
 		cur_regs(env) + meta->insn.dst_reg;
 
+	if (bpf_insn_is_hmul(&meta->insn)) {
+		pr_vlog(env, "UHMUL/SHMUL are not supported\n");
+		return -EOPNOTSUPP;
+	}
+
 	meta->umin_src = min(meta->umin_src, reg_umin(sreg));
 	meta->umax_src = max(meta->umax_src, reg_umax(sreg));
 	meta->umin_dst = min(meta->umin_dst, reg_umin(dreg));
diff --git a/include/linux/filter.h b/include/linux/filter.h
index e0218e677a1dc..fa8a1d18691d6 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -23,6 +23,7 @@
 #include <linux/sockptr.h>
 #include <linux/static_call.h>
 #include <linux/u64_stats_sync.h>
+#include <linux/math64.h>
 
 #include <net/sch_generic.h>
 
@@ -152,6 +153,43 @@ struct ctl_table_header;
 #define BPF_ALU32_IMM(OP, DST, IMM)				\
 	BPF_ALU32_IMM_OFF(OP, DST, IMM, 0)
 
+static inline u64 bpf_uhmul64(u64 a, u64 b)
+{
+	return mul_u64_u64_shr(a, b, 64);
+}
+
+static inline u64 bpf_shmul64(s64 a, s64 b)
+{
+	u64 ua = a, ub = b;
+	u64 hi = bpf_uhmul64(ua, ub);
+
+	/*
+	 * Let M = 2^64, sa = (a < 0), and sb = (b < 0). Then:
+	 *
+	 * a * b = (ua - sa * M) * (ub - sb * M)
+	 *       = ua * ub - sa * M * ub - sb * M * ua + sa * sb * M^2
+	 *
+	 * Therefore, signed_hi = unsigned_hi - sa * ub - sb * ua (mod M).
+	 */
+	if (a < 0)
+		hi -= ub;
+	if (b < 0)
+		hi -= ua;
+	return hi;
+}
+
+static inline bool bpf_is_hmul_variant(s16 off)
+{
+	return off == BPF_UHMUL || off == BPF_SHMUL;
+}
+
+static inline bool bpf_insn_is_hmul(const struct bpf_insn *insn)
+{
+	return BPF_CLASS(insn->code) == BPF_ALU64 &&
+	       BPF_OP(insn->code) == BPF_MUL &&
+	       bpf_is_hmul_variant(insn->off);
+}
+
 /* Endianess conversion, cpu_to_{l,b}e(), {l,b}e_to_cpu() */
 
 #define BPF_ENDIAN(TYPE, DST, LEN)				\
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index f29b0aad440cb..90c30e5d790b9 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -34,6 +34,12 @@
 #define BPF_FROM_LE	BPF_TO_LE
 #define BPF_FROM_BE	BPF_TO_BE
 
+/* BPF_MUL off values for high-half multiplication; BPF_ALU64 only. */
+enum bpf_mul_variant {
+	BPF_UHMUL = 1,
+	BPF_SHMUL = 2,
+};
+
 /* jmp encodings */
 #define BPF_JNE		0x50	/* jump != */
 #define BPF_JLT		0xa0	/* LT is unsigned, '<' */
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 3030beb531d99..f4f8cfa80033a 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -1943,7 +1943,46 @@ static u64 ___bpf_prog_run(u64 *regs, const struct bpf_insn *insn)
 	ALU(AND,  &)
 	ALU(OR,   |)
 	ALU(XOR,  ^)
-	ALU(MUL,  *)
+	/*
+	 * MUL needs explicit handlers because ALU64 insn->off selects
+	 * low-half, unsigned high-half, or signed high-half multiplication.
+	 */
+	ALU64_MUL_X:
+		switch (OFF) {
+		case 0:
+			DST = DST * SRC;
+			break;
+		case BPF_UHMUL:
+			DST = bpf_uhmul64(DST, SRC);
+			break;
+		case BPF_SHMUL:
+			DST = bpf_shmul64((s64)DST, (s64)SRC);
+			break;
+		default:
+			goto default_label;
+		}
+		CONT;
+	ALU_MUL_X:
+		DST = (u32) DST * (u32) SRC;
+		CONT;
+	ALU64_MUL_K:
+		switch (OFF) {
+		case 0:
+			DST = DST * IMM;
+			break;
+		case BPF_UHMUL:
+			DST = bpf_uhmul64(DST, (u64)IMM);
+			break;
+		case BPF_SHMUL:
+			DST = bpf_shmul64((s64)DST, (s32)IMM);
+			break;
+		default:
+			goto default_label;
+		}
+		CONT;
+	ALU_MUL_K:
+		DST = (u32) DST * (u32) IMM;
+		CONT;
 	SHT(LSH, <<)
 	SHT(RSH, >>)
 #undef SHT
diff --git a/kernel/bpf/disasm.c b/kernel/bpf/disasm.c
index 889fc074496f1..0b1bbb27e9e4e 100644
--- a/kernel/bpf/disasm.c
+++ b/kernel/bpf/disasm.c
@@ -163,6 +163,22 @@ static bool is_sdiv_smod(const struct bpf_insn *insn)
 	       insn->off == 1;
 }
 
+/* Return the UHMUL or SHMUL operator string, or NULL for all other encodings. */
+static const char *hmul_op_string(const struct bpf_insn *insn)
+{
+	if (BPF_CLASS(insn->code) != BPF_ALU64 ||
+	    BPF_OP(insn->code) != BPF_MUL)
+		return NULL;
+	switch (insn->off) {
+	case BPF_UHMUL:
+		return "uh*=";
+	case BPF_SHMUL:
+		return "sh*=";
+	default:
+		return NULL;
+	}
+}
+
 static bool is_movsx(const struct bpf_insn *insn)
 {
 	return BPF_OP(insn->code) == BPF_MOV &&
@@ -215,6 +231,7 @@ void print_bpf_insn(const struct bpf_insn_cbs *cbs,
 			verbose(cbs->private_data, "(%02x) %c%d %s %s%c%d",
 				insn->code, class == BPF_ALU ? 'w' : 'r',
 				insn->dst_reg,
+				hmul_op_string(insn) ?:
 				is_sdiv_smod(insn) ? bpf_alu_sign_string[BPF_OP(insn->code) >> 4]
 						   : bpf_alu_string[BPF_OP(insn->code) >> 4],
 				is_movsx(insn) ? bpf_movsx_string[(insn->off >> 3) - 1] : "",
@@ -224,6 +241,7 @@ void print_bpf_insn(const struct bpf_insn_cbs *cbs,
 			verbose(cbs->private_data, "(%02x) %c%d %s %d",
 				insn->code, class == BPF_ALU ? 'w' : 'r',
 				insn->dst_reg,
+				hmul_op_string(insn) ?:
 				is_sdiv_smod(insn) ? bpf_alu_sign_string[BPF_OP(insn->code) >> 4]
 						   : bpf_alu_string[BPF_OP(insn->code) >> 4],
 				insn->imm);
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 5601d20562186..fbb9202397832 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -16664,6 +16664,24 @@ static void scalar_min_max_mul(struct bpf_reg_state *dst_reg,
 					cnum64_from_srange(smin, smax));
 }
 
+static void scalar_min_max_uhmul(struct bpf_reg_state *dst_reg,
+				 struct bpf_reg_state *src_reg)
+{
+	u64 dst = reg_const_value(dst_reg, false);
+	u64 src = reg_const_value(src_reg, false);
+
+	___mark_reg_known(dst_reg, bpf_uhmul64(dst, src));
+}
+
+static void scalar_min_max_shmul(struct bpf_reg_state *dst_reg,
+				 struct bpf_reg_state *src_reg)
+{
+	s64 dst = reg_const_value(dst_reg, false);
+	s64 src = reg_const_value(src_reg, false);
+
+	___mark_reg_known(dst_reg, bpf_shmul64(dst, src));
+}
+
 static void scalar32_min_max_udiv(struct bpf_reg_state *dst_reg,
 				  struct bpf_reg_state *src_reg)
 {
@@ -17364,6 +17382,25 @@ static int adjust_scalar_min_max_vals(struct bpf_verifier_env *env,
 		reg_step_reset(dst_reg);
 		break;
 	case BPF_MUL:
+		/*
+		 * Range analysis for UHMUL/SHMUL is not implemented yet.
+		 * Compute an exact result only when both operands are constants.
+		 * Ordinary low-half MUL continues to use the analysis below.
+		 */
+		if (bpf_insn_is_hmul(insn) &&
+		    (!is_reg_const(dst_reg, false) ||
+		     !is_reg_const(&src_reg, false))) {
+			__mark_reg_unknown(env, dst_reg);
+			break;
+		}
+		if (!alu32 && insn->off == BPF_UHMUL) {
+			scalar_min_max_uhmul(dst_reg, &src_reg);
+			break;
+		}
+		if (!alu32 && insn->off == BPF_SHMUL) {
+			scalar_min_max_shmul(dst_reg, &src_reg);
+			break;
+		}
 		scalar_step_mul(dst_reg, &src_reg);
 		dst_reg->var_off = tnum_mul(dst_reg->var_off, src_reg.var_off);
 		scalar32_min_max_mul(dst_reg, &src_reg);
@@ -17499,6 +17536,11 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
 	/* Case where at least one operand is an arena. */
 	if (dst_reg->type == PTR_TO_ARENA || (src_reg && src_reg->type == PTR_TO_ARENA)) {
 
+		if (bpf_insn_is_hmul(insn)) {
+			verbose(env, "UHMUL/SHMUL with arena pointers are not supported\n");
+			return -EOPNOTSUPP;
+		}
+
 		if (dst_reg->type != PTR_TO_ARENA)
 			*dst_reg = *src_reg;
 
@@ -21194,6 +21236,20 @@ static int fd_array_get_map_idx(struct bpf_verifier_env *env, u32 idx)
 	return -EPROTO;
 }
 
+static bool is_valid_alu_variant(const struct bpf_insn *insn)
+{
+	switch (BPF_OP(insn->code)) {
+	case BPF_MUL:
+		return insn->off == 0 || bpf_insn_is_hmul(insn);
+	case BPF_DIV:
+	case BPF_MOD:
+		/* off == 1 selects SDIV/SMOD. */
+		return insn->off == 0 || insn->off == 1;
+	default:
+		return insn->off == 0;
+	}
+}
+
 static int check_alu_fields(struct bpf_verifier_env *env, struct bpf_insn *insn)
 {
 	u8 class = BPF_CLASS(insn->code);
@@ -21249,15 +21305,9 @@ static int check_alu_fields(struct bpf_verifier_env *env, struct bpf_insn *insn)
 	case BPF_MUL:
 	case BPF_DIV:
 	case BPF_MOD:
-		if (BPF_SRC(insn->code) == BPF_X) {
-			if (insn->imm != 0 || (insn->off != 0 && insn->off != 1) ||
-			    (insn->off == 1 && opcode != BPF_MOD && opcode != BPF_DIV)) {
-				verbose(env, "BPF_ALU uses reserved fields\n");
-				return -EINVAL;
-			}
-		} else if (insn->src_reg != BPF_REG_0 ||
-			   (insn->off != 0 && insn->off != 1) ||
-			   (insn->off == 1 && opcode != BPF_MOD && opcode != BPF_DIV)) {
+		if (!is_valid_alu_variant(insn) ||
+		    (BPF_SRC(insn->code) == BPF_X && insn->imm != 0) ||
+		    (BPF_SRC(insn->code) == BPF_K && insn->src_reg != BPF_REG_0)) {
 			verbose(env, "BPF_ALU uses reserved fields\n");
 			return -EINVAL;
 		}
diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
index f29b0aad440cb..90c30e5d790b9 100644
--- a/tools/include/uapi/linux/bpf.h
+++ b/tools/include/uapi/linux/bpf.h
@@ -34,6 +34,12 @@
 #define BPF_FROM_LE	BPF_TO_LE
 #define BPF_FROM_BE	BPF_TO_BE
 
+/* BPF_MUL off values for high-half multiplication; BPF_ALU64 only. */
+enum bpf_mul_variant {
+	BPF_UHMUL = 1,
+	BPF_SHMUL = 2,
+};
+
 /* jmp encodings */
 #define BPF_JNE		0x50	/* jump != */
 #define BPF_JLT		0xa0	/* LT is unsigned, '<' */
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index 04740ba37e918..795c3e56c23e0 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -59,6 +59,7 @@
 #include "verifier_helper_packet_access.skel.h"
 #include "verifier_helper_restricted.skel.h"
 #include "verifier_helper_value_access.skel.h"
+#include "verifier_hmul.skel.h"
 #include "verifier_idoms.skel.h"
 #include "verifier_int_ptr.skel.h"
 #include "verifier_iterating_callbacks.skel.h"
@@ -251,6 +252,7 @@ void test_verifier_helper_access_var_len(void) { RUN(verifier_helper_access_var_
 void test_verifier_helper_packet_access(void) { RUN(verifier_helper_packet_access); }
 void test_verifier_helper_restricted(void)    { RUN(verifier_helper_restricted); }
 void test_verifier_helper_value_access(void)  { RUN(verifier_helper_value_access); }
+void test_verifier_hmul(void)                 { RUN(verifier_hmul); }
 void test_verifier_idoms(void)                { RUN(verifier_idoms); }
 void test_verifier_int_ptr(void)              { RUN(verifier_int_ptr); }
 void test_verifier_iterating_callbacks(void)  { RUN(verifier_iterating_callbacks); }
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier_hmul_arena.c b/tools/testing/selftests/bpf/prog_tests/verifier_hmul_arena.c
new file mode 100644
index 0000000000000..f9cd7b7b9781a
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/verifier_hmul_arena.c
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include "verifier_hmul_arena.skel.h"
+
+void test_verifier_hmul_arena(void)
+{
+	int ret;
+
+	/* Arena support can depend on CPU features, such as Zacas on RV64. */
+	ret = libbpf_probe_bpf_map_type(BPF_MAP_TYPE_ARENA, NULL);
+	if (!ASSERT_GE(ret, 0, "probe_arena"))
+		return;
+	if (!ret) {
+		test__skip();
+		return;
+	}
+	RUN_TESTS(verifier_hmul_arena);
+}
diff --git a/tools/testing/selftests/bpf/progs/verifier_hmul.c b/tools/testing/selftests/bpf/progs/verifier_hmul.c
new file mode 100644
index 0000000000000..acd92355c6365
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_hmul.c
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bpf.h>
+#include <limits.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+#include "../../../include/linux/filter.h"
+
+#define BPF_UHMUL64_REG(DST, SRC) \
+	BPF_RAW_INSN(BPF_ALU64 | BPF_MUL | BPF_X, DST, SRC, BPF_UHMUL, 0)
+
+#define BPF_UHMUL64_IMM(DST, IMM) \
+	BPF_RAW_INSN(BPF_ALU64 | BPF_MUL | BPF_K, DST, 0, BPF_UHMUL, IMM)
+
+#define BPF_SHMUL64_REG(DST, SRC) \
+	BPF_RAW_INSN(BPF_ALU64 | BPF_MUL | BPF_X, DST, SRC, BPF_SHMUL, 0)
+
+#define BPF_SHMUL64_IMM(DST, IMM) \
+	BPF_RAW_INSN(BPF_ALU64 | BPF_MUL | BPF_K, DST, 0, BPF_SHMUL, IMM)
+
+/*
+ * Inject UHMUL/SHMUL as raw instructions so these tests do not require
+ * assembler support for the new mnemonics. All surrounding instructions use
+ * existing BPF assembly syntax supported by the baseline selftests toolchain.
+ */
+
+#if defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64) || \
+	(defined(__TARGET_ARCH_riscv) && __riscv_xlen == 64)
+
+/*
+ * BPF_PROG_TEST_RUN reports a 32-bit retval. Fold both halves of
+ * (actual ^ expected) into w0 so retval == 0 checks the full 64-bit result.
+ */
+
+SEC("socket")
+__description("UHMUL64, U64_MAX * U64_MAX, register source")
+__success __success_unpriv __retval(0)
+__naked void uhmul64_max_reg(void)
+{
+	asm volatile (
+	"r0 = -1;"
+	"r1 = -1;"
+	".8byte %[hmul];"
+	"r2 = -2;"
+	"r0 ^= r2;"
+	"r2 = r0;"
+	"r2 >>= 32;"
+	"w0 |= w2;"
+	"exit;"
+	:
+	: __imm_insn(hmul, BPF_UHMUL64_REG(BPF_REG_0, BPF_REG_1))
+	: __clobber_all);
+}
+
+SEC("socket")
+__description("UHMUL64, (1ULL << 63) * 2, register source")
+__success __success_unpriv __retval(0)
+__naked void uhmul64_pow2_reg(void)
+{
+	asm volatile (
+	"r0 = 2;"
+	"r1 = %[llong_min] ll;"
+	".8byte %[hmul];"
+	"r0 = r1;"
+	"r2 = 1;"
+	"r0 ^= r2;"
+	"r2 = r0;"
+	"r2 >>= 32;"
+	"w0 |= w2;"
+	"exit;"
+	:
+	: __imm_const(llong_min, LLONG_MIN),
+	  __imm_insn(hmul, BPF_UHMUL64_REG(BPF_REG_1, BPF_REG_0))
+	: __clobber_all);
+}
+
+SEC("socket")
+__description("UHMUL64, (1ULL << 63) * 2, R3 destination")
+__success __success_unpriv __retval(0)
+__naked void uhmul64_r3_dst(void)
+{
+	asm volatile (
+	"r3 = %[llong_min] ll;"
+	"r0 = 2;"
+	".8byte %[hmul];"
+	"r2 = 1;"
+	"r3 ^= r2;"
+	"r2 = r3;"
+	"r2 >>= 32;"
+	"w3 |= w2;"
+	"r0 = r3;"
+	"exit;"
+	:
+	: __imm_const(llong_min, LLONG_MIN),
+	  __imm_insn(hmul, BPF_UHMUL64_REG(BPF_REG_3, BPF_REG_0))
+	: __clobber_all);
+}
+
+SEC("socket")
+__description("UHMUL64, (1ULL << 63) * 2, immediate source")
+__success __success_unpriv __retval(0)
+__naked void uhmul64_pow2_imm(void)
+{
+	asm volatile (
+	"r0 = %[llong_min] ll;"
+	".8byte %[hmul];"
+	"r2 = 1;"
+	"r0 ^= r2;"
+	"r2 = r0;"
+	"r2 >>= 32;"
+	"w0 |= w2;"
+	"exit;"
+	:
+	: __imm_const(llong_min, LLONG_MIN),
+	  __imm_insn(hmul, BPF_UHMUL64_IMM(BPF_REG_0, 2))
+	: __clobber_all);
+}
+
+SEC("socket")
+__description("UHMUL64, (1ULL << 63) * -2, immediate source")
+__success __success_unpriv __retval(0)
+__naked void uhmul64_neg_imm(void)
+{
+	asm volatile (
+	"r0 = %[llong_min] ll;"
+	".8byte %[hmul];"
+	"r2 = %[llong_max] ll;"
+	"r0 ^= r2;"
+	"r2 = r0;"
+	"r2 >>= 32;"
+	"w0 |= w2;"
+	"exit;"
+	:
+	: __imm_const(llong_min, LLONG_MIN),
+	  __imm_const(llong_max, LLONG_MAX),
+	  __imm_insn(hmul, BPF_UHMUL64_IMM(BPF_REG_0, -2))
+	: __clobber_all);
+}
+
+SEC("socket")
+__description("SHMUL64, INT64_MAX * INT64_MAX, register source")
+__success __success_unpriv __retval(0)
+__naked void shmul64_max_reg(void)
+{
+	asm volatile (
+	"r0 = %[llong_max] ll;"
+	"r1 = %[llong_max] ll;"
+	".8byte %[hmul];"
+	"r2 = %[expected] ll;"
+	"r0 ^= r2;"
+	"r2 = r0;"
+	"r2 >>= 32;"
+	"w0 |= w2;"
+	"exit;"
+	:
+	: __imm_const(expected, 0x3fffffffffffffffULL),
+	  __imm_const(llong_max, LLONG_MAX),
+	  __imm_insn(hmul, BPF_SHMUL64_REG(BPF_REG_0, BPF_REG_1))
+	: __clobber_all);
+}
+
+SEC("socket")
+__description("SHMUL64, INT64_MIN * -2, register source")
+__success __success_unpriv __retval(0)
+__naked void shmul64_min_neg2_reg(void)
+{
+	asm volatile (
+	"r0 = %[llong_min] ll;"
+	"r1 = -2;"
+	".8byte %[hmul];"
+	"r2 = 1;"
+	"r0 ^= r2;"
+	"r2 = r0;"
+	"r2 >>= 32;"
+	"w0 |= w2;"
+	"exit;"
+	:
+	: __imm_const(llong_min, LLONG_MIN),
+	  __imm_insn(hmul, BPF_SHMUL64_REG(BPF_REG_0, BPF_REG_1))
+	: __clobber_all);
+}
+
+SEC("socket")
+__description("SHMUL64, 1 * -2, immediate source")
+__success __success_unpriv __retval(0)
+__naked void shmul64_neg_imm(void)
+{
+	asm volatile (
+	"r0 = 1;"
+	".8byte %[hmul];"
+	"r2 = -1;"
+	"r0 ^= r2;"
+	"r2 = r0;"
+	"r2 >>= 32;"
+	"w0 |= w2;"
+	"exit;"
+	:
+	: __imm_insn(hmul, BPF_SHMUL64_IMM(BPF_REG_0, -2))
+	: __clobber_all);
+}
+
+SEC("socket")
+__description("SHMUL64, INT64_MIN * 2, immediate source")
+__success __success_unpriv __retval(0)
+__naked void shmul64_min_imm(void)
+{
+	asm volatile (
+	"r0 = %[llong_min] ll;"
+	".8byte %[hmul];"
+	"r2 = -1;"
+	"r0 ^= r2;"
+	"r2 = r0;"
+	"r2 >>= 32;"
+	"w0 |= w2;"
+	"exit;"
+	:
+	: __imm_const(llong_min, LLONG_MIN),
+	  __imm_insn(hmul, BPF_SHMUL64_IMM(BPF_REG_0, 2))
+	: __clobber_all);
+}
+
+#else
+
+SEC("socket")
+__description("UHMUL/SHMUL are not supported by this JIT, use a dummy test")
+__skip("UHMUL/SHMUL are not supported by this jit")
+__success
+int dummy_test(void)
+{
+	return 0;
+}
+
+#endif
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_hmul_arena.c b/tools/testing/selftests/bpf/progs/verifier_hmul_arena.c
new file mode 100644
index 0000000000000..fc0ca88114d08
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_hmul_arena.c
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+#include "../../../include/linux/filter.h"
+
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+
+#define BPF_UHMUL64_IMM(DST, IMM) \
+	BPF_RAW_INSN(BPF_ALU64 | BPF_MUL | BPF_K, DST, 0, BPF_UHMUL, IMM)
+
+#define BPF_SHMUL64_REG(DST, SRC) \
+	BPF_RAW_INSN(BPF_ALU64 | BPF_MUL | BPF_X, DST, SRC, BPF_SHMUL, 0)
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARENA);
+	__uint(map_flags, BPF_F_MMAPABLE);
+	__uint(max_entries, 1);
+} arena SEC(".maps");
+
+SEC("syscall")
+__description("UHMUL64 with arena pointer dst")
+__failure __msg("UHMUL/SHMUL with arena pointers are not supported")
+__naked void uhmul64_arena_ptr(void)
+{
+	asm volatile (
+	"r1 = %[arena] ll;"
+	"r0 = 1;"
+	"r0 = addr_space_cast(r0, 0x0, 0x1);"
+	".8byte %[hmul];"
+	"exit;"
+	:
+	: __imm_addr(arena),
+	  __imm_insn(hmul, BPF_UHMUL64_IMM(BPF_REG_0, 2))
+	: __clobber_all);
+}
+
+SEC("syscall")
+__description("SHMUL64 with arena pointer src")
+__failure __msg("UHMUL/SHMUL with arena pointers are not supported")
+__naked void shmul64_arena_ptr(void)
+{
+	asm volatile (
+	"r1 = %[arena] ll;"
+	"r1 = 1;"
+	"r1 = addr_space_cast(r1, 0x0, 0x1);"
+	"r0 = 2;"
+	".8byte %[hmul];"
+	"exit;"
+	:
+	: __imm_addr(arena),
+	  __imm_insn(hmul, BPF_SHMUL64_REG(BPF_REG_0, BPF_REG_1))
+	: __clobber_all);
+}
+
+#else
+
+SEC("socket")
+__description("HMUL arena tests require compiler support for addr_space_cast")
+__skip("compiler doesn't support addr_space_cast")
+__success
+int dummy_test(void)
+{
+	return 0;
+}
+
+#endif
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c b/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c
index 31663338866d3..209d5be551555 100644
--- a/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c
+++ b/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c
@@ -367,7 +367,8 @@ __naked void flow_keys_illegal_variable_offset_alu(void)
 
 /*
  * Offset fields of 0 and 1 are legal for BPF_{DIV,MOD} instructions.
- * Offset fields of 0 are legal for the rest of ALU instructions.
+ * Offset fields of 0, 1 and 2 are legal for BPF_ALU64 | BPF_MUL.
+ * Only offset 0 is legal for BPF_ALU | BPF_MUL.
  * Test that error is reported for illegal offsets, assuming that tests
  * for legal offsets exist.
  */
@@ -377,5 +378,9 @@ DEFINE_BAD_OFFSET_TEST(bad_offset_addx, BPF_ALU64 | BPF_ADD | BPF_X, -1, 0)
 DEFINE_BAD_OFFSET_TEST(bad_offset_divx2, BPF_ALU64 | BPF_DIV | BPF_X, 2, 0)
 DEFINE_BAD_OFFSET_TEST(bad_offset_modk2, BPF_ALU64 | BPF_MOD | BPF_K, 2, 1)
 DEFINE_BAD_OFFSET_TEST(bad_offset_addx2, BPF_ALU64 | BPF_ADD | BPF_X, 1, 0)
+DEFINE_BAD_OFFSET_TEST(bad_offset_mulx, BPF_ALU64 | BPF_MUL | BPF_X, -1, 0)
+DEFINE_BAD_OFFSET_TEST(bad_offset_mulk, BPF_ALU64 | BPF_MUL | BPF_K, 3, 0)
+DEFINE_BAD_OFFSET_TEST(bad_uhmul32_x, BPF_ALU | BPF_MUL | BPF_X, BPF_UHMUL, 0)
+DEFINE_BAD_OFFSET_TEST(bad_shmul32_k, BPF_ALU | BPF_MUL | BPF_K, BPF_SHMUL, 0)
 
 char _license[] SEC("license") = "GPL";

3/2 2026/10/22 03:52 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 21313
							output: 42
							thoughts: 708
						

Thoughts:
**Evaluating the Patch's Suitability for Syzkaller Fuzzing**

Okay, so the prompt is asking me to determine if this patch is a good candidate for fuzzing with syzkaller. Let's break it down. The target architecture is amd64, which is a key consideration. Now, let's look at what the patch itself touches.

I see a lot of modifications, specifically within the BPF (Berkeley Packet Filter) subsystem. It's hitting `Documentation/bpf/standardization/instruction-set.rst`, which means new BPF instructions are being added. It also touches a wide array of architectures' JIT compilers and core files like `kernel/bpf/core.c`, the interpreter, and `kernel/bpf/verifier.c`, the verifier. The patch even updates the disassembler in `kernel/bpf/disasm.c`. It also changes include files and parts of the driver files in `drivers/net/ethernet/netronome/nfp/bpf/verifier.c`.

Specifically, it looks like this patch is adding new instructions: `UHMUL` (unsigned high half multiplication) and `SHMUL` (signed high half multiplication), and these are ALU64 only. The patch is then altering core components like the interpreter, the verifier (with several specific functions impacted), and the JIT compilers (e.g., `arch/x86/net/bpf_jit_comp.c`), which is where the translation of BPF bytecode to native instructions happens. This screams "fuzzing target" to me because the verifier and JIT compilation are often where errors and security vulnerabilities are found.

And the changes modify code that's *highly* reachable from userspace through the `bpf(2)` syscall. People can load BPF programs, test them, attach them to sockets, use them with tracepoints, cgroups, etc. That's a huge attack surface.

Therefore, is this WorthFuzzing=true? YES, absolutely! This is directly modifying the BPF core subsystem: the verifier, the interpreter, and the amd64 JIT compiler. The changes are very likely to expose new code paths and potential vulnerabilities that syzkaller can easily exercise. The BPF subsystem is already a prime target for fuzzing, and this patch makes it even more so! Now I should probably look at the Kconfig options and figure out which symbols this touches.



4/2 2026/10/22 03:52 tool grepper
0m Args:
{
  "Expression": "BPF_JIT",
  "PathPrefixes": [
    "arch/x86/net/",
    "kernel/bpf/"
  ]
}

Results:
{
  "Output": "\nFull output is too long, showing 200 out of 221 lines.\nUse more precise expression or PathPrefixes if possible.\n\nNumber of matching lines per file (10 files in total):\narch/x86/net/Makefile:2\narch/x86/net/bpf_jit_comp.c:12\narch/x86/net/bpf_jit_comp32.c:1\nkernel/bpf/Kconfig:16\nkernel/bpf/Makefile:4\nkernel/bpf/btf.c:1\nkernel/bpf/core.c:13\nkernel/bpf/fixups.c:3\nkernel/bpf/syscall.c:1\nkernel/bpf/verifier.c:1\n\narch/x86/net/Makefile=6=ifeq ($(CONFIG_X86_32),y)\narch/x86/net/Makefile:7:        obj-$(CONFIG_BPF_JIT) += bpf_jit_comp32.o\narch/x86/net/Makefile-8-else\narch/x86/net/Makefile:9:        obj-$(CONFIG_BPF_JIT) += bpf_jit_comp.o bpf_timed_may_goto.o\narch/x86/net/Makefile-10-endif\n--\narch/x86/net/bpf_jit_comp.c-26-\narch/x86/net/bpf_jit_comp.c:27:#if IS_ENABLED(CONFIG_BPF_JIT_KASAN)\narch/x86/net/bpf_jit_comp.c-28-void __asan_load1(void *p);\n--\narch/x86/net/bpf_jit_comp.c=167=static int bpf_size_to_x86_bytes(int bpf_size)\n--\narch/x86/net/bpf_jit_comp.c-196-/* Pick a register outside of BPF range for JIT internal work */\narch/x86/net/bpf_jit_comp.c:197:#define AUX_REG (MAX_BPF_JIT_REG + 1)\narch/x86/net/bpf_jit_comp.c:198:#define X86_REG_R9 (MAX_BPF_JIT_REG + 2)\narch/x86/net/bpf_jit_comp.c:199:#define X86_REG_R12 (MAX_BPF_JIT_REG + 3)\narch/x86/net/bpf_jit_comp.c-200-\n--\narch/x86/net/bpf_jit_comp.c=1301=static int emit_kasan_check(struct bpf_verifier_env *env, u8 **pprog,\n--\narch/x86/net/bpf_jit_comp.c-1304-{\narch/x86/net/bpf_jit_comp.c:1305:#ifdef CONFIG_BPF_JIT_KASAN\narch/x86/net/bpf_jit_comp.c-1306-\tu8 *prog = *pprog;\n--\narch/x86/net/bpf_jit_comp.c-1376-\t*pprog = prog;\narch/x86/net/bpf_jit_comp.c:1377:#endif /* CONFIG_BPF_JIT_KASAN */\narch/x86/net/bpf_jit_comp.c-1378-\treturn 0;\n--\narch/x86/net/bpf_jit_comp.c=2410=static int emit_kfunc_arg_moves(const struct btf_func_model *fm, s32 stack_base, u8 **pprog)\narch/x86/net/bpf_jit_comp.c-2411-{\narch/x86/net/bpf_jit_comp.c:2412:\tstruct bpf_jit_arg_move moves[BPF_JIT_MAX_ARG_MOVES];\narch/x86/net/bpf_jit_comp.c-2413-\tconst u8 nreg = x86_arg_abi.nr_arg_regs;\n--\narch/x86/net/bpf_jit_comp.c-2420-\t\tu8 dst = moves[i].dst, src = moves[i].src, reg;\narch/x86/net/bpf_jit_comp.c:2421:\t\tbool dst_mem = dst != BPF_JIT_ARG_TMP \u0026\u0026 dst \u003e= nreg;\narch/x86/net/bpf_jit_comp.c:2422:\t\tbool src_mem = src != BPF_JIT_ARG_TMP \u0026\u0026 src \u003e= nreg;\narch/x86/net/bpf_jit_comp.c-2423-\n--\narch/x86/net/bpf_jit_comp.c-2428-\t\t */\narch/x86/net/bpf_jit_comp.c:2429:\t\tif (src == BPF_JIT_ARG_TMP) {\narch/x86/net/bpf_jit_comp.c-2430-\t\t\treg = AUX_REG;\narch/x86/net/bpf_jit_comp.c-2431-\t\t} else if (src_mem) {\narch/x86/net/bpf_jit_comp.c:2432:\t\t\treg = dst == BPF_JIT_ARG_TMP ? AUX_REG :\narch/x86/net/bpf_jit_comp.c-2433-\t\t\t      dst_mem ? BPF_REG_AX : x86_arg_reg[dst];\n--\narch/x86/net/bpf_jit_comp.c-2440-\t\t/* And leave it where the argument belongs. */\narch/x86/net/bpf_jit_comp.c:2441:\t\tif (dst == BPF_JIT_ARG_TMP)\narch/x86/net/bpf_jit_comp.c-2442-\t\t\temit_mov_reg(\u0026prog, true, AUX_REG, reg);\n--\narch/x86/net/bpf_jit_comp32.c=89=static bool is_simm32(s64 value)\n--\narch/x86/net/bpf_jit_comp32.c-94-#define STACK_OFFSET(k)\t(k)\narch/x86/net/bpf_jit_comp32.c:95:#define TCALL_CNT\t(MAX_BPF_JIT_REG + 0)\t/* Tail Call Count */\narch/x86/net/bpf_jit_comp32.c-96-\n--\nkernel/bpf/Kconfig=4=config BPF\n--\nkernel/bpf/Kconfig-12-# Classic BPF JIT (cBPF)\nkernel/bpf/Kconfig:13:config HAVE_CBPF_JIT\nkernel/bpf/Kconfig-14-\tbool\n--\nkernel/bpf/Kconfig-16-# Extended BPF JIT (eBPF)\nkernel/bpf/Kconfig:17:config HAVE_EBPF_JIT\nkernel/bpf/Kconfig-18-\tbool\n--\nkernel/bpf/Kconfig-20-# KASAN support for JIT compiler\nkernel/bpf/Kconfig:21:config HAVE_EBPF_JIT_KASAN\nkernel/bpf/Kconfig-22-\tbool\n--\nkernel/bpf/Kconfig-25-# default for kernels that were compiled with BPF JIT support.\nkernel/bpf/Kconfig:26:config ARCH_WANT_DEFAULT_BPF_JIT\nkernel/bpf/Kconfig-27-\tbool\n--\nkernel/bpf/Kconfig=31=config BPF_SYSCALL\n--\nkernel/bpf/Kconfig-45-\nkernel/bpf/Kconfig:46:config BPF_JIT\nkernel/bpf/Kconfig-47-\tbool \"Enable BPF Just In Time compiler\"\nkernel/bpf/Kconfig-48-\tdepends on BPF\nkernel/bpf/Kconfig:49:\tdepends on HAVE_CBPF_JIT || HAVE_EBPF_JIT\nkernel/bpf/Kconfig-50-\tselect EXECMEM\n--\nkernel/bpf/Kconfig-61-\nkernel/bpf/Kconfig:62:config BPF_JIT_ALWAYS_ON\nkernel/bpf/Kconfig-63-\tbool \"Permanently enable BPF JIT and remove BPF interpreter\"\nkernel/bpf/Kconfig:64:\tdepends on BPF_SYSCALL \u0026\u0026 HAVE_EBPF_JIT \u0026\u0026 BPF_JIT\nkernel/bpf/Kconfig-65-\thelp\n--\nkernel/bpf/Kconfig-68-\nkernel/bpf/Kconfig:69:\t  When CONFIG_BPF_JIT_ALWAYS_ON is enabled, /proc/sys/net/core/bpf_jit_enable\nkernel/bpf/Kconfig-70-\t  is permanently set to 1 and setting any other value than that will\n--\nkernel/bpf/Kconfig-72-\nkernel/bpf/Kconfig:73:config BPF_JIT_DEFAULT_ON\nkernel/bpf/Kconfig:74:\tdef_bool ARCH_WANT_DEFAULT_BPF_JIT || BPF_JIT_ALWAYS_ON\nkernel/bpf/Kconfig:75:\tdepends on HAVE_EBPF_JIT \u0026\u0026 BPF_JIT\nkernel/bpf/Kconfig-76-\n--\nkernel/bpf/Kconfig=105=config BPF_LSM\n--\nkernel/bpf/Kconfig-109-\tdepends on SECURITY\nkernel/bpf/Kconfig:110:\tdepends on BPF_JIT\nkernel/bpf/Kconfig-111-\thelp\n--\nkernel/bpf/Kconfig-116-\nkernel/bpf/Kconfig:117:config BPF_JIT_KASAN\nkernel/bpf/Kconfig-118-\tbool\nkernel/bpf/Kconfig:119:\tdepends on HAVE_EBPF_JIT_KASAN\nkernel/bpf/Kconfig-120-\tdepends on KASAN_GENERIC\nkernel/bpf/Kconfig-121-\tdepends on KASAN_VMALLOC\nkernel/bpf/Kconfig:122:\tdepends on BPF_JIT\nkernel/bpf/Kconfig-123-\tdefault y\n--\nkernel/bpf/Makefile=2=obj-y := core.o\nkernel/bpf/Makefile:3:ifneq ($(CONFIG_BPF_JIT_ALWAYS_ON),y)\nkernel/bpf/Makefile-4-# ___bpf_prog_run() needs GCSE disabled on x86; see 3193c0836f203 for details\n--\nkernel/bpf/Makefile=16=obj-$(CONFIG_BPF_SYSCALL) += disasm.o mprog.o\nkernel/bpf/Makefile:17:obj-$(CONFIG_BPF_JIT) += trampoline.o\nkernel/bpf/Makefile-18-obj-$(CONFIG_BPF_SYSCALL) += btf.o memalloc.o rqspinlock.o stream.o\n--\nkernel/bpf/Makefile=21=endif\nkernel/bpf/Makefile:22:obj-$(CONFIG_BPF_JIT) += dispatcher.o\nkernel/bpf/Makefile-23-ifeq ($(CONFIG_NET),y)\n--\nkernel/bpf/Makefile=46=endif\nkernel/bpf/Makefile:47:ifeq ($(CONFIG_BPF_JIT),y)\nkernel/bpf/Makefile-48-obj-$(CONFIG_BPF_SYSCALL) += bpf_struct_ops.o\n--\nkernel/bpf/btf.c=10768=bool btf_type_ids_nocast_alias(struct bpf_verifier_log *log,\n--\nkernel/bpf/btf.c-10822-\nkernel/bpf/btf.c:10823:#ifdef CONFIG_BPF_JIT\nkernel/bpf/btf.c-10824-static int\n--\nkernel/bpf/core.c=102=struct bpf_prog *bpf_prog_alloc_no_stats(unsigned int size, gfp_t gfp_extra_flags)\n--\nkernel/bpf/core.c-130-\tfp-\u003ejit_requested = ebpf_jit_enabled();\nkernel/bpf/core.c:131:\tfp-\u003ejit_required = IS_ENABLED(CONFIG_BPF_JIT_ALWAYS_ON);\nkernel/bpf/core.c-132-\tfp-\u003eblinding_requested = bpf_jit_blinding_enabled(fp);\n--\nkernel/bpf/core.c=542=void bpf_prog_kallsyms_del_all(struct bpf_prog *fp)\n--\nkernel/bpf/core.c-547-\nkernel/bpf/core.c:548:#ifdef CONFIG_BPF_JIT\nkernel/bpf/core.c-549-/* All BPF JIT sysctl knobs here. */\nkernel/bpf/core.c:550:int bpf_jit_enable   __read_mostly = IS_BUILTIN(CONFIG_BPF_JIT_DEFAULT_ON);\nkernel/bpf/core.c:551:int bpf_jit_kallsyms __read_mostly = IS_BUILTIN(CONFIG_BPF_JIT_DEFAULT_ON);\nkernel/bpf/core.c-552-int bpf_jit_harden   __read_mostly;\n--\nkernel/bpf/core.c=1362=static int bpf_jit_blind_insn(const struct bpf_insn *from,\n--\nkernel/bpf/core.c-1370-\nkernel/bpf/core.c:1371:\tBUILD_BUG_ON(BPF_REG_PARAMS + 2 != MAX_BPF_JIT_REG);\nkernel/bpf/core.c:1372:\tBUILD_BUG_ON(BPF_REG_AX + 1 != MAX_BPF_JIT_REG);\nkernel/bpf/core.c-1373-\n--\nkernel/bpf/core.c=1662=u16 bpf_out_stack_arg_cnt(const struct bpf_verifier_env *env, const struct bpf_prog *prog)\n--\nkernel/bpf/core.c-1670-}\nkernel/bpf/core.c:1671:#endif /* CONFIG_BPF_JIT */\nkernel/bpf/core.c-1672-\n--\nkernel/bpf/core.c=1829=bool bpf_opcode_in_insntable(u8 code)\n--\nkernel/bpf/core.c-1852-\nkernel/bpf/core.c:1853:#ifndef CONFIG_BPF_JIT_ALWAYS_ON\nkernel/bpf/core.c-1854-/* Absolute value of s32 without undefined behavior for S32_MIN */\n--\nkernel/bpf/core.c=2544=static unsigned int __bpf_prog_ret0_warn(const void *ctx,\n--\nkernel/bpf/core.c-2546-{\nkernel/bpf/core.c:2547:\t/* If this handler ever gets executed, then BPF_JIT_ALWAYS_ON\nkernel/bpf/core.c-2548-\t * is not working properly, so warn about it!\n--\nkernel/bpf/core.c=2656=static bool bpf_prog_select_interpreter(struct bpf_prog *fp)\n--\nkernel/bpf/core.c-2658-\tbool select_interpreter = false;\nkernel/bpf/core.c:2659:#ifndef CONFIG_BPF_JIT_ALWAYS_ON\nkernel/bpf/core.c-2660-\tu32 stack_depth = max_t(u32, fp-\u003eaux-\u003estack_depth, 1);\n--\nkernel/bpf/core.c=2735=static struct bpf_prog *bpf_prog_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog)\nkernel/bpf/core.c-2736-{\nkernel/bpf/core.c:2737:#ifdef CONFIG_BPF_JIT\nkernel/bpf/core.c-2738-\tstruct bpf_prog *orig_prog;\n--\nkernel/bpf/core.c=3500=u32 bpf_jit_plan_arg_moves(const struct bpf_jit_arg_abi *abi,\n--\nkernel/bpf/core.c-3522-\t\tif (pos \u003c slot) {\nkernel/bpf/core.c:3523:\t\t\tmoves[n].dst = BPF_JIT_ARG_TMP;\nkernel/bpf/core.c-3524-\t\t\tback = slot;\n--\nkernel/bpf/core.c-3533-\t\tmoves[n].dst = pos_of_slot[back];\nkernel/bpf/core.c:3534:\t\tmoves[n].src = BPF_JIT_ARG_TMP;\nkernel/bpf/core.c-3535-\t\tn++;\n--\nkernel/bpf/fixups.c=165=static int add_kfunc_in_insns(struct bpf_verifier_env *env,\n--\nkernel/bpf/fixups.c-179-\nkernel/bpf/fixups.c:180:#ifndef CONFIG_BPF_JIT_ALWAYS_ON\n\n"
}

5/2 2026/10/22 03:52 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 27061
							output: 38
							thoughts: 30
						

Thoughts:
Okay, here's the summary, rendered as a first-person thought process, keeping in mind the user is a BPF expert:

**Investigating BPF Configuration Options**

Alright, I'm diving in. Let's get down to the nitty-gritty and see what's what with these BPF configurations. My first stop? That old reliable `kernel/bpf/Kconfig`. I need to refresh my memory on the specific options. Specifically, I'm interested in how the BPF syscall is configured, naturally. Then, of course, I'm curious about the BPF JIT options. Knowing how the JIT is enabled and tuned is always crucial. I also want to make sure I don't overlook anything else relevant in this context. There could be some subtle but important settings lurking in there that I need to be aware of. Let's see what gems are buried in this file.



6/2 2026/10/22 03:52 tool grepper
0m Args:
{
  "Expression": "config BPF",
  "PathPrefixes": [
    "kernel/bpf/Kconfig",
    "init/Kconfig"
  ]
}

Results:
{
  "Output": "kernel/bpf/Kconfig-3-# BPF interpreter that, for example, classic socket filters depend on.\nkernel/bpf/Kconfig:4:config BPF\nkernel/bpf/Kconfig-5-\tbool\n--\nkernel/bpf/Kconfig=29=menu \"BPF subsystem\"\nkernel/bpf/Kconfig-30-\nkernel/bpf/Kconfig:31:config BPF_SYSCALL\nkernel/bpf/Kconfig-32-\tbool \"Enable bpf() system call\"\n--\nkernel/bpf/Kconfig-45-\nkernel/bpf/Kconfig:46:config BPF_JIT\nkernel/bpf/Kconfig-47-\tbool \"Enable BPF Just In Time compiler\"\n--\nkernel/bpf/Kconfig-61-\nkernel/bpf/Kconfig:62:config BPF_JIT_ALWAYS_ON\nkernel/bpf/Kconfig-63-\tbool \"Permanently enable BPF JIT and remove BPF interpreter\"\n--\nkernel/bpf/Kconfig-72-\nkernel/bpf/Kconfig:73:config BPF_JIT_DEFAULT_ON\nkernel/bpf/Kconfig-74-\tdef_bool ARCH_WANT_DEFAULT_BPF_JIT || BPF_JIT_ALWAYS_ON\n--\nkernel/bpf/Kconfig-76-\nkernel/bpf/Kconfig:77:config BPF_UNPRIV_DEFAULT_OFF\nkernel/bpf/Kconfig-78-\tbool \"Disable unprivileged BPF by default\"\n--\nkernel/bpf/Kconfig-93-\nkernel/bpf/Kconfig:94:config BPF_CRYPTO\nkernel/bpf/Kconfig-95-\tdef_bool y\n--\nkernel/bpf/Kconfig=103=source \"kernel/bpf/preload/Kconfig\"\nkernel/bpf/Kconfig-104-\nkernel/bpf/Kconfig:105:config BPF_LSM\nkernel/bpf/Kconfig-106-\tbool \"Enable BPF LSM Instrumentation\"\n--\nkernel/bpf/Kconfig-116-\nkernel/bpf/Kconfig:117:config BPF_JIT_KASAN\nkernel/bpf/Kconfig-118-\tbool\n"
}

7/2 2026/10/22 03:52 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 27895
							output: 32
							thoughts: 88
						

Thoughts:
Here's my thought process, as it stands:

**BPF Verifier Compilation Path Check**

Okay, so I'm diving into the BPF verifier code and I need to figure out how it's being compiled. My first instinct is to check out `kernel/bpf/verifier.c` directly. I also need to understand its relationship to the build process, so let's see how it's linked into the object files. Ah, here it is: `obj-$(CONFIG_BPF_SYSCALL) += syscall.o verifier.o inode.o helpers.o tnum.o log.o base.o bpf_iter.o map_in_map.o token.o`. Got it, so it looks like `verifier.o` is being included as part of the overall BPF compilation when `CONFIG_BPF_SYSCALL` is enabled, which makes perfect sense. Next, I should take a look at the main `Makefile` in the bpf directory to understand the build flow and dependencies. Let's see what `kernel/bpf/Makefile` tells us.



8/2 2026/10/22 03:52 tool read-file
0m Args:
{
  "File": "kernel/bpf/Makefile",
  "FirstLine": 1,
  "LineCount": 40
}

Results:
{
  "Contents": "   1:\t# SPDX-License-Identifier: GPL-2.0\n   2:\tobj-y := core.o\n   3:\tifneq ($(CONFIG_BPF_JIT_ALWAYS_ON),y)\n   4:\t# ___bpf_prog_run() needs GCSE disabled on x86; see 3193c0836f203 for details\n   5:\tcflags-nogcse-$(CONFIG_X86)$(CONFIG_CC_IS_GCC) := -fno-gcse\n   6:\tendif\n   7:\tCFLAGS_core.o += -Wno-override-init $(cflags-nogcse-yy)\n   8:\t\n   9:\tobj-$(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 loops.o scev.o\n  10:\tobj-$(CONFIG_BPF_SYSCALL) += bpf_iter.o map_iter.o task_iter.o prog_iter.o link_iter.o\n  11:\tobj-$(CONFIG_BPF_SYSCALL) += hashtab.o arraymap.o percpu_freelist.o bpf_lru_list.o lpm_trie.o map_in_map.o bloom_filter.o\n  12:\tobj-$(CONFIG_BPF_SYSCALL) += local_storage.o queue_stack_maps.o ringbuf.o bpf_insn_array.o\n  13:\tobj-$(CONFIG_BPF_SYSCALL) += bpf_local_storage.o bpf_task_storage.o\n  14:\tobj-$(CONFIG_BPF_SYSCALL) += fixups.o cfg.o states.o backtrack.o check_btf.o\n  15:\tobj-${CONFIG_BPF_LSM}\t  += bpf_inode_storage.o\n  16:\tobj-$(CONFIG_BPF_SYSCALL) += disasm.o mprog.o\n  17:\tobj-$(CONFIG_BPF_JIT) += trampoline.o\n  18:\tobj-$(CONFIG_BPF_SYSCALL) += btf.o memalloc.o rqspinlock.o stream.o\n  19:\tifeq ($(CONFIG_MMU)$(CONFIG_64BIT),yy)\n  20:\tobj-$(CONFIG_BPF_SYSCALL) += arena.o range_tree.o\n  21:\tendif\n  22:\tobj-$(CONFIG_BPF_JIT) += dispatcher.o\n  23:\tifeq ($(CONFIG_NET),y)\n  24:\tobj-$(CONFIG_BPF_SYSCALL) += devmap.o\n  25:\tobj-$(CONFIG_BPF_SYSCALL) += cpumap.o\n  26:\tobj-$(CONFIG_BPF_SYSCALL) += offload.o\n  27:\tobj-$(CONFIG_BPF_SYSCALL) += net_namespace.o\n  28:\tobj-$(CONFIG_BPF_SYSCALL) += tcx.o\n  29:\tendif\n  30:\tifeq ($(CONFIG_KEYS),y)\n  31:\tobj-$(CONFIG_BPF_SYSCALL) += keys.o\n  32:\tendif\n  33:\tifeq ($(CONFIG_PERF_EVENTS),y)\n  34:\tobj-$(CONFIG_BPF_SYSCALL) += stackmap.o\n  35:\tendif\n  36:\tifeq ($(CONFIG_CGROUPS),y)\n  37:\tobj-$(CONFIG_BPF_SYSCALL) += cgroup_iter.o bpf_cgrp_storage.o\n  38:\tendif\n  39:\tobj-$(CONFIG_CGROUP_BPF) += cgroup.o\n  40:\tifeq ($(CONFIG_INET),y)\n"
}

9/2 2026/10/22 03:52 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 29231
							output: 40
							thoughts: 286
						

Thoughts:
**My Analysis of the eBPF JIT Compilation Patch**

Okay, so I'm looking at this patch and immediately, I'm zeroing in on the core dependencies. I see that `obj-$(CONFIG_BPF_SYSCALL) += syscall.o verifier.o ...` is crucial. Obviously, that means the `CONFIG_BPF_SYSCALL` option needs to be enabled for things like the verifier and disassembler to work, and for loading eBPF programs using `sys_bpf`. Then, for the x86 JIT compilation, `CONFIG_BPF_JIT` is the key, bringing in `bpf_jit_comp.o` and `bpf_timed_may_goto.o` in `arch/x86/net/Makefile`. Makes perfect sense.

Now, I need to understand the impact of the changes. The patch modifies or adds a few functions, I see. In `arch/x86/net/bpf_jit_comp.c`, there's `emit_hmul_aux`, a static helper, and then `do_jit`. I see that `do_jit` calls `emit_hmul_aux`, specifically when dealing with `BPF_ALU64 | BPF_MUL | BPF_K` and `BPF_X` instructions, triggered by `bpf_insn_is_hmul(insn)`.

Hold on... I need to clarify something. Is `do_jit` static? Where is it called from? I need to check the callers of `do_jit` within `arch/x86/net/bpf_jit_comp.c` to fully grasp its role in this compilation process. That's the next step to confirm my understanding of the patch.



10/2 2026/10/22 03:52 tool grepper
0m Args:
{
  "Expression": "do_jit",
  "PathPrefixes": [
    "arch/x86/net/bpf_jit_comp.c"
  ]
}

Results:
{
  "Output": "arch/x86/net/bpf_jit_comp.c=1844=static int emit_atomic_fetch_rmw(u8 **pprog, struct bpf_insn *insn, u32 dst_reg,\n--\narch/x86/net/bpf_jit_comp.c-1930- *                      of a fetching AND/OR/XOR. A sequence long enough to overflow this\narch/x86/net/bpf_jit_comp.c:1931: *                      field would first have to exceed BPF_MAX_INSN_SIZE, which do_jit()\narch/x86/net/bpf_jit_comp.c-1932- *                      rejects with -EFAULT before the image is used.\n--\narch/x86/net/bpf_jit_comp.c=2593=static int emit_unwind_ret_addr(u8 **pprog, u8 *ip)\n--\narch/x86/net/bpf_jit_comp.c-2608-\narch/x86/net/bpf_jit_comp.c:2609:static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *addrs, u8 *image,\narch/x86/net/bpf_jit_comp.c-2610-\t\t  u8 *rw_image, int oldproglen, struct jit_context *ctx, bool jmp_padding)\n--\narch/x86/net/bpf_jit_comp.c=4942=struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog)\n--\narch/x86/net/bpf_jit_comp.c-5035-\t\t\tpadding = true;\narch/x86/net/bpf_jit_comp.c:5036:\t\tproglen = do_jit(env, prog, addrs, image, rw_image, oldproglen,\narch/x86/net/bpf_jit_comp.c-5037-\t\t\t\t \u0026ctx, padding);\n"
}

11/2 2026/10/22 03:52 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 30492
							output: 34
							thoughts: 1104
						

Thoughts:
**Analyzing BPF JIT Compilation and Verifier Changes**

Okay, so `bpf_int_jit_compile` calls `do_jit`. My immediate task is to figure out what functions were added or modified, focusing on what's relevant to this new functionality and avoiding generic, ubiquitous functions. Let's start with `kernel/bpf/verifier.c`.

Right, I see several changes: `check_alu_fields`, `is_valid_alu_variant`, `adjust_scalar_min_max_vals`, `scalar_min_max_uhmul`, `scalar_min_max_shmul`, and `adjust_reg_min_max_vals`. I need to determine if these are static or inlined, and how specific they are to the core logic. I will skip over functions in other files like `___bpf_prog_run` and `print_bpf_insn` because these are likely ubiquitous.

Then, there are the static inline functions in `include/linux/filter.h`: `bpf_uhmul64`, `bpf_shmul64`, `bpf_is_hmul_variant`, and `bpf_insn_is_hmul`. According to the guidelines, I need to focus on non-static caller functions, not the static inline helpers themselves.

Now, let's look closely at functions in `kernel/bpf/verifier.c`: `scalar_min_max_uhmul` and `scalar_min_max_shmul` and, importantly, `adjust_scalar_min_max_vals`. The last one seems particularly interesting as it handles scalar range tracking. Does it get called for all scalar ALU ops? Yes, but it is specific to BPF verifier ALU analysis. `check_alu_fields`, `is_valid_alu_variant`, and `adjust_reg_min_max_vals` are also worth investigation.

Okay, back to `bpf_int_jit_compile` and `do_jit` in `arch/x86/net/bpf_jit_comp.c`. I see `emit_hmul_aux` and `do_jit`. Ah, `emit_hmul_aux` and `do_jit` are both static, but `bpf_int_jit_compile` is non-static!

So, the key here is to find the non-static functions, and the non-inlined static functions. Even if `do_jit` is static, it could still have a symbol. `bpf_check` is the main entry point for the verifier, but it is broad, so let's look at `check_alu_fields` in `kernel/bpf/verifier.c`.



12/2 2026/10/22 03:53 tool grepper
0m Args:
{
  "Expression": "check_alu_fields",
  "PathPrefixes": [
    "kernel/bpf/verifier.c"
  ]
}

Results:
{
  "Output": "kernel/bpf/verifier.c=21239=static bool is_valid_alu_variant(const struct bpf_insn *insn)\n--\nkernel/bpf/verifier.c-21252-\nkernel/bpf/verifier.c:21253:static int check_alu_fields(struct bpf_verifier_env *env, struct bpf_insn *insn)\nkernel/bpf/verifier.c-21254-{\n--\nkernel/bpf/verifier.c=21387=static int check_insn_fields(struct bpf_verifier_env *env, struct bpf_insn *insn)\n--\nkernel/bpf/verifier.c-21391-\tcase BPF_ALU64:\nkernel/bpf/verifier.c:21392:\t\treturn check_alu_fields(env, insn);\nkernel/bpf/verifier.c-21393-\tcase BPF_LDX:\n"
}

13/2 2026/10/22 03:53 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 32476
							output: 38
							thoughts: 32
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Diving into the BPF Verifier's Scalar Adjustments**

Alright, my internal radar is pinging on something related to the BPF verifier. I need to get my head around how it handles scalar values, specifically within the `adjust_scalar_min_max_vals` function. I have a hunch there's a particular behavior there I need to scrutinize. I'm going to focus my attention on `kernel/bpf/verifier.c`. My primary interest at the moment is understanding the logic around `adjust_scalar_min_max_vals`. From experience, I know that function is where the verifier attempts to refine the known minimum and maximum values of BPF registers based on various instructions. I also want to follow the call chain to see the broader context. I need to see where this function is being called from, which is key. Understanding the callers will illuminate what specific parts of the verification process rely on these scalar value adjustments and how potential issues could propagate. I'll need to look at both the direct calls to the function itself and any indirect calls or uses of its results in the verification logic.



14/2 2026/10/22 03:53 tool grepper
0m Args:
{
  "Expression": "adjust_scalar_min_max_vals",
  "PathPrefixes": [
    "kernel/bpf/verifier.c"
  ]
}

Results:
{
  "Output": "kernel/bpf/verifier.c=17295=static int maybe_fork_scalars(struct bpf_verifier_env *env, struct bpf_insn *insn,\n--\nkernel/bpf/verifier.c-17327- */\nkernel/bpf/verifier.c:17328:static int adjust_scalar_min_max_vals(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c-17329-\t\t\t\t      struct bpf_insn *insn,\n--\nkernel/bpf/verifier.c=17518=static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-17647-\t * For alu32 linked register tracking, we need to check dst_reg's\nkernel/bpf/verifier.c:17648:\t * umax_value before the ALU operation. After adjust_scalar_min_max_vals(),\nkernel/bpf/verifier.c-17649-\t * alu32 ops will have zero-extended the result, making umax_value \u003c= U32_MAX.\n--\nkernel/bpf/verifier.c-17652-\nkernel/bpf/verifier.c:17653:\terr = adjust_scalar_min_max_vals(env, insn, dst_reg, *src_reg);\nkernel/bpf/verifier.c-17654-\tif (err)\n--\nkernel/bpf/verifier.c=17818=static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)\n--\nkernel/bpf/verifier.c-17840-\t\t\terr = check_reg_arg(env, insn-\u003edst_reg, DST_OP_NO_MARK);\nkernel/bpf/verifier.c:17841:\t\t\terr = err ?: adjust_scalar_min_max_vals(env, insn,\nkernel/bpf/verifier.c-17842-\t\t\t\t\t\t\t \u0026regs[insn-\u003edst_reg],\n"
}

15/2 2026/10/22 03:53 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 33335
							output: 35
							thoughts: 1456
						

Thoughts:
**Identifying Key Functions for BPF Multiplication Feature**

Alright, let's break this down systematically. My initial thought is to zero in on the core functions related to this new BPF multiplication feature. First, I need to know where it's implemented. `kernel/bpf/core.c` is the BPF interpreter, and `___bpf_prog_run` is the interpreter's main entry point. However, this could be a ubiquitous hot-path if the JIT is not enabled, and that's something I need to avoid listing based on the guidelines. It *is* called by non-JIT'd BPF, but it handles a lot of generic interpreter tasks, making it less specific.

Next, I'll turn my attention to the verifier at `kernel/bpf/verifier.c`. Here, I've identified several functions that seem directly relevant: `check_alu_fields`, `adjust_scalar_min_max_vals`, `scalar_min_max_uhmul`, `scalar_min_max_shmul`, and `adjust_reg_min_max_vals`. Looking at `kernel/bpf/verifier.c`, I see that `scalar_min_max_uhmul` and `scalar_min_max_shmul` are static, and thus may be inlined. However, `adjust_scalar_min_max_vals` is static, large, and likely *not* inlined, or perhaps only called from `adjust_reg_min_max_vals` or `check_alu_op`. `check_alu_fields` is also a clear target, being called from `check_insn_fields`.

Now, let's explore the JIT compilation side in `arch/x86/net/bpf_jit_comp.c`. The two prominent functions here are `emit_hmul_aux` and `do_jit`. `do_jit` is massive, and it's unlikely it'll be inlined into any function.

I need to confirm that these functions are indeed the key ones for the feature. Let's think: are `scalar_min_max_uhmul` and `scalar_min_max_shmul` inlined? If they are small enough, GCC *might* inline them into `adjust_scalar_min_max_vals`, making the latter more significant. However, `adjust_scalar_min_max_vals` is called in only two places, so there's less of a chance of it being inlined, and even if so, it's still specifically created for this feature.

Now, let's focus on `emit_hmul_aux`, since this is new in this patch. Let's see how many lines `emit_hmul_aux` has: it should be a distinct symbol if not inlined. It seems that `do_jit` calls `emit_hmul_aux` in two cases, specifically for the BPF_ALU MUL instruction. That suggests it's unlikely to be inlined and is key. `scalar_min_max_uhmul`, `scalar_min_max_shmul` and `check_alu_fields` and `adjust_scalar_min_max_vals` are also highly specific.

Now, about the configurations. To use all of this I need, at a minimum, `BPF_SYSCALL` for the `bpf()` system call and related verifier operations and `BPF_JIT` for the x86 JIT compilation. Let's confirm any dependencies for `BPF_SYSCALL` in the `Kconfig`.



16/2 2026/10/22 03:53 tool grepper
0m Args:
{
  "Expression": "config BPF_SYSCALL",
  "PathPrefixes": [
    "kernel/bpf/Kconfig"
  ]
}

Results:
{
  "Output": "kernel/bpf/Kconfig=29=menu \"BPF subsystem\"\nkernel/bpf/Kconfig-30-\nkernel/bpf/Kconfig:31:config BPF_SYSCALL\nkernel/bpf/Kconfig-32-\tbool \"Enable bpf() system call\"\n"
}

17/2 2026/10/22 03:53 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 35689
							output: 34
							thoughts: 27
						

Thoughts:
Okay, let's dive into this. Here's how I'd approach that kernel configuration file analysis:

**Analyzing Kernel BPF Configuration**

Alright, so I need to understand the dependencies and selections within `kernel/bpf/Kconfig`. Specifically, I want to zero in around line 31. *My* first thought is to quickly understand the structure of this section. I'll need to look for `depends on` statements, which are crucial to establishing the *prerequisites* for this BPF configuration option. Then I'll be focused on `select` statements. I need to note any other kernel features this BPF configuration explicitly links to. Understanding the interconnections is key; as an expert, I know that even a seemingly isolated option can have cascading effects on the rest of the kernel, potentially impacting the feature space or kernel build time. I'll have to keep an eye on how this config option interacts with any related modules or sub-systems.



18/2 2026/10/22 03:53 tool read-file
0m Args:
{
  "File": "kernel/bpf/Kconfig",
  "FirstLine": 30,
  "LineCount": 35
}

Results:
{
  "Contents": "  30:\t\n  31:\tconfig BPF_SYSCALL\n  32:\t\tbool \"Enable bpf() system call\"\n  33:\t\tselect BPF\n  34:\t\tselect IRQ_WORK\n  35:\t\tselect NEED_TASKS_RCU\n  36:\t\tselect TASKS_TRACE_RCU\n  37:\t\tselect BINARY_PRINTF\n  38:\t\tselect NET_SOCK_MSG if NET\n  39:\t\tselect NET_XGRESS if NET\n  40:\t\tselect PAGE_POOL if NET\n  41:\t\tdefault n\n  42:\t\thelp\n  43:\t\t  Enable the bpf() system call that allows to manipulate BPF programs\n  44:\t\t  and maps via file descriptors.\n  45:\t\n  46:\tconfig BPF_JIT\n  47:\t\tbool \"Enable BPF Just In Time compiler\"\n  48:\t\tdepends on BPF\n  49:\t\tdepends on HAVE_CBPF_JIT || HAVE_EBPF_JIT\n  50:\t\tselect EXECMEM\n  51:\t\thelp\n  52:\t\t  BPF programs are normally handled by a BPF interpreter. This option\n  53:\t\t  allows the kernel to generate native code when a program is loaded\n  54:\t\t  into the kernel. This will significantly speed-up processing of BPF\n  55:\t\t  programs.\n  56:\t\n  57:\t\t  Note, an admin should enable this feature changing:\n  58:\t\t  /proc/sys/net/core/bpf_jit_enable\n  59:\t\t  /proc/sys/net/core/bpf_jit_harden   (optional)\n  60:\t\t  /proc/sys/net/core/bpf_jit_kallsyms (optional)\n  61:\t\n  62:\tconfig BPF_JIT_ALWAYS_ON\n  63:\t\tbool \"Permanently enable BPF JIT and remove BPF interpreter\"\n  64:\t\tdepends on BPF_SYSCALL \u0026\u0026 HAVE_EBPF_JIT \u0026\u0026 BPF_JIT\n"
}

19/2 2026/10/22 03:53 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 36475
							output: 195
							thoughts: 1656
						

Thoughts:
**BPF UHMUL/SHMUL Instruction Patch Analysis**

Okay, so I've been digging into this patch adding UHMUL and SHMUL instructions to BPF. First, I need to make sure I understand the core configuration dependencies. `BPF_SYSCALL` is fundamental, no surprises there. And `BPF_JIT` obviously depends on `BPF` and `HAVE_CBPF_JIT || HAVE_EBPF_JIT` which is good because x86 has that. Are there any other essential configurations that I am missing?

I need to confirm if anything like `BPF_ARENA` plays a role, so I should look into the arena pointer checks within `adjust_reg_min_max_vals`. Ah, that's straightforward: the verifier explicitly rejects arena pointers with UHMUL/SHMUL via `EOPNOTSUPP`, so that is not an issue. Let me focus on `BPF_SYSCALL` and `BPF_JIT` again. Yes, those two are the main configs, enabling the verifier, the `bpf()` syscall and the JIT compiler.

Now, let's go over the patch in detail to make sure nothing has been missed:
- Target architecture: amd64. Got it.
- Changes: The patch adds specifications to `instruction-set.rst`, emits x86 assembly via `mul` or `imul` in the JIT compiler, does verifier updates, enables the interpreter, adds disassembler symbols and includes helper math functions to `filter.h`
- Other architectures: Either add support for the instructions (arm64, riscv), or reject them, which is perfect for portability.
- There are also self-tests.
This means that this patch is complete, and ready for review.

Reachability: It works on amd64, and can be used from userspace through the `bpf()` syscall, which means this is fuzzable. Yes, definitely fuzzable.

FocusSymbols: What about the focus symbols? Let's go through the code and figure this out.
- `emit_hmul_aux`
- `scalar_min_max_uhmul`
- `scalar_min_max_shmul`
- `adjust_scalar_min_max_vals`
- `check_alu_fields`
- `___bpf_prog_run`

Well, is `___bpf_prog_run` needed? No, that's just the interpreter entry point. But `adjust_scalar_min_max_vals`, `check_alu_fields` and `emit_hmul_aux` are highly targeted, so let's keep those.
Wait, since those are static functions, could `scalar_min_max_uhmul` and `scalar_min_max_shmul` be inlined into `adjust_scalar_min_max_vals`? Yes, they are small static functions, so the caller to focus on is `adjust_scalar_min_max_vals`. And `emit_hmul_aux` is called twice in `do_jit`. So the list now includes:
- `emit_hmul_aux`
- `adjust_scalar_min_max_vals`
- `scalar_min_max_uhmul`
- `scalar_min_max_shmul`
- `check_alu_fields`
And `is_valid_alu_variant` is only called by `check_alu_fields`, so it is already included.

Let's check for other symbols:
`bpf_uhmul64`, `bpf_shmul64`, `bpf_is_hmul_variant`, `bpf_insn_is_hmul` are static inline functions in the header. The callers are: `___bpf_prog_run` (interpreter), `scalar_min_max_uhmul` and `scalar_min_max_shmul`, `check_alu_fields` (in the verifier) and `emit_hmul_aux` / `do_jit` (in the JIT compiler).

Okay, so we have the required configurations and the focus symbols.

Reasoning: The patch adds UHMUL/SHMUL to BPF. It updates the verifier, JIT, and interpreter and affects code reachable via the `bpf()` syscall. This is a high-value target for fuzzing.

I'm confident. Time to call `set-results`.



20/2 2026/10/22 03:53 tool set-results
0m Args:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "BPF_JIT"
  ],
  "FocusSymbols": [
    "emit_hmul_aux",
    "adjust_scalar_min_max_vals",
    "scalar_min_max_uhmul",
    "scalar_min_max_shmul",
    "check_alu_fields"
  ],
  "Reasoning": "The patch introduces two new 64-bit BPF ALU multiplication instructions (UHMUL for unsigned high-half multiplication and SHMUL for signed high-half multiplication). The changes implement verifier validation, scalar range tracking, x86_64 JIT compilation, and interpreter support. These are core BPF subsystem components directly reachable from userspace via the bpf() syscall and warrant fuzzing for verifier correctness, range tracking bugs, and JIT code generation issues.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "BPF_JIT"
  ],
  "FocusSymbols": [
    "emit_hmul_aux",
    "adjust_scalar_min_max_vals",
    "scalar_min_max_uhmul",
    "scalar_min_max_shmul",
    "check_alu_fields"
  ],
  "Reasoning": "The patch introduces two new 64-bit BPF ALU multiplication instructions (UHMUL for unsigned high-half multiplication and SHMUL for signed high-half multiplication). The changes implement verifier validation, scalar range tracking, x86_64 JIT compilation, and interpreter support. These are core BPF subsystem components directly reachable from userspace via the bpf() syscall and warrant fuzzing for verifier correctness, range tracking bugs, and JIT code generation issues.",
  "WorthFuzzing": true
}

21/1 2026/10/22 03:53 action If
0m Args:
{
  "WorthFuzzing": true
}

Results:
null

22/2 2026/10/22 03:53 agent kmsan-evaluator
0m Model:
core

Args:
null

Results:
{
  "KMSANReasoning": "The patch series introduces BPF support for unsigned and signed 64-bit high-half multiplication instructions (UHMUL and SHMUL). It adds instruction definitions to the BPF specification, handles them in the BPF interpreter and disassembler, adds scalar constant-folding and arena-pointer rejection in the BPF verifier, and implements JIT support across architectures (x86_64, arm64, riscv64) while returning -EOPNOTSUPP on unsupported architectures.\n\nRegarding memory safety:\n1. No kernel structures or buffers are copied to user space (no copy_to_user, put_user, netlink skb attributes, ioctls, or info-leaks).\n2. All variables in the new math helpers (bpf_uhmul64, bpf_shmul64), interpreter routines, and JIT emission functions are fully initialized.\n3. The BPF verifier treats UHMUL/SHMUL on non-constant registers by setting them to unknown (__mark_reg_unknown) and marks known constants via ___mark_reg_known; arena pointer operations are rejected. No uninitialized register state or buffer length confusion is created.\n4. Any potential issues with JIT code generation, register clobbering, or verifier scalar tracking are either logic bugs, execution failures, or memory corruption/out-of-bounds accesses, which are covered by standard kernel testing and KASAN.\n\nBecause the patch does not introduce or expose uninitialized memory reads or information leaks, a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false
}

Instruction:
You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.

CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
  a comprehensive suite of debugging tools and sanitizers, including KASAN
  (out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
  (locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
  or page allocations) and kernel-to-user memory info-leaks.

Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.

Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.

Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
   attributes, ioctl output arguments, socket options, or BPF buffers) where fields
   or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
   or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
   could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
   uninitialized bytes of existing buffers.

Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
  dereferences, locking deadlocks, or use-after-free bugs (these are already caught
  by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
  or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.

Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit b4427bda51c386dff85db43d5a27629e6188444f
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Oct 22 03:52:31 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/bpf/standardization/instruction-set.rst b/Documentation/bpf/standardization/instruction-set.rst
index 39c74611752b6..6f3cb6bcaa1a6 100644
--- a/Documentation/bpf/standardization/instruction-set.rst
+++ b/Documentation/bpf/standardization/instruction-set.rst
@@ -142,8 +142,9 @@ This document defines the following conformance groups:
 * atomic32: includes 32-bit atomic operation instructions (see `Atomic operations`_).
 * atomic64: includes atomic32, plus 64-bit atomic operation instructions.
 * divmul32: includes 32-bit division, multiplication, and modulo instructions.
-* divmul64: includes divmul32, plus 64-bit division, multiplication,
+* divmul64: includes divmul32, plus 64-bit division, low-half multiplication,
   and modulo instructions.
+* divmul64v2: includes divmul64, plus high-half multiplication instructions.
 * packet: deprecated packet access instructions.
 
 Instruction encoding
@@ -330,6 +331,8 @@ register.
   ADD    0x0    0        dst += src
   SUB    0x1    0        dst -= src
   MUL    0x2    0        dst \*= src
+  UHMUL  0x2    1        ``dst uh*= src`` (unsigned high half, ``ALU64`` only)
+  SHMUL  0x2    2        ``dst sh*= src`` (signed high half, ``ALU64`` only)
   DIV    0x3    0        dst = (src != 0) ? (dst / src) : 0
   SDIV   0x3    1        dst = (src == 0) ? 0 : ((src == -1 && dst == LLONG_MIN) ? LLONG_MIN : (dst s/ src))
   OR     0x4    0        dst \|= src
@@ -379,13 +382,33 @@ where '(u32)' indicates that the upper 32 bits are zeroed.
 
   dst = dst ^ imm
 
-Note that most arithmetic instructions have 'offset' set to 0. Only three instructions
-(``SDIV``, ``SMOD``, ``MOVSX``) have a non-zero 'offset'.
+Note that most arithmetic instructions have 'offset' set to 0. The following
+instructions may have a non-zero 'offset':
+
+* ``SDIV`` and ``SMOD``: ``offset == 1`` selects the signed variant.
+* ``MOVSX``: ``offset`` selects the source operand width.
+* ``MUL``: ``offset`` selects the multiplication variant (see below).
+
+Division, low-half multiplication (``MUL`` with ``offset == 0``), and
+modulo operations for ``ALU`` are part of the "divmul32" conformance
+group, and their ``ALU64`` variants are part of the "divmul64"
+conformance group.
+
+For ``ALU64`` multiplication, ``offset == 1`` selects unsigned
+high-half multiplication (``UHMUL``), and ``offset == 2`` selects
+signed high-half multiplication (``SHMUL``). Both instructions belong
+to the "divmul64v2" conformance group.
+
+For ``X`` mode, ``src64`` is the 64-bit value in ``src_reg``. For
+``K`` mode, ``src64`` is ``imm`` sign-extended from 32 to 64 bits.
+The operations are::
+
+  UHMUL: dst = (u64)(((u128)dst * (u128)(u64)src64) >> 64)
+  SHMUL: dst = (u64)(((s128)(s64)dst * (s128)(s64)src64) >> 64)
+
+For ``ALU`` multiplication, only ``offset == 0`` is valid. For ``ALU64``
+multiplication, offset values other than 0, 1, and 2 are reserved.
 
-Division, multiplication, and modulo operations for ``ALU`` are part
-of the "divmul32" conformance group, and division, multiplication, and
-modulo operations for ``ALU64`` are part of the "divmul64" conformance
-group.
 The division and modulo operations support both unsigned and signed flavors.
 
 For unsigned operations (``DIV`` and ``MOD``), for ``ALU``,
diff --git a/arch/arc/net/bpf_jit_core.c b/arch/arc/net/bpf_jit_core.c
index 639a2736f029e..78b1819cee5a6 100644
--- a/arch/arc/net/bpf_jit_core.c
+++ b/arch/arc/net/bpf_jit_core.c
@@ -734,6 +734,9 @@ static int handle_insn(struct jit_context *ctx, u32 idx)
 	u8  len = 0;
 	int ret = 0;
 
+	if (bpf_insn_is_hmul(insn))
+		return -EOPNOTSUPP;
+
 	switch (code) {
 	/* dst += src (32-bit) */
 	case BPF_ALU | BPF_ADD | BPF_X:
diff --git a/arch/arm/net/bpf_jit_32.c b/arch/arm/net/bpf_jit_32.c
index 9ede81afbc50e..aa466fc79d964 100644
--- a/arch/arm/net/bpf_jit_32.c
+++ b/arch/arm/net/bpf_jit_32.c
@@ -1611,6 +1611,9 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx)
 } while (0)
 #define check_imm24(imm) check_imm(24, imm)
 
+	if (bpf_insn_is_hmul(insn))
+		goto notyet;
+
 	switch (code) {
 	/* ALU operations */
 
diff --git a/arch/arm64/include/asm/insn.h b/arch/arm64/include/asm/insn.h
index 1ce75a4426387..f38738fa54a27 100644
--- a/arch/arm64/include/asm/insn.h
+++ b/arch/arm64/include/asm/insn.h
@@ -247,6 +247,8 @@ enum aarch64_insn_data2_type {
 enum aarch64_insn_data3_type {
 	AARCH64_INSN_DATA3_MADD,
 	AARCH64_INSN_DATA3_MSUB,
+	AARCH64_INSN_DATA3_SMULH,
+	AARCH64_INSN_DATA3_UMULH,
 };
 
 enum aarch64_insn_logic_type {
@@ -396,6 +398,8 @@ __AARCH64_INSN_FUNCS(sub_ext,	0x7FE00000, 0x4B200000)
 __AARCH64_INSN_FUNCS(subs_ext,	0x7FE00000, 0x6B200000)
 __AARCH64_INSN_FUNCS(madd,	0x7FE08000, 0x1B000000)
 __AARCH64_INSN_FUNCS(msub,	0x7FE08000, 0x1B008000)
+__AARCH64_INSN_FUNCS(smulh,	0x7FE0FC00, 0x1B407C00)
+__AARCH64_INSN_FUNCS(umulh,	0x7FE0FC00, 0x1BC07C00)
 __AARCH64_INSN_FUNCS(udiv,	0x7FE0FC00, 0x1AC00800)
 __AARCH64_INSN_FUNCS(sdiv,	0x7FE0FC00, 0x1AC00C00)
 __AARCH64_INSN_FUNCS(lslv,	0x7FE0FC00, 0x1AC02000)
diff --git a/arch/arm64/lib/insn.c b/arch/arm64/lib/insn.c
index e70ac02385153..dc483d1b8bd54 100644
--- a/arch/arm64/lib/insn.c
+++ b/arch/arm64/lib/insn.c
@@ -1155,6 +1155,12 @@ u32 aarch64_insn_gen_data3(enum aarch64_insn_register dst,
 	case AARCH64_INSN_DATA3_MSUB:
 		insn = aarch64_insn_get_msub_value();
 		break;
+	case AARCH64_INSN_DATA3_SMULH:
+		insn = aarch64_insn_get_smulh_value();
+		break;
+	case AARCH64_INSN_DATA3_UMULH:
+		insn = aarch64_insn_get_umulh_value();
+		break;
 	default:
 		pr_err("%s: unknown data3 encoding %d\n", __func__, type);
 		return AARCH64_BREAK_FAULT;
diff --git a/arch/arm64/net/bpf_jit.h b/arch/arm64/net/bpf_jit.h
index b2fe6e6dcf447..70fc329c661db 100644
--- a/arch/arm64/net/bpf_jit.h
+++ b/arch/arm64/net/bpf_jit.h
@@ -281,6 +281,12 @@
 	A64_VARIANT(sf), AARCH64_INSN_DATA3_MSUB)
 /* Rd = Rn * Rm */
 #define A64_MUL(sf, Rd, Rn, Rm) A64_MADD(sf, Rd, A64_ZR, Rn, Rm)
+/* Rd = high 64 bits of signed Rn * Rm */
+#define A64_SMULH(Rd, Rn, Rm) aarch64_insn_gen_data3(Rd, A64_ZR, Rn, Rm, \
+	AARCH64_INSN_VARIANT_64BIT, AARCH64_INSN_DATA3_SMULH)
+/* Rd = high 64 bits of unsigned Rn * Rm */
+#define A64_UMULH(Rd, Rn, Rm) aarch64_insn_gen_data3(Rd, A64_ZR, Rn, Rm, \
+	AARCH64_INSN_VARIANT_64BIT, AARCH64_INSN_DATA3_UMULH)
 
 /* Logical (shifted register) */
 #define A64_LOGIC_SREG(sf, Rd, Rn, Rm, type) \
diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c
index 326e9e1dea0a7..a9989cd5098cc 100644
--- a/arch/arm64/net/bpf_jit_comp.c
+++ b/arch/arm64/net/bpf_jit_comp.c
@@ -144,6 +144,17 @@ static inline void emit_a64_mov_i(const int is64, const int reg,
 	}
 }
 
+static inline void emit_a64_mul(const bool is64, const u8 dst, const u8 src,
+				const s16 off, struct jit_ctx *ctx)
+{
+	if (is64 && off == BPF_UHMUL)
+		emit(A64_UMULH(dst, dst, src), ctx);
+	else if (is64 && off == BPF_SHMUL)
+		emit(A64_SMULH(dst, dst, src), ctx);
+	else
+		emit(A64_MUL(is64, dst, dst, src), ctx);
+}
+
 static int i64_i16_blocks(const u64 val, bool inverse)
 {
 	return (((val >>  0) & 0xffff) != (inverse ? 0xffff : 0x0000)) +
@@ -1531,7 +1542,7 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn
 		break;
 	case BPF_ALU | BPF_MUL | BPF_X:
 	case BPF_ALU64 | BPF_MUL | BPF_X:
-		emit(A64_MUL(is64, dst, dst, src), ctx);
+		emit_a64_mul(is64, dst, src, off, ctx);
 		break;
 	case BPF_ALU | BPF_DIV | BPF_X:
 	case BPF_ALU64 | BPF_DIV | BPF_X:
@@ -1660,7 +1671,7 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn
 	case BPF_ALU | BPF_MUL | BPF_K:
 	case BPF_ALU64 | BPF_MUL | BPF_K:
 		emit_a64_mov_i(is64, tmp, imm, ctx);
-		emit(A64_MUL(is64, dst, dst, tmp), ctx);
+		emit_a64_mul(is64, dst, tmp, off, ctx);
 		break;
 	case BPF_ALU | BPF_DIV | BPF_K:
 	case BPF_ALU64 | BPF_DIV | BPF_K:
diff --git a/arch/loongarch/net/bpf_jit.c b/arch/loongarch/net/bpf_jit.c
index 9393e4b0db8e1..71bd518ceaf32 100644
--- a/arch/loongarch/net/bpf_jit.c
+++ b/arch/loongarch/net/bpf_jit.c
@@ -709,6 +709,9 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext
 	const s32 imm = insn->imm;
 	const bool is32 = BPF_CLASS(insn->code) == BPF_ALU || BPF_CLASS(insn->code) == BPF_JMP32;
 
+	if (bpf_insn_is_hmul(insn))
+		return -EOPNOTSUPP;
+
 	switch (code) {
 	/* dst = src */
 	case BPF_ALU | BPF_MOV | BPF_X:
diff --git a/arch/mips/net/bpf_jit_comp32.c b/arch/mips/net/bpf_jit_comp32.c
index 84d9c7036e4f0..4a8bedddf61be 100644
--- a/arch/mips/net/bpf_jit_comp32.c
+++ b/arch/mips/net/bpf_jit_comp32.c
@@ -1544,6 +1544,9 @@ int build_insn(const struct bpf_insn *insn, struct jit_context *ctx)
 	s32 val, rel;
 	u8 alu, jmp;
 
+	if (bpf_insn_is_hmul(insn))
+		goto notyet;
+
 	switch (code) {
 	/* ALU operations */
 	/* dst = imm */
diff --git a/arch/mips/net/bpf_jit_comp64.c b/arch/mips/net/bpf_jit_comp64.c
index 994589c3de46c..75202cac31f47 100644
--- a/arch/mips/net/bpf_jit_comp64.c
+++ b/arch/mips/net/bpf_jit_comp64.c
@@ -719,6 +719,9 @@ int build_insn(const struct bpf_insn *insn, struct jit_context *ctx)
 	s32 val, rel;
 	u8 alu, jmp;
 
+	if (bpf_insn_is_hmul(insn))
+		goto notyet;
+
 	switch (code) {
 	/* ALU operations */
 	/* dst = imm */
diff --git a/arch/parisc/net/bpf_jit_comp32.c b/arch/parisc/net/bpf_jit_comp32.c
index eea0a34a3bfee..294eee24f2d9d 100644
--- a/arch/parisc/net/bpf_jit_comp32.c
+++ b/arch/parisc/net/bpf_jit_comp32.c
@@ -1142,6 +1142,9 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct hppa_jit_context *ctx,
 		BPF_CLASS(code), code, (code & BPF_ALU64) ? 1:0, BPF_SIZE(code),
 		BPF_OP(code), insn->src_reg, insn->dst_reg);
 
+	if (bpf_insn_is_hmul(insn))
+		return -EOPNOTSUPP;
+
 	switch (code) {
 	/* dst = src */
 	case BPF_ALU64 | BPF_MOV | BPF_X:
diff --git a/arch/parisc/net/bpf_jit_comp64.c b/arch/parisc/net/bpf_jit_comp64.c
index c326fa737ec8c..319bd28f1e5b6 100644
--- a/arch/parisc/net/bpf_jit_comp64.c
+++ b/arch/parisc/net/bpf_jit_comp64.c
@@ -610,6 +610,9 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct hppa_jit_context *ctx,
 
 	init_regs(&rd, &rs, insn, ctx);
 
+	if (bpf_insn_is_hmul(insn))
+		return -EOPNOTSUPP;
+
 	switch (code) {
 	/* dst = src */
 	case BPF_ALU | BPF_MOV | BPF_X:
diff --git a/arch/powerpc/net/bpf_jit_comp32.c b/arch/powerpc/net/bpf_jit_comp32.c
index bfdc50740da8e..6bfc97a029d73 100644
--- a/arch/powerpc/net/bpf_jit_comp32.c
+++ b/arch/powerpc/net/bpf_jit_comp32.c
@@ -349,6 +349,9 @@ int bpf_jit_build_body(struct bpf_prog *fp, u32 *image, u32 *fimage, struct code
 		u32 true_cond;
 		u32 tmp_idx;
 
+		if (bpf_insn_is_hmul(&insn[i]))
+			return -EOPNOTSUPP;
+
 		if (i && (BPF_CLASS(code) == BPF_ALU64 || BPF_CLASS(code) == BPF_ALU) &&
 		    (BPF_CLASS(prevcode) == BPF_ALU64 || BPF_CLASS(prevcode) == BPF_ALU) &&
 		    BPF_OP(prevcode) == BPF_MOV && BPF_SRC(prevcode) == BPF_X &&
diff --git a/arch/powerpc/net/bpf_jit_comp64.c b/arch/powerpc/net/bpf_jit_comp64.c
index fc9db691e8203..192c96e0ed34a 100644
--- a/arch/powerpc/net/bpf_jit_comp64.c
+++ b/arch/powerpc/net/bpf_jit_comp64.c
@@ -992,6 +992,9 @@ int bpf_jit_build_body(struct bpf_prog *fp, u32 *image, u32 *fimage, struct code
 		u32 tmp_idx;
 		u32 jmp_off;
 
+		if (bpf_insn_is_hmul(&insn[i]))
+			return -EOPNOTSUPP;
+
 		/*
 		 * addrs[] maps a BPF bytecode address into a real offset from
 		 * the start of the body code.
diff --git a/arch/riscv/net/bpf_jit.h b/arch/riscv/net/bpf_jit.h
index 5c4f53060c918..21a190a6a6d99 100644
--- a/arch/riscv/net/bpf_jit.h
+++ b/arch/riscv/net/bpf_jit.h
@@ -436,6 +436,11 @@ static inline u32 rv_mul(u8 rd, u8 rs1, u8 rs2)
 	return rv_r_insn(1, rs2, rs1, 0, rd, 0x33);
 }
 
+static inline u32 rv_mulh(u8 rd, u8 rs1, u8 rs2)
+{
+	return rv_r_insn(1, rs2, rs1, 1, rd, 0x33);
+}
+
 static inline u32 rv_mulhu(u8 rd, u8 rs1, u8 rs2)
 {
 	return rv_r_insn(1, rs2, rs1, 3, rd, 0x33);
diff --git a/arch/riscv/net/bpf_jit_comp32.c b/arch/riscv/net/bpf_jit_comp32.c
index a9e0bd5cc81d2..64bc6779d25c7 100644
--- a/arch/riscv/net/bpf_jit_comp32.c
+++ b/arch/riscv/net/bpf_jit_comp32.c
@@ -1014,6 +1014,9 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,
 	const s8 *tmp1 = bpf2rv32[TMP_REG_1];
 	const s8 *tmp2 = bpf2rv32[TMP_REG_2];
 
+	if (bpf_insn_is_hmul(insn))
+		goto notsupported;
+
 	switch (code) {
 	case BPF_ALU64 | BPF_MOV | BPF_X:
 		if (insn->off != 0) {
diff --git a/arch/riscv/net/bpf_jit_comp64.c b/arch/riscv/net/bpf_jit_comp64.c
index 788fcc21fd5b8..dfaf85926f204 100644
--- a/arch/riscv/net/bpf_jit_comp64.c
+++ b/arch/riscv/net/bpf_jit_comp64.c
@@ -197,6 +197,19 @@ static void emit_imm(u8 rd, s64 val, struct rv_jit_context *ctx)
 		emit_addi(rd, rd, lower, ctx);
 }
 
+static void emit_mul(u8 rd, u8 rs, bool is64, s16 off,
+		     struct rv_jit_context *ctx)
+{
+	if (!is64)
+		emit(rv_mulw(rd, rd, rs), ctx);
+	else if (off == BPF_UHMUL)
+		emit(rv_mulhu(rd, rd, rs), ctx);
+	else if (off == BPF_SHMUL)
+		emit(rv_mulh(rd, rd, rs), ctx);
+	else
+		emit(rv_mul(rd, rd, rs), ctx);
+}
+
 static void __build_epilogue(bool is_tail_call, struct rv_jit_context *ctx)
 {
 	int stack_adjust = ctx->stack_size, store_offset = stack_adjust - 8;
@@ -1526,7 +1539,7 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,
 		break;
 	case BPF_ALU | BPF_MUL | BPF_X:
 	case BPF_ALU64 | BPF_MUL | BPF_X:
-		emit(is64 ? rv_mul(rd, rd, rs) : rv_mulw(rd, rd, rs), ctx);
+		emit_mul(rd, rs, is64, off, ctx);
 		if (!is64 && !aux->verifier_zext)
 			emit_zextw(rd, rd, ctx);
 		break;
@@ -1662,8 +1675,7 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,
 	case BPF_ALU | BPF_MUL | BPF_K:
 	case BPF_ALU64 | BPF_MUL | BPF_K:
 		emit_imm(RV_REG_T1, imm, ctx);
-		emit(is64 ? rv_mul(rd, rd, RV_REG_T1) :
-		     rv_mulw(rd, rd, RV_REG_T1), ctx);
+		emit_mul(rd, RV_REG_T1, is64, off, ctx);
 		if (!is64 && !aux->verifier_zext)
 			emit_zextw(rd, rd, ctx);
 		break;
diff --git a/arch/s390/net/bpf_jit_comp.c b/arch/s390/net/bpf_jit_comp.c
index 8eaaac7028a16..35af9a32294a8 100644
--- a/arch/s390/net/bpf_jit_comp.c
+++ b/arch/s390/net/bpf_jit_comp.c
@@ -993,6 +993,9 @@ static noinline int bpf_jit_insn(struct bpf_jit *jit, struct bpf_prog *fp,
 
 	bpf_jit_probe_init(&probe);
 
+	if (bpf_insn_is_hmul(insn))
+		return -EOPNOTSUPP;
+
 	switch (insn->code) {
 	/*
 	 * BPF_MOV
diff --git a/arch/sparc/net/bpf_jit_comp_64.c b/arch/sparc/net/bpf_jit_comp_64.c
index 2fa0e93751276..1154ddc515b0f 100644
--- a/arch/sparc/net/bpf_jit_comp_64.c
+++ b/arch/sparc/net/bpf_jit_comp_64.c
@@ -901,6 +901,9 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx)
 	const s16 off = insn->off;
 	const s32 imm = insn->imm;
 
+	if (bpf_insn_is_hmul(insn))
+		return -EOPNOTSUPP;
+
 	if (insn->src_reg == BPF_REG_FP)
 		ctx->saw_frame_pointer = true;
 
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 6a2fe3d0039d3..53f38e21f7f3d 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -1378,6 +1378,38 @@ static int emit_kasan_check(struct bpf_verifier_env *env, u8 **pprog,
 	return 0;
 }
 
+/*
+ * The one-operand x86 mul/imul forms use RAX as the implicit first
+ * operand and store the 128-bit product in RDX:RAX.
+ * The JIT uses AUX_REG as the explicit second operand.
+ */
+static void emit_hmul_aux(u8 **pprog, u32 dst_reg, bool is_signed)
+{
+	u8 *prog = *pprog;
+	bool save_rax = dst_reg != BPF_REG_0;
+	bool save_rdx = dst_reg != BPF_REG_3;
+	u8 mul_modrm_base = is_signed ? 0xE8 : 0xE0; /* imul /5 or mul /4 */
+
+	if (save_rax)
+		EMIT1(0x50); /* push rax */
+	if (save_rdx)
+		EMIT1(0x52); /* push rdx */
+
+	if (save_rax)
+		emit_mov_reg(&prog, true, BPF_REG_0, dst_reg);
+	maybe_emit_1mod(&prog, AUX_REG, true);
+	EMIT2(0xF7, add_1reg(mul_modrm_base, AUX_REG));
+	if (save_rdx)
+		emit_mov_reg(&prog, true, dst_reg, BPF_REG_3);
+
+	if (save_rdx)
+		EMIT1(0x5A); /* pop rdx */
+	if (save_rax)
+		EMIT1(0x58); /* pop rax */
+
+	*pprog = prog;
+}
+
 /* LDX: dst_reg = *(u8*)(src_reg + off) */
 static void emit_ldx(u8 **pprog, u32 size, u32 dst_reg, u32 src_reg, int off)
 {
@@ -2916,6 +2948,14 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
 
 		case BPF_ALU | BPF_MUL | BPF_K:
 		case BPF_ALU64 | BPF_MUL | BPF_K:
+			if (bpf_insn_is_hmul(insn)) {
+				/* imul/mul use AUX_REG as src. */
+				emit_mov_imm32(&prog, true, AUX_REG, imm32);
+				emit_hmul_aux(&prog, dst_reg,
+					      insn->off == BPF_SHMUL);
+				break;
+			}
+
 			/*
 			 * lea for 3, 5 and 9, which LLVM leaves as a
 			 * multiplication, but not for r13, which needs a
@@ -2951,6 +2991,14 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
 
 		case BPF_ALU | BPF_MUL | BPF_X:
 		case BPF_ALU64 | BPF_MUL | BPF_X:
+			if (bpf_insn_is_hmul(insn)) {
+				/* imul/mul use AUX_REG as src. */
+				emit_mov_reg(&prog, true, AUX_REG, src_reg);
+				emit_hmul_aux(&prog, dst_reg,
+					      insn->off == BPF_SHMUL);
+				break;
+			}
+
 			maybe_emit_mod(&prog, src_reg, dst_reg,
 				       BPF_CLASS(insn->code) == BPF_ALU64);
 
diff --git a/arch/x86/net/bpf_jit_comp32.c b/arch/x86/net/bpf_jit_comp32.c
index 852baf2e4db4d..11ed65130d1e5 100644
--- a/arch/x86/net/bpf_jit_comp32.c
+++ b/arch/x86/net/bpf_jit_comp32.c
@@ -1683,6 +1683,9 @@ static int do_jit(struct bpf_prog *bpf_prog, int *addrs, u8 *image,
 		int ilen;
 		u8 *func;
 
+		if (bpf_insn_is_hmul(insn))
+			goto notyet;
+
 		switch (code) {
 		/* ALU operations */
 		/* dst = src */
diff --git a/drivers/net/ethernet/netronome/nfp/bpf/verifier.c b/drivers/net/ethernet/netronome/nfp/bpf/verifier.c
index 9ddbfe9d60194..4481c8d84d280 100644
--- a/drivers/net/ethernet/netronome/nfp/bpf/verifier.c
+++ b/drivers/net/ethernet/netronome/nfp/bpf/verifier.c
@@ -3,6 +3,7 @@
 
 #include <linux/bpf.h>
 #include <linux/bpf_verifier.h>
+#include <linux/filter.h>
 #include <linux/kernel.h>
 #include <linux/netdevice.h>
 #include <linux/pkt_cls.h>
@@ -561,6 +562,11 @@ nfp_bpf_check_alu(struct nfp_prog *nfp_prog, struct nfp_insn_meta *meta,
 	const struct bpf_reg_state *dreg =
 		cur_regs(env) + meta->insn.dst_reg;
 
+	if (bpf_insn_is_hmul(&meta->insn)) {
+		pr_vlog(env, "UHMUL/SHMUL are not supported\n");
+		return -EOPNOTSUPP;
+	}
+
 	meta->umin_src = min(meta->umin_src, reg_umin(sreg));
 	meta->umax_src = max(meta->umax_src, reg_umax(sreg));
 	meta->umin_dst = min(meta->umin_dst, reg_umin(dreg));
diff --git a/include/linux/filter.h b/include/linux/filter.h
index e0218e677a1dc..fa8a1d18691d6 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -23,6 +23,7 @@
 #include <linux/sockptr.h>
 #include <linux/static_call.h>
 #include <linux/u64_stats_sync.h>
+#include <linux/math64.h>
 
 #include <net/sch_generic.h>
 
@@ -152,6 +153,43 @@ struct ctl_table_header;
 #define BPF_ALU32_IMM(OP, DST, IMM)				\
 	BPF_ALU32_IMM_OFF(OP, DST, IMM, 0)
 
+static inline u64 bpf_uhmul64(u64 a, u64 b)
+{
+	return mul_u64_u64_shr(a, b, 64);
+}
+
+static inline u64 bpf_shmul64(s64 a, s64 b)
+{
+	u64 ua = a, ub = b;
+	u64 hi = bpf_uhmul64(ua, ub);
+
+	/*
+	 * Let M = 2^64, sa = (a < 0), and sb = (b < 0). Then:
+	 *
+	 * a * b = (ua - sa * M) * (ub - sb * M)
+	 *       = ua * ub - sa * M * ub - sb * M * ua + sa * sb * M^2
+	 *
+	 * Therefore, signed_hi = unsigned_hi - sa * ub - sb * ua (mod M).
+	 */
+	if (a < 0)
+		hi -= ub;
+	if (b < 0)
+		hi -= ua;
+	return hi;
+}
+
+static inline bool bpf_is_hmul_variant(s16 off)
+{
+	return off == BPF_UHMUL || off == BPF_SHMUL;
+}
+
+static inline bool bpf_insn_is_hmul(const struct bpf_insn *insn)
+{
+	return BPF_CLASS(insn->code) == BPF_ALU64 &&
+	       BPF_OP(insn->code) == BPF_MUL &&
+	       bpf_is_hmul_variant(insn->off);
+}
+
 /* Endianess conversion, cpu_to_{l,b}e(), {l,b}e_to_cpu() */
 
 #define BPF_ENDIAN(TYPE, DST, LEN)				\
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index f29b0aad440cb..90c30e5d790b9 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -34,6 +34,12 @@
 #define BPF_FROM_LE	BPF_TO_LE
 #define BPF_FROM_BE	BPF_TO_BE
 
+/* BPF_MUL off values for high-half multiplication; BPF_ALU64 only. */
+enum bpf_mul_variant {
+	BPF_UHMUL = 1,
+	BPF_SHMUL = 2,
+};
+
 /* jmp encodings */
 #define BPF_JNE		0x50	/* jump != */
 #define BPF_JLT		0xa0	/* LT is unsigned, '<' */
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 3030beb531d99..f4f8cfa80033a 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -1943,7 +1943,46 @@ static u64 ___bpf_prog_run(u64 *regs, const struct bpf_insn *insn)
 	ALU(AND,  &)
 	ALU(OR,   |)
 	ALU(XOR,  ^)
-	ALU(MUL,  *)
+	/*
+	 * MUL needs explicit handlers because ALU64 insn->off selects
+	 * low-half, unsigned high-half, or signed high-half multiplication.
+	 */
+	ALU64_MUL_X:
+		switch (OFF) {
+		case 0:
+			DST = DST * SRC;
+			break;
+		case BPF_UHMUL:
+			DST = bpf_uhmul64(DST, SRC);
+			break;
+		case BPF_SHMUL:
+			DST = bpf_shmul64((s64)DST, (s64)SRC);
+			break;
+		default:
+			goto default_label;
+		}
+		CONT;
+	ALU_MUL_X:
+		DST = (u32) DST * (u32) SRC;
+		CONT;
+	ALU64_MUL_K:
+		switch (OFF) {
+		case 0:
+			DST = DST * IMM;
+			break;
+		case BPF_UHMUL:
+			DST = bpf_uhmul64(DST, (u64)IMM);
+			break;
+		case BPF_SHMUL:
+			DST = bpf_shmul64((s64)DST, (s32)IMM);
+			break;
+		default:
+			goto default_label;
+		}
+		CONT;
+	ALU_MUL_K:
+		DST = (u32) DST * (u32) IMM;
+		CONT;
 	SHT(LSH, <<)
 	SHT(RSH, >>)
 #undef SHT
diff --git a/kernel/bpf/disasm.c b/kernel/bpf/disasm.c
index 889fc074496f1..0b1bbb27e9e4e 100644
--- a/kernel/bpf/disasm.c
+++ b/kernel/bpf/disasm.c
@@ -163,6 +163,22 @@ static bool is_sdiv_smod(const struct bpf_insn *insn)
 	       insn->off == 1;
 }
 
+/* Return the UHMUL or SHMUL operator string, or NULL for all other encodings. */
+static const char *hmul_op_string(const struct bpf_insn *insn)
+{
+	if (BPF_CLASS(insn->code) != BPF_ALU64 ||
+	    BPF_OP(insn->code) != BPF_MUL)
+		return NULL;
+	switch (insn->off) {
+	case BPF_UHMUL:
+		return "uh*=";
+	case BPF_SHMUL:
+		return "sh*=";
+	default:
+		return NULL;
+	}
+}
+
 static bool is_movsx(const struct bpf_insn *insn)
 {
 	return BPF_OP(insn->code) == BPF_MOV &&
@@ -215,6 +231,7 @@ void print_bpf_insn(const struct bpf_insn_cbs *cbs,
 			verbose(cbs->private_data, "(%02x) %c%d %s %s%c%d",
 				insn->code, class == BPF_ALU ? 'w' : 'r',
 				insn->dst_reg,
+				hmul_op_string(insn) ?:
 				is_sdiv_smod(insn) ? bpf_alu_sign_string[BPF_OP(insn->code) >> 4]
 						   : bpf_alu_string[BPF_OP(insn->code) >> 4],
 				is_movsx(insn) ? bpf_movsx_string[(insn->off >> 3) - 1] : "",
@@ -224,6 +241,7 @@ void print_bpf_insn(const struct bpf_insn_cbs *cbs,
 			verbose(cbs->private_data, "(%02x) %c%d %s %d",
 				insn->code, class == BPF_ALU ? 'w' : 'r',
 				insn->dst_reg,
+				hmul_op_string(insn) ?:
 				is_sdiv_smod(insn) ? bpf_alu_sign_string[BPF_OP(insn->code) >> 4]
 						   : bpf_alu_string[BPF_OP(insn->code) >> 4],
 				insn->imm);
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 5601d20562186..fbb9202397832 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -16664,6 +16664,24 @@ static void scalar_min_max_mul(struct bpf_reg_state *dst_reg,
 					cnum64_from_srange(smin, smax));
 }
 
+static void scalar_min_max_uhmul(struct bpf_reg_state *dst_reg,
+				 struct bpf_reg_state *src_reg)
+{
+	u64 dst = reg_const_value(dst_reg, false);
+	u64 src = reg_const_value(src_reg, false);
+
+	___mark_reg_known(dst_reg, bpf_uhmul64(dst, src));
+}
+
+static void scalar_min_max_shmul(struct bpf_reg_state *dst_reg,
+				 struct bpf_reg_state *src_reg)
+{
+	s64 dst = reg_const_value(dst_reg, false);
+	s64 src = reg_const_value(src_reg, false);
+
+	___mark_reg_known(dst_reg, bpf_shmul64(dst, src));
+}
+
 static void scalar32_min_max_udiv(struct bpf_reg_state *dst_reg,
 				  struct bpf_reg_state *src_reg)
 {
@@ -17364,6 +17382,25 @@ static int adjust_scalar_min_max_vals(struct bpf_verifier_env *env,
 		reg_step_reset(dst_reg);
 		break;
 	case BPF_MUL:
+		/*
+		 * Range analysis for UHMUL/SHMUL is not implemented yet.
+		 * Compute an exact result only when both operands are constants.
+		 * Ordinary low-half MUL continues to use the analysis below.
+		 */
+		if (bpf_insn_is_hmul(insn) &&
+		    (!is_reg_const(dst_reg, false) ||
+		     !is_reg_const(&src_reg, false))) {
+			__mark_reg_unknown(env, dst_reg);
+			break;
+		}
+		if (!alu32 && insn->off == BPF_UHMUL) {
+			scalar_min_max_uhmul(dst_reg, &src_reg);
+			break;
+		}
+		if (!alu32 && insn->off == BPF_SHMUL) {
+			scalar_min_max_shmul(dst_reg, &src_reg);
+			break;
+		}
 		scalar_step_mul(dst_reg, &src_reg);
 		dst_reg->var_off = tnum_mul(dst_reg->var_off, src_reg.var_off);
 		scalar32_min_max_mul(dst_reg, &src_reg);
@@ -17499,6 +17536,11 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
 	/* Case where at least one operand is an arena. */
 	if (dst_reg->type == PTR_TO_ARENA || (src_reg && src_reg->type == PTR_TO_ARENA)) {
 
+		if (bpf_insn_is_hmul(insn)) {
+			verbose(env, "UHMUL/SHMUL with arena pointers are not supported\n");
+			return -EOPNOTSUPP;
+		}
+
 		if (dst_reg->type != PTR_TO_ARENA)
 			*dst_reg = *src_reg;
 
@@ -21194,6 +21236,20 @@ static int fd_array_get_map_idx(struct bpf_verifier_env *env, u32 idx)
 	return -EPROTO;
 }
 
+static bool is_valid_alu_variant(const struct bpf_insn *insn)
+{
+	switch (BPF_OP(insn->code)) {
+	case BPF_MUL:
+		return insn->off == 0 || bpf_insn_is_hmul(insn);
+	case BPF_DIV:
+	case BPF_MOD:
+		/* off == 1 selects SDIV/SMOD. */
+		return insn->off == 0 || insn->off == 1;
+	default:
+		return insn->off == 0;
+	}
+}
+
 static int check_alu_fields(struct bpf_verifier_env *env, struct bpf_insn *insn)
 {
 	u8 class = BPF_CLASS(insn->code);
@@ -21249,15 +21305,9 @@ static int check_alu_fields(struct bpf_verifier_env *env, struct bpf_insn *insn)
 	case BPF_MUL:
 	case BPF_DIV:
 	case BPF_MOD:
-		if (BPF_SRC(insn->code) == BPF_X) {
-			if (insn->imm != 0 || (insn->off != 0 && insn->off != 1) ||
-			    (insn->off == 1 && opcode != BPF_MOD && opcode != BPF_DIV)) {
-				verbose(env, "BPF_ALU uses reserved fields\n");
-				return -EINVAL;
-			}
-		} else if (insn->src_reg != BPF_REG_0 ||
-			   (insn->off != 0 && insn->off != 1) ||
-			   (insn->off == 1 && opcode != BPF_MOD && opcode != BPF_DIV)) {
+		if (!is_valid_alu_variant(insn) ||
+		    (BPF_SRC(insn->code) == BPF_X && insn->imm != 0) ||
+		    (BPF_SRC(insn->code) == BPF_K && insn->src_reg != BPF_REG_0)) {
 			verbose(env, "BPF_ALU uses reserved fields\n");
 			return -EINVAL;
 		}
diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
index f29b0aad440cb..90c30e5d790b9 100644
--- a/tools/include/uapi/linux/bpf.h
+++ b/tools/include/uapi/linux/bpf.h
@@ -34,6 +34,12 @@
 #define BPF_FROM_LE	BPF_TO_LE
 #define BPF_FROM_BE	BPF_TO_BE
 
+/* BPF_MUL off values for high-half multiplication; BPF_ALU64 only. */
+enum bpf_mul_variant {
+	BPF_UHMUL = 1,
+	BPF_SHMUL = 2,
+};
+
 /* jmp encodings */
 #define BPF_JNE		0x50	/* jump != */
 #define BPF_JLT		0xa0	/* LT is unsigned, '<' */
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index 04740ba37e918..795c3e56c23e0 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -59,6 +59,7 @@
 #include "verifier_helper_packet_access.skel.h"
 #include "verifier_helper_restricted.skel.h"
 #include "verifier_helper_value_access.skel.h"
+#include "verifier_hmul.skel.h"
 #include "verifier_idoms.skel.h"
 #include "verifier_int_ptr.skel.h"
 #include "verifier_iterating_callbacks.skel.h"
@@ -251,6 +252,7 @@ void test_verifier_helper_access_var_len(void) { RUN(verifier_helper_access_var_
 void test_verifier_helper_packet_access(void) { RUN(verifier_helper_packet_access); }
 void test_verifier_helper_restricted(void)    { RUN(verifier_helper_restricted); }
 void test_verifier_helper_value_access(void)  { RUN(verifier_helper_value_access); }
+void test_verifier_hmul(void)                 { RUN(verifier_hmul); }
 void test_verifier_idoms(void)                { RUN(verifier_idoms); }
 void test_verifier_int_ptr(void)              { RUN(verifier_int_ptr); }
 void test_verifier_iterating_callbacks(void)  { RUN(verifier_iterating_callbacks); }
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier_hmul_arena.c b/tools/testing/selftests/bpf/prog_tests/verifier_hmul_arena.c
new file mode 100644
index 0000000000000..f9cd7b7b9781a
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/verifier_hmul_arena.c
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include "verifier_hmul_arena.skel.h"
+
+void test_verifier_hmul_arena(void)
+{
+	int ret;
+
+	/* Arena support can depend on CPU features, such as Zacas on RV64. */
+	ret = libbpf_probe_bpf_map_type(BPF_MAP_TYPE_ARENA, NULL);
+	if (!ASSERT_GE(ret, 0, "probe_arena"))
+		return;
+	if (!ret) {
+		test__skip();
+		return;
+	}
+	RUN_TESTS(verifier_hmul_arena);
+}
diff --git a/tools/testing/selftests/bpf/progs/verifier_hmul.c b/tools/testing/selftests/bpf/progs/verifier_hmul.c
new file mode 100644
index 0000000000000..acd92355c6365
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_hmul.c
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bpf.h>
+#include <limits.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+#include "../../../include/linux/filter.h"
+
+#define BPF_UHMUL64_REG(DST, SRC) \
+	BPF_RAW_INSN(BPF_ALU64 | BPF_MUL | BPF_X, DST, SRC, BPF_UHMUL, 0)
+
+#define BPF_UHMUL64_IMM(DST, IMM) \
+	BPF_RAW_INSN(BPF_ALU64 | BPF_MUL | BPF_K, DST, 0, BPF_UHMUL, IMM)
+
+#define BPF_SHMUL64_REG(DST, SRC) \
+	BPF_RAW_INSN(BPF_ALU64 | BPF_MUL | BPF_X, DST, SRC, BPF_SHMUL, 0)
+
+#define BPF_SHMUL64_IMM(DST, IMM) \
+	BPF_RAW_INSN(BPF_ALU64 | BPF_MUL | BPF_K, DST, 0, BPF_SHMUL, IMM)
+
+/*
+ * Inject UHMUL/SHMUL as raw instructions so these tests do not require
+ * assembler support for the new mnemonics. All surrounding instructions use
+ * existing BPF assembly syntax supported by the baseline selftests toolchain.
+ */
+
+#if defined(__TARGET_ARCH_x86) || defined(__TARGET_ARCH_arm64) || \
+	(defined(__TARGET_ARCH_riscv) && __riscv_xlen == 64)
+
+/*
+ * BPF_PROG_TEST_RUN reports a 32-bit retval. Fold both halves of
+ * (actual ^ expected) into w0 so retval == 0 checks the full 64-bit result.
+ */
+
+SEC("socket")
+__description("UHMUL64, U64_MAX * U64_MAX, register source")
+__success __success_unpriv __retval(0)
+__naked void uhmul64_max_reg(void)
+{
+	asm volatile (
+	"r0 = -1;"
+	"r1 = -1;"
+	".8byte %[hmul];"
+	"r2 = -2;"
+	"r0 ^= r2;"
+	"r2 = r0;"
+	"r2 >>= 32;"
+	"w0 |= w2;"
+	"exit;"
+	:
+	: __imm_insn(hmul, BPF_UHMUL64_REG(BPF_REG_0, BPF_REG_1))
+	: __clobber_all);
+}
+
+SEC("socket")
+__description("UHMUL64, (1ULL << 63) * 2, register source")
+__success __success_unpriv __retval(0)
+__naked void uhmul64_pow2_reg(void)
+{
+	asm volatile (
+	"r0 = 2;"
+	"r1 = %[llong_min] ll;"
+	".8byte %[hmul];"
+	"r0 = r1;"
+	"r2 = 1;"
+	"r0 ^= r2;"
+	"r2 = r0;"
+	"r2 >>= 32;"
+	"w0 |= w2;"
+	"exit;"
+	:
+	: __imm_const(llong_min, LLONG_MIN),
+	  __imm_insn(hmul, BPF_UHMUL64_REG(BPF_REG_1, BPF_REG_0))
+	: __clobber_all);
+}
+
+SEC("socket")
+__description("UHMUL64, (1ULL << 63) * 2, R3 destination")
+__success __success_unpriv __retval(0)
+__naked void uhmul64_r3_dst(void)
+{
+	asm volatile (
+	"r3 = %[llong_min] ll;"
+	"r0 = 2;"
+	".8byte %[hmul];"
+	"r2 = 1;"
+	"r3 ^= r2;"
+	"r2 = r3;"
+	"r2 >>= 32;"
+	"w3 |= w2;"
+	"r0 = r3;"
+	"exit;"
+	:
+	: __imm_const(llong_min, LLONG_MIN),
+	  __imm_insn(hmul, BPF_UHMUL64_REG(BPF_REG_3, BPF_REG_0))
+	: __clobber_all);
+}
+
+SEC("socket")
+__description("UHMUL64, (1ULL << 63) * 2, immediate source")
+__success __success_unpriv __retval(0)
+__naked void uhmul64_pow2_imm(void)
+{
+	asm volatile (
+	"r0 = %[llong_min] ll;"
+	".8byte %[hmul];"
+	"r2 = 1;"
+	"r0 ^= r2;"
+	"r2 = r0;"
+	"r2 >>= 32;"
+	"w0 |= w2;"
+	"exit;"
+	:
+	: __imm_const(llong_min, LLONG_MIN),
+	  __imm_insn(hmul, BPF_UHMUL64_IMM(BPF_REG_0, 2))
+	: __clobber_all);
+}
+
+SEC("socket")
+__description("UHMUL64, (1ULL << 63) * -2, immediate source")
+__success __success_unpriv __retval(0)
+__naked void uhmul64_neg_imm(void)
+{
+	asm volatile (
+	"r0 = %[llong_min] ll;"
+	".8byte %[hmul];"
+	"r2 = %[llong_max] ll;"
+	"r0 ^= r2;"
+	"r2 = r0;"
+	"r2 >>= 32;"
+	"w0 |= w2;"
+	"exit;"
+	:
+	: __imm_const(llong_min, LLONG_MIN),
+	  __imm_const(llong_max, LLONG_MAX),
+	  __imm_insn(hmul, BPF_UHMUL64_IMM(BPF_REG_0, -2))
+	: __clobber_all);
+}
+
+SEC("socket")
+__description("SHMUL64, INT64_MAX * INT64_MAX, register source")
+__success __success_unpriv __retval(0)
+__naked void shmul64_max_reg(void)
+{
+	asm volatile (
+	"r0 = %[llong_max] ll;"
+	"r1 = %[llong_max] ll;"
+	".8byte %[hmul];"
+	"r2 = %[expected] ll;"
+	"r0 ^= r2;"
+	"r2 = r0;"
+	"r2 >>= 32;"
+	"w0 |= w2;"
+	"exit;"
+	:
+	: __imm_const(expected, 0x3fffffffffffffffULL),
+	  __imm_const(llong_max, LLONG_MAX),
+	  __imm_insn(hmul, BPF_SHMUL64_REG(BPF_REG_0, BPF_REG_1))
+	: __clobber_all);
+}
+
+SEC("socket")
+__description("SHMUL64, INT64_MIN * -2, register source")
+__success __success_unpriv __retval(0)
+__naked void shmul64_min_neg2_reg(void)
+{
+	asm volatile (
+	"r0 = %[llong_min] ll;"
+	"r1 = -2;"
+	".8byte %[hmul];"
+	"r2 = 1;"
+	"r0 ^= r2;"
+	"r2 = r0;"
+	"r2 >>= 32;"
+	"w0 |= w2;"
+	"exit;"
+	:
+	: __imm_const(llong_min, LLONG_MIN),
+	  __imm_insn(hmul, BPF_SHMUL64_REG(BPF_REG_0, BPF_REG_1))
+	: __clobber_all);
+}
+
+SEC("socket")
+__description("SHMUL64, 1 * -2, immediate source")
+__success __success_unpriv __retval(0)
+__naked void shmul64_neg_imm(void)
+{
+	asm volatile (
+	"r0 = 1;"
+	".8byte %[hmul];"
+	"r2 = -1;"
+	"r0 ^= r2;"
+	"r2 = r0;"
+	"r2 >>= 32;"
+	"w0 |= w2;"
+	"exit;"
+	:
+	: __imm_insn(hmul, BPF_SHMUL64_IMM(BPF_REG_0, -2))
+	: __clobber_all);
+}
+
+SEC("socket")
+__description("SHMUL64, INT64_MIN * 2, immediate source")
+__success __success_unpriv __retval(0)
+__naked void shmul64_min_imm(void)
+{
+	asm volatile (
+	"r0 = %[llong_min] ll;"
+	".8byte %[hmul];"
+	"r2 = -1;"
+	"r0 ^= r2;"
+	"r2 = r0;"
+	"r2 >>= 32;"
+	"w0 |= w2;"
+	"exit;"
+	:
+	: __imm_const(llong_min, LLONG_MIN),
+	  __imm_insn(hmul, BPF_SHMUL64_IMM(BPF_REG_0, 2))
+	: __clobber_all);
+}
+
+#else
+
+SEC("socket")
+__description("UHMUL/SHMUL are not supported by this JIT, use a dummy test")
+__skip("UHMUL/SHMUL are not supported by this jit")
+__success
+int dummy_test(void)
+{
+	return 0;
+}
+
+#endif
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_hmul_arena.c b/tools/testing/selftests/bpf/progs/verifier_hmul_arena.c
new file mode 100644
index 0000000000000..fc0ca88114d08
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_hmul_arena.c
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+#include "../../../include/linux/filter.h"
+
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST)
+
+#define BPF_UHMUL64_IMM(DST, IMM) \
+	BPF_RAW_INSN(BPF_ALU64 | BPF_MUL | BPF_K, DST, 0, BPF_UHMUL, IMM)
+
+#define BPF_SHMUL64_REG(DST, SRC) \
+	BPF_RAW_INSN(BPF_ALU64 | BPF_MUL | BPF_X, DST, SRC, BPF_SHMUL, 0)
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARENA);
+	__uint(map_flags, BPF_F_MMAPABLE);
+	__uint(max_entries, 1);
+} arena SEC(".maps");
+
+SEC("syscall")
+__description("UHMUL64 with arena pointer dst")
+__failure __msg("UHMUL/SHMUL with arena pointers are not supported")
+__naked void uhmul64_arena_ptr(void)
+{
+	asm volatile (
+	"r1 = %[arena] ll;"
+	"r0 = 1;"
+	"r0 = addr_space_cast(r0, 0x0, 0x1);"
+	".8byte %[hmul];"
+	"exit;"
+	:
+	: __imm_addr(arena),
+	  __imm_insn(hmul, BPF_UHMUL64_IMM(BPF_REG_0, 2))
+	: __clobber_all);
+}
+
+SEC("syscall")
+__description("SHMUL64 with arena pointer src")
+__failure __msg("UHMUL/SHMUL with arena pointers are not supported")
+__naked void shmul64_arena_ptr(void)
+{
+	asm volatile (
+	"r1 = %[arena] ll;"
+	"r1 = 1;"
+	"r1 = addr_space_cast(r1, 0x0, 0x1);"
+	"r0 = 2;"
+	".8byte %[hmul];"
+	"exit;"
+	:
+	: __imm_addr(arena),
+	  __imm_insn(hmul, BPF_SHMUL64_REG(BPF_REG_0, BPF_REG_1))
+	: __clobber_all);
+}
+
+#else
+
+SEC("socket")
+__description("HMUL arena tests require compiler support for addr_space_cast")
+__skip("compiler doesn't support addr_space_cast")
+__success
+int dummy_test(void)
+{
+	return 0;
+}
+
+#endif
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c b/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c
index 31663338866d3..209d5be551555 100644
--- a/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c
+++ b/tools/testing/selftests/bpf/progs/verifier_value_illegal_alu.c
@@ -367,7 +367,8 @@ __naked void flow_keys_illegal_variable_offset_alu(void)
 
 /*
  * Offset fields of 0 and 1 are legal for BPF_{DIV,MOD} instructions.
- * Offset fields of 0 are legal for the rest of ALU instructions.
+ * Offset fields of 0, 1 and 2 are legal for BPF_ALU64 | BPF_MUL.
+ * Only offset 0 is legal for BPF_ALU | BPF_MUL.
  * Test that error is reported for illegal offsets, assuming that tests
  * for legal offsets exist.
  */
@@ -377,5 +378,9 @@ DEFINE_BAD_OFFSET_TEST(bad_offset_addx, BPF_ALU64 | BPF_ADD | BPF_X, -1, 0)
 DEFINE_BAD_OFFSET_TEST(bad_offset_divx2, BPF_ALU64 | BPF_DIV | BPF_X, 2, 0)
 DEFINE_BAD_OFFSET_TEST(bad_offset_modk2, BPF_ALU64 | BPF_MOD | BPF_K, 2, 1)
 DEFINE_BAD_OFFSET_TEST(bad_offset_addx2, BPF_ALU64 | BPF_ADD | BPF_X, 1, 0)
+DEFINE_BAD_OFFSET_TEST(bad_offset_mulx, BPF_ALU64 | BPF_MUL | BPF_X, -1, 0)
+DEFINE_BAD_OFFSET_TEST(bad_offset_mulk, BPF_ALU64 | BPF_MUL | BPF_K, 3, 0)
+DEFINE_BAD_OFFSET_TEST(bad_uhmul32_x, BPF_ALU | BPF_MUL | BPF_X, BPF_UHMUL, 0)
+DEFINE_BAD_OFFSET_TEST(bad_shmul32_k, BPF_ALU | BPF_MUL | BPF_K, BPF_SHMUL, 0)
 
 char _license[] SEC("license") = "GPL";

23/3 2026/10/22 03:53 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 20525
							output: 35
							thoughts: 1526
						

Thoughts:
**KMSAN Analysis of eBPF Multiplication Instruction Patch**

Okay, let's break down this patch and determine if it warrants a KMSAN check. Since I'm targeting amd64, I need to focus on the x86_64 implementation specifically, but I'll consider the general flow.

The patch introduces support for unsigned and signed high-half 64-bit multiplication instructions (UHMUL and SHMUL) within the eBPF instruction set. That's a core change, expanding the ALU64 capabilities. The main areas of change include documentation, architecture-specific JIT updates (with x86_64 being key for my analysis), driver changes, and updates to the core BPF interpreter, verifier, and disassembler.

Looking at the code, for x86_64, the JIT implementation uses `emit_hmul_aux`, leveraging the x86 `mul` and `imul` instructions. The verifier has been updated to handle the new instructions, including constant folding and min/max value adjustments.

Now, KMSAN. The goal here is to identify potential risks of uninitialized memory usage. I need to walk through the rules:

*   **No copy to user?** I immediately check for any data transfer to userspace. Fortunately, the patch doesn't add or touch `copy_to_user`, `put_user`, netlink, socket options, or ioctl operations. That's a huge relief because uninitialized memory leaks to userspace are a priority.

*   **Conditional logic with uninitialized variables?** I have to dig deeper, this is where it's likely to come up. Let's look at `kernel/bpf/verifier.c`. Specifically, functions like `scalar_min_max_uhmul` and `scalar_min_max_shmul` and other related functions. In those functions, the code uses `reg_const_value` to determine the value of a register and then calls `___mark_reg_known`. I need to confirm that `___mark_reg_known` is handling the register state correctly. Is it fully initializing all fields of `dst_reg`? If `___mark_reg_known` doesn't properly initialize all state fields, then potentially, some value could remain uninitialized. I need to trace this function to understand its behavior thoroughly.

*   **Complex data structures with uninitialized fields?** No new complex structures are introduced directly. The changes are focused on instruction handling within existing structures. I don't see any obvious allocation/initialization of complex data structures that might leave fields uninitialized.

*   **Bounds checks/lengths leading to uninitialized byte access?** The patch doesn't seem to be changing any bounds checks in a way that would introduce uninitialized memory exposure. It's handling existing memory in a new way, and the changes appear to be within the existing bounds of the BPF structures.

In conclusion, I need to verify that `___mark_reg_known` handles the complete initialization of `dst_reg` state. Only then can I be sure that the patch does not have any code paths introducing uninitialized memory usage or information leaks, and would not require KMSAN.



24/3 2026/10/22 03:54 tool grepper
0m Args:
{
  "Expression": "___mark_reg_known",
  "PathPrefixes": [
    "kernel/bpf/verifier.c"
  ]
}

Results:
{
  "Output": "kernel/bpf/verifier.c=2057=static void reg_step_check_unsigned(struct bpf_reg_state *reg, u32 bits)\n--\nkernel/bpf/verifier.c-2063-/* This helper doesn't clear reg-\u003eid */\nkernel/bpf/verifier.c:2064:static void ___mark_reg_known(struct bpf_reg_state *reg, u64 imm)\nkernel/bpf/verifier.c-2065-{\n--\nkernel/bpf/verifier.c=2075=static void __mark_reg_known(struct bpf_reg_state *reg, u64 imm)\n--\nkernel/bpf/verifier.c-2082-\treg-\u003emap_uid = 0;\nkernel/bpf/verifier.c:2083:\t___mark_reg_known(reg, imm);\nkernel/bpf/verifier.c-2084-}\n--\nkernel/bpf/verifier.c=2267=static void __update_reg64_bounds(struct bpf_reg_state *reg)\n--\nkernel/bpf/verifier.c-2282-\t\t */\nkernel/bpf/verifier.c:2283:\t\t___mark_reg_known(reg, reg_umin(reg));\nkernel/bpf/verifier.c-2284-\t} else if (!umin_in_tnum \u0026\u0026 tnum_next == tmax) {\n--\nkernel/bpf/verifier.c-2289-\t\t */\nkernel/bpf/verifier.c:2290:\t\t___mark_reg_known(reg, tmax);\nkernel/bpf/verifier.c-2291-\t} else if (!umin_in_tnum \u0026\u0026 tnum_next \u003c= reg_umax(reg) \u0026\u0026\n--\nkernel/bpf/verifier.c-2297-\t\t */\nkernel/bpf/verifier.c:2298:\t\t___mark_reg_known(reg, tnum_next);\nkernel/bpf/verifier.c-2299-\t}\n--\nkernel/bpf/verifier.c=16667=static void scalar_min_max_uhmul(struct bpf_reg_state *dst_reg,\n--\nkernel/bpf/verifier.c-16672-\nkernel/bpf/verifier.c:16673:\t___mark_reg_known(dst_reg, bpf_uhmul64(dst, src));\nkernel/bpf/verifier.c-16674-}\n--\nkernel/bpf/verifier.c=16676=static void scalar_min_max_shmul(struct bpf_reg_state *dst_reg,\n--\nkernel/bpf/verifier.c-16681-\nkernel/bpf/verifier.c:16682:\t___mark_reg_known(dst_reg, bpf_shmul64(dst, src));\nkernel/bpf/verifier.c-16683-}\n--\nkernel/bpf/verifier.c=17328=static int adjust_scalar_min_max_vals(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-17411-\t\tif ((alu32 \u0026\u0026 reg_u32_min(\u0026src_reg) == 0) || (!alu32 \u0026\u0026 reg_umin(\u0026src_reg) == 0)) {\nkernel/bpf/verifier.c:17412:\t\t\t___mark_reg_known(dst_reg, 0);\nkernel/bpf/verifier.c-17413-\t\t\tbreak;\n"
}

25/3 2026/10/22 03:54 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 23674
							output: 37
							thoughts: 26
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Investigating Scalar Min-Max Function Implementation**

Okay, so I need to get a handle on how the `___mark_reg_known` function is *actually* implemented. I'm thinking I should start by tracing its definition and see what's going on under the hood. More specifically, I want to see exactly how other scalar min-max functions are using or calling this. My expectation is that this function likely plays a key role in the optimization process, so understanding its behavior and interaction with other components is essential to see how the system has been tuned, and whether there are improvements to be made.



26/3 2026/10/22 03:54 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 2060,
  "LineCount": 30
}

Results:
{
  "Contents": "2060:\t\t\treg_step_reset(reg);\n2061:\t}\n2062:\t\n2063:\t/* This helper doesn't clear reg-\u003eid */\n2064:\tstatic void ___mark_reg_known(struct bpf_reg_state *reg, u64 imm)\n2065:\t{\n2066:\t\treg-\u003evar_off = tnum_const(imm);\n2067:\t\treg-\u003er64 = cnum64_from_urange(imm, imm);\n2068:\t\treg-\u003er32 = cnum32_from_urange((u32)imm, (u32)imm);\n2069:\t\treg_step_reset(reg);\n2070:\t}\n2071:\t\n2072:\t/* Mark the unknown part of a register (variable offset or scalar value) as\n2073:\t * known to have the value @imm.\n2074:\t */\n2075:\tstatic void __mark_reg_known(struct bpf_reg_state *reg, u64 imm)\n2076:\t{\n2077:\t\t/* Clear off and union(map_ptr, range) */\n2078:\t\tmemset(((u8 *)reg) + sizeof(reg-\u003etype), 0,\n2079:\t\t       offsetof(struct bpf_reg_state, var_off) - sizeof(reg-\u003etype));\n2080:\t\treg-\u003eid = 0;\n2081:\t\treg-\u003eparent_id = 0;\n2082:\t\treg-\u003emap_uid = 0;\n2083:\t\t___mark_reg_known(reg, imm);\n2084:\t}\n2085:\t\n2086:\tvoid bpf_mark_reg_known_scalar(struct bpf_reg_state *reg, u64 imm)\n2087:\t{\n2088:\t\t__mark_reg_known(reg, imm);\n2089:\t\treg-\u003etype = SCALAR_VALUE;\n"
}

27/3 2026/10/22 03:54 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 24409
							output: 359
							thoughts: 3155
						

Thoughts:
**KMSAN Assessment: High-Half Multiplication Instructions**

Okay, here's my assessment of this patch. My primary concern, as always, is whether it introduces any potential for uninitialized memory usage or information leaks, which is the domain where KMSAN provides unique value beyond what KASAN or other tools can catch.

The patch's core function is to add support for 64-bit high-half multiplication instructions, specifically `UHMUL` (unsigned) and `SHMUL` (signed), to the BPF instruction set and various components: the interpreter, the verifier, and the JIT compilers for x86_64, arm64, and riscv64. It also rejects usage on unsupported architectures. It modifies the verifier to handle these new instructions by either marking the result register as completely unknown or calculating an exact constant.

I've carefully examined the code changes with the following considerations:

1.  **Kernel-to-User Space Data Transfer:** The patch *does not* introduce any new kernel structures that are copied to user space, either directly via `copy_to_user`, indirect methods, BPF buffers, or other avenues.

2.  **Conditional Logic and Branching:** The patch adds conditional branches but those branches are directly related to the new instructions (UHMUL/SHMUL) and, critically, they operate on known values or check for unsupported configurations. There are no branches that depend on uninitialized variables or struct fields in a way that could lead to information leaks.

3.  **Data Structure Initialization:** The code doesn't introduce any new complex data structures or allocations where uninitialized fields could be read later. All new variables used are local, initialized, or directly involved in processing known scalar values.

4.  **Bounds Checks and Lengths:** The patch introduces new arithmetic operations, but it does so cautiously. Any register bounds are updated to unknown (safe state) or, for constant values, the exact constant value is computed. It explicitly forbids operations on arena pointers with these new instructions. Any logic flaws in JIT code generation or verifier handling would likely manifest as an instruction decoding or execution failure, or incorrect bounds calculation, which are detectable by existing testing, KASAN, or other standard bug detectors.

   Moreover, since existing BPF code performs ALU ops and bounds checking/length calculations, this new code leverages existing structures. The verifier already tracks register state and ensures that operations are performed on valid data, so no new uninitialized memory vulnerabilities arise here.

Given these points, I am confident that this patch does *not* introduce any risks of uninitialized memory usage or information leaks that would be uniquely detectable by KMSAN. Therefore, `NeedsKMSAN=false`.



28/3 2026/10/22 03:54 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch series introduces BPF support for unsigned and signed 64-bit high-half multiplication instructions (UHMUL and SHMUL). It adds instruction definitions to the BPF specification, handles them in the BPF interpreter and disassembler, adds scalar constant-folding and arena-pointer rejection in the BPF verifier, and implements JIT support across architectures (x86_64, arm64, riscv64) while returning -EOPNOTSUPP on unsupported architectures.\n\nRegarding memory safety:\n1. No kernel structures or buffers are copied to user space (no copy_to_user, put_user, netlink skb attributes, ioctls, or info-leaks).\n2. All variables in the new math helpers (bpf_uhmul64, bpf_shmul64), interpreter routines, and JIT emission functions are fully initialized.\n3. The BPF verifier treats UHMUL/SHMUL on non-constant registers by setting them to unknown (__mark_reg_unknown) and marks known constants via ___mark_reg_known; arena pointer operations are rejected. No uninitialized register state or buffer length confusion is created.\n4. Any potential issues with JIT code generation, register clobbering, or verifier scalar tracking are either logic bugs, execution failures, or memory corruption/out-of-bounds accesses, which are covered by standard kernel testing and KASAN.\n\nBecause the patch does not introduce or expose uninitialized memory reads or information leaks, a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch series introduces BPF support for unsigned and signed 64-bit high-half multiplication instructions (UHMUL and SHMUL). It adds instruction definitions to the BPF specification, handles them in the BPF interpreter and disassembler, adds scalar constant-folding and arena-pointer rejection in the BPF verifier, and implements JIT support across architectures (x86_64, arm64, riscv64) while returning -EOPNOTSUPP on unsupported architectures.\n\nRegarding memory safety:\n1. No kernel structures or buffers are copied to user space (no copy_to_user, put_user, netlink skb attributes, ioctls, or info-leaks).\n2. All variables in the new math helpers (bpf_uhmul64, bpf_shmul64), interpreter routines, and JIT emission functions are fully initialized.\n3. The BPF verifier treats UHMUL/SHMUL on non-constant registers by setting them to unknown (__mark_reg_unknown) and marks known constants via ___mark_reg_known; arena pointer operations are rejected. No uninitialized register state or buffer length confusion is created.\n4. Any potential issues with JIT code generation, register clobbering, or verifier scalar tracking are either logic bugs, execution failures, or memory corruption/out-of-bounds accesses, which are covered by standard kernel testing and KASAN.\n\nBecause the patch does not introduce or expose uninitialized memory reads or information leaks, a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)