| // SPDX-License-Identifier: GPL-2.0-only | |
| /* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ | |
| #include <linux/bpf_verifier.h> | |
| /* | |
| * Forward dataflow analysis to determine constant register values at every | |
| * instruction. Tracks 64-bit constant values in R0-R9 through the program, | |
| * using a fixed-point iteration in reverse postorder. Records which registers | |
| * hold known constants and their values in | |
| * env->insn_aux_data[].{const_reg_mask, const_reg_vals}. | |
| */ | |
| enum const_arg_state { | |
| CONST_ARG_UNVISITED, /* instruction not yet reached */ | |
| CONST_ARG_UNKNOWN, /* register value not a known constant */ | |
| CONST_ARG_CONST, /* register holds a known 64-bit constant */ | |
| CONST_ARG_MAP_PTR, /* register holds a map pointer, map_index is set */ | |
| CONST_ARG_MAP_VALUE, /* register points to map value data, val is offset */ | |
| CONST_ARG_SUBPROG, /* register holds a subprog pointer, val is subprog number */ | |
| }; | |
| struct const_arg_info { | |
| enum const_arg_state state; | |
| u32 map_index; | |
| u64 val; | |
| }; | |
| static bool ci_is_unvisited(const struct const_arg_info *ci) | |
| { | |
| return ci->state == CONST_ARG_UNVISITED; | |
| } | |
| static bool ci_is_unknown(const struct const_arg_info *ci) | |
| { | |
| return ci->state == CONST_ARG_UNKNOWN; | |
| } | |
| static bool ci_is_const(const struct const_arg_info *ci) | |
| { | |
| return ci->state == CONST_ARG_CONST; | |
| } | |
| static bool ci_is_map_value(const struct const_arg_info *ci) | |
| { | |
| return ci->state == CONST_ARG_MAP_VALUE; | |
| } | |
| /* Transfer function: compute output register state from instruction. */ | |
| static void const_reg_xfer(struct bpf_verifier_env *env, struct const_arg_info *ci_out, | |
| struct bpf_insn *insn, struct bpf_insn *insns, int idx) | |
| { | |
| struct const_arg_info unknown = { .state = CONST_ARG_UNKNOWN, .val = 0 }; | |
| struct const_arg_info *dst = &ci_out[insn->dst_reg]; | |
| struct const_arg_info *src = &ci_out[insn->src_reg]; | |
| u8 class = BPF_CLASS(insn->code); | |
| u8 mode = BPF_MODE(insn->code); | |
| u8 opcode = BPF_OP(insn->code) | BPF_SRC(insn->code); | |
| int r; | |
| /* Stack arg stores (r11-based) are outside the tracked register set. */ | |
| if (is_stack_arg_st(insn) || is_stack_arg_stx(insn)) | |
| return; | |
| if (is_stack_arg_ldx(insn)) { | |
| ci_out[insn->dst_reg] = unknown; | |
| return; | |
| } | |
| switch (class) { | |
| case BPF_ALU: | |
| case BPF_ALU64: | |
| switch (opcode) { | |
| case BPF_MOV | BPF_K: | |
| dst->state = CONST_ARG_CONST; | |
| dst->val = (s64)insn->imm; | |
| break; | |
| case BPF_MOV | BPF_X: | |
| *dst = *src; | |
| if (!insn->off) | |
| break; | |
| if (!ci_is_const(dst)) { | |
| *dst = unknown; | |
| break; | |
| } | |
| switch (insn->off) { | |
| case 8: dst->val = (s8)dst->val; break; | |
| case 16: dst->val = (s16)dst->val; break; | |
| case 32: dst->val = (s32)dst->val; break; | |
| default: *dst = unknown; break; | |
| } | |
| break; | |
| case BPF_ADD | BPF_K: | |
| if (!ci_is_const(dst) && !ci_is_map_value(dst)) { | |
| *dst = unknown; | |
| break; | |
| } | |
| dst->val += insn->imm; | |
| break; | |
| case BPF_SUB | BPF_K: | |
| if (!ci_is_const(dst) && !ci_is_map_value(dst)) { | |
| *dst = unknown; | |
| break; | |
| } | |
| dst->val -= insn->imm; | |
| break; | |
| case BPF_AND | BPF_K: | |
| if (!ci_is_const(dst)) { | |
| if (!insn->imm) { | |
| dst->state = CONST_ARG_CONST; | |
| dst->val = 0; | |
| } else { | |
| *dst = unknown; | |
| } | |
| break; | |
| } | |
| dst->val &= (s64)insn->imm; | |
| break; | |
| case BPF_AND | BPF_X: | |
| if (ci_is_const(dst) && dst->val == 0) | |
| break; /* 0 & x == 0 */ | |
| if (ci_is_const(src) && src->val == 0) { | |
| dst->state = CONST_ARG_CONST; | |
| dst->val = 0; | |
| break; | |
| } | |
| if (!ci_is_const(dst) || !ci_is_const(src)) { | |
| *dst = unknown; | |
| break; | |
| } | |
| dst->val &= src->val; | |
| break; | |
| default: | |
| *dst = unknown; | |
| break; | |
| } | |
| if (class == BPF_ALU) { | |
| if (ci_is_const(dst)) | |
| dst->val = (u32)dst->val; | |
| else if (!ci_is_unknown(dst)) | |
| *dst = unknown; | |
| } | |
| break; | |
| case BPF_LD: | |
| if (mode == BPF_ABS || mode == BPF_IND) | |
| goto process_call; | |
| if (mode != BPF_IMM || BPF_SIZE(insn->code) != BPF_DW) | |
| break; | |
| if (insn->src_reg == BPF_PSEUDO_FUNC) { | |
| int subprog = bpf_find_subprog(env, idx + insn->imm + 1); | |
| if (subprog >= 0) { | |
| dst->state = CONST_ARG_SUBPROG; | |
| dst->val = subprog; | |
| } else { | |
| *dst = unknown; | |
| } | |
| } else if (insn->src_reg == BPF_PSEUDO_MAP_VALUE || | |
| insn->src_reg == BPF_PSEUDO_MAP_IDX_VALUE) { | |
| dst->state = CONST_ARG_MAP_VALUE; | |
| dst->map_index = env->insn_aux_data[idx].map_index; | |
| dst->val = env->insn_aux_data[idx].map_off; | |
| } else if (insn->src_reg == BPF_PSEUDO_MAP_FD || | |
| insn->src_reg == BPF_PSEUDO_MAP_IDX) { | |
| dst->state = CONST_ARG_MAP_PTR; | |
| dst->map_index = env->insn_aux_data[idx].map_index; | |
| } else if (insn->src_reg == 0) { | |
| dst->state = CONST_ARG_CONST; | |
| dst->val = (u64)(u32)insn->imm | ((u64)(u32)insns[idx + 1].imm << 32); | |
| } else { | |
| *dst = unknown; | |
| } | |
| break; | |
| case BPF_LDX: | |
| if (!ci_is_map_value(src)) { | |
| *dst = unknown; | |
| break; | |
| } | |
| struct bpf_map *map = env->used_maps[src->map_index]; | |
| int size = bpf_size_to_bytes(BPF_SIZE(insn->code)); | |
| bool is_ldsx = mode == BPF_MEMSX; | |
| int off = src->val + insn->off; | |
| u64 val = 0; | |
| if (!bpf_map_is_rdonly(map) || !map->ops->map_direct_value_addr || | |
| off < 0 || off + size > map->value_size || | |
| bpf_map_direct_read(map, off, size, &val, is_ldsx)) { | |
| *dst = unknown; | |
| break; | |
| } | |
| dst->state = CONST_ARG_CONST; | |
| dst->val = val; | |
| break; | |
| case BPF_JMP: | |
| if (opcode != BPF_CALL) | |
| break; | |
| process_call: | |
| for (r = BPF_REG_0; r <= BPF_REG_5; r++) | |
| ci_out[r] = unknown; | |
| break; | |
| case BPF_STX: | |
| r = bpf_atomic_load_reg(insn); | |
| if (r >= 0) | |
| ci_out[r] = unknown; | |
| break; | |
| } | |
| } | |
| /* Join function: merge output state into a successor's input state. */ | |
| static bool const_reg_join(struct const_arg_info *ci_target, | |
| struct const_arg_info *ci_out) | |
| { | |
| bool changed = false; | |
| int r; | |
| for (r = 0; r < MAX_BPF_REG; r++) { | |
| struct const_arg_info *old = &ci_target[r]; | |
| struct const_arg_info *new = &ci_out[r]; | |
| if (ci_is_unvisited(old) && !ci_is_unvisited(new)) { | |
| ci_target[r] = *new; | |
| changed = true; | |
| } else if (!ci_is_unknown(old) && !ci_is_unvisited(old) && | |
| (new->state != old->state || new->val != old->val || | |
| new->map_index != old->map_index)) { | |
| old->state = CONST_ARG_UNKNOWN; | |
| changed = true; | |
| } | |
| } | |
| return changed; | |
| } | |
| int bpf_compute_const_regs(struct bpf_verifier_env *env) | |
| { | |
| struct const_arg_info unknown = { .state = CONST_ARG_UNKNOWN, .val = 0 }; | |
| struct bpf_insn_aux_data *insn_aux = env->insn_aux_data; | |
| struct bpf_insn *insns = env->prog->insnsi; | |
| int insn_cnt = env->prog->len; | |
| struct const_arg_info (*ci_in)[MAX_BPF_REG]; | |
| struct const_arg_info ci_out[MAX_BPF_REG]; | |
| struct bpf_iarray *succ; | |
| bool changed; | |
| int i, r; | |
| /* kvzalloc zeroes memory, so all entries start as CONST_ARG_UNVISITED (0) */ | |
| ci_in = kvzalloc_objs(*ci_in, insn_cnt, GFP_KERNEL_ACCOUNT); | |
| if (!ci_in) | |
| return -ENOMEM; | |
| /* Subprogram entries (including main at subprog 0): all registers unknown */ | |
| for (i = 0; i < env->subprog_cnt; i++) { | |
| int start = env->subprog_info[i].start; | |
| for (r = 0; r < MAX_BPF_REG; r++) | |
| ci_in[start][r] = unknown; | |
| } | |
| redo: | |
| changed = false; | |
| for (i = env->cfg.cur_postorder - 1; i >= 0; i--) { | |
| int idx = env->cfg.insn_postorder[i]; | |
| struct bpf_insn *insn = &insns[idx]; | |
| struct const_arg_info *ci = ci_in[idx]; | |
| memcpy(ci_out, ci, sizeof(ci_out)); | |
| const_reg_xfer(env, ci_out, insn, insns, idx); | |
| succ = bpf_insn_successors(env, idx); | |
| for (int s = 0; s < succ->cnt; s++) | |
| changed |= const_reg_join(ci_in[succ->items[s]], ci_out); | |
| } | |
| if (changed) | |
| goto redo; | |
| /* Save computed constants into insn_aux[] if they fit into 32-bit */ | |
| for (i = 0; i < insn_cnt; i++) { | |
| u16 mask = 0, map_mask = 0, subprog_mask = 0; | |
| struct bpf_insn_aux_data *aux = &insn_aux[i]; | |
| struct const_arg_info *ci = ci_in[i]; | |
| for (r = BPF_REG_0; r < ARRAY_SIZE(aux->const_reg_vals); r++) { | |
| struct const_arg_info *c = &ci[r]; | |
| switch (c->state) { | |
| case CONST_ARG_CONST: { | |
| u64 val = c->val; | |
| if (val != (u32)val) | |
| break; | |
| mask |= BIT(r); | |
| aux->const_reg_vals[r] = val; | |
| break; | |
| } | |
| case CONST_ARG_MAP_PTR: | |
| map_mask |= BIT(r); | |
| aux->const_reg_vals[r] = c->map_index; | |
| break; | |
| case CONST_ARG_SUBPROG: | |
| subprog_mask |= BIT(r); | |
| aux->const_reg_vals[r] = c->val; | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| aux->const_reg_mask = mask; | |
| aux->const_reg_map_mask = map_mask; | |
| aux->const_reg_subprog_mask = subprog_mask; | |
| } | |
| kvfree(ci_in); | |
| return 0; | |
| } | |
| static int eval_const_branch(u8 opcode, u64 dst_val, u64 src_val) | |
| { | |
| switch (BPF_OP(opcode)) { | |
| case BPF_JEQ: return dst_val == src_val; | |
| case BPF_JNE: return dst_val != src_val; | |
| case BPF_JGT: return dst_val > src_val; | |
| case BPF_JGE: return dst_val >= src_val; | |
| case BPF_JLT: return dst_val < src_val; | |
| case BPF_JLE: return dst_val <= src_val; | |
| case BPF_JSGT: return (s64)dst_val > (s64)src_val; | |
| case BPF_JSGE: return (s64)dst_val >= (s64)src_val; | |
| case BPF_JSLT: return (s64)dst_val < (s64)src_val; | |
| case BPF_JSLE: return (s64)dst_val <= (s64)src_val; | |
| case BPF_JSET: return (bool)(dst_val & src_val); | |
| default: return -1; | |
| } | |
| } | |
| /* | |
| * Rewrite conditional branches with constant outcomes into unconditional | |
| * jumps using register values resolved by bpf_compute_const_regs() pass. | |
| * This eliminates dead edges from the CFG so that compute_live_registers() | |
| * doesn't propagate liveness through dead code. | |
| */ | |
| int bpf_prune_dead_branches(struct bpf_verifier_env *env) | |
| { | |
| struct bpf_insn_aux_data *insn_aux = env->insn_aux_data; | |
| struct bpf_insn *insns = env->prog->insnsi; | |
| int insn_cnt = env->prog->len; | |
| bool changed = false; | |
| int i; | |
| for (i = 0; i < insn_cnt; i++) { | |
| struct bpf_insn_aux_data *aux = &insn_aux[i]; | |
| struct bpf_insn *insn = &insns[i]; | |
| u8 class = BPF_CLASS(insn->code); | |
| u64 dst_val, src_val; | |
| int taken; | |
| if (!bpf_insn_is_cond_jump(insn->code)) | |
| continue; | |
| if (bpf_is_may_goto_insn(insn)) | |
| continue; | |
| if (!(aux->const_reg_mask & BIT(insn->dst_reg))) | |
| continue; | |
| dst_val = aux->const_reg_vals[insn->dst_reg]; | |
| if (BPF_SRC(insn->code) == BPF_K) { | |
| src_val = insn->imm; | |
| } else { | |
| if (!(aux->const_reg_mask & BIT(insn->src_reg))) | |
| continue; | |
| src_val = aux->const_reg_vals[insn->src_reg]; | |
| } | |
| if (class == BPF_JMP32) { | |
| /* | |
| * The (s32) cast maps the 32-bit range into two u64 sub-ranges: | |
| * [0x00000000, 0x7FFFFFFF] -> [0x0000000000000000, 0x000000007FFFFFFF] | |
| * [0x80000000, 0xFFFFFFFF] -> [0xFFFFFFFF80000000, 0xFFFFFFFFFFFFFFFF] | |
| * The ordering is preserved within each sub-range, and | |
| * the second sub-range is above the first as u64. | |
| */ | |
| dst_val = (s32)dst_val; | |
| src_val = (s32)src_val; | |
| } | |
| taken = eval_const_branch(insn->code, dst_val, src_val); | |
| if (taken < 0) { | |
| bpf_log(&env->log, "Unknown conditional jump %x\n", insn->code); | |
| return -EFAULT; | |
| } | |
| *insn = BPF_JMP_A(taken ? insn->off : 0); | |
| changed = true; | |
| } | |
| if (!changed) | |
| return 0; | |
| /* recompute postorder, since CFG has changed */ | |
| kvfree(env->cfg.insn_postorder); | |
| env->cfg.insn_postorder = NULL; | |
| return bpf_compute_postorder(env); | |
| } |