Skip to content

Bpf rdonly cast void #9176

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: bpf-next_base
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 67 additions & 12 deletions kernel/bpf/verifier.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ static const struct bpf_verifier_ops * const bpf_verifier_ops[] = {
#undef BPF_LINK_TYPE
};

enum bpf_features {
BPF_FEAT_RDONLY_CAST_TO_VOID = 0,
__MAX_BPF_FEAT = 1,
};

struct bpf_mem_alloc bpf_global_percpu_ma;
static bool bpf_global_percpu_ma_set;

Expand Down Expand Up @@ -7535,6 +7540,7 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
}
} else if (base_type(reg->type) == PTR_TO_MEM) {
bool rdonly_mem = type_is_rdonly_mem(reg->type);
bool rdonly_untrusted = rdonly_mem && (reg->type & PTR_UNTRUSTED);

if (type_may_be_null(reg->type)) {
verbose(env, "R%d invalid mem access '%s'\n", regno,
Expand All @@ -7554,8 +7560,13 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
return -EACCES;
}

err = check_mem_region_access(env, regno, off, size,
reg->mem_size, false);
/*
* Accesses to untrusted PTR_TO_MEM are done through probe
* instructions, hence no need to check bounds in that case.
*/
if (!rdonly_untrusted)
err = check_mem_region_access(env, regno, off, size,
reg->mem_size, false);
if (!err && value_regno >= 0 && (t == BPF_READ || rdonly_mem))
mark_reg_unknown(env, regs, value_regno);
} else if (reg->type == PTR_TO_CTX) {
Expand Down Expand Up @@ -13602,16 +13613,24 @@ static int check_special_kfunc(struct bpf_verifier_env *env, struct bpf_kfunc_ca
regs[BPF_REG_0].btf_id = meta->ret_btf_id;
} else if (meta->func_id == special_kfunc_list[KF_bpf_rdonly_cast]) {
ret_t = btf_type_by_id(desc_btf, meta->arg_constant.value);
if (!ret_t || !btf_type_is_struct(ret_t)) {
if (!ret_t) {
verbose(env, "Unknown type ID %lld passed to kfunc bpf_rdonly_cast\n",
meta->arg_constant.value);
return -EINVAL;
} else if (btf_type_is_struct(ret_t)) {
mark_reg_known_zero(env, regs, BPF_REG_0);
regs[BPF_REG_0].type = PTR_TO_BTF_ID | PTR_UNTRUSTED;
regs[BPF_REG_0].btf = desc_btf;
regs[BPF_REG_0].btf_id = meta->arg_constant.value;
} else if (btf_type_is_void(ret_t)) {
mark_reg_known_zero(env, regs, BPF_REG_0);
regs[BPF_REG_0].type = PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED;
regs[BPF_REG_0].mem_size = 0;
} else {
verbose(env,
"kfunc bpf_rdonly_cast type ID argument must be of a struct\n");
"kfunc bpf_rdonly_cast type ID argument must be of a struct or void\n");
return -EINVAL;
}

mark_reg_known_zero(env, regs, BPF_REG_0);
regs[BPF_REG_0].type = PTR_TO_BTF_ID | PTR_UNTRUSTED;
regs[BPF_REG_0].btf = desc_btf;
regs[BPF_REG_0].btf_id = meta->arg_constant.value;
} else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_slice] ||
meta->func_id == special_kfunc_list[KF_bpf_dynptr_slice_rdwr]) {
enum bpf_type_flag type_flag = get_dynptr_type_flag(meta->initialized_dynptr.type);
Expand Down Expand Up @@ -14410,6 +14429,13 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
return -EACCES;
}

/*
* Accesses to untrusted PTR_TO_MEM are done through probe
* instructions, hence no need to track offsets.
*/
if (base_type(ptr_reg->type) == PTR_TO_MEM && (ptr_reg->type & PTR_UNTRUSTED))
return 0;

switch (base_type(ptr_reg->type)) {
case PTR_TO_CTX:
case PTR_TO_MAP_VALUE:
Expand Down Expand Up @@ -19567,10 +19593,27 @@ static bool reg_type_mismatch(enum bpf_reg_type src, enum bpf_reg_type prev)
!reg_type_mismatch_ok(prev));
}

static bool is_ptr_to_mem_or_btf_id(enum bpf_reg_type type)
{
switch (base_type(type)) {
case PTR_TO_MEM:
case PTR_TO_BTF_ID:
return true;
default:
return false;
}
}

static bool is_ptr_to_mem(enum bpf_reg_type type)
{
return base_type(type) == PTR_TO_MEM;
}

static int save_aux_ptr_type(struct bpf_verifier_env *env, enum bpf_reg_type type,
bool allow_trust_mismatch)
{
enum bpf_reg_type *prev_type = &env->insn_aux_data[env->insn_idx].ptr_type;
enum bpf_reg_type merged_type;

if (*prev_type == NOT_INIT) {
/* Saw a valid insn
Expand All @@ -19587,15 +19630,24 @@ static int save_aux_ptr_type(struct bpf_verifier_env *env, enum bpf_reg_type typ
* Reject it.
*/
if (allow_trust_mismatch &&
base_type(type) == PTR_TO_BTF_ID &&
base_type(*prev_type) == PTR_TO_BTF_ID) {
is_ptr_to_mem_or_btf_id(type) &&
is_ptr_to_mem_or_btf_id(*prev_type)) {
/*
* Have to support a use case when one path through
* the program yields TRUSTED pointer while another
* is UNTRUSTED. Fallback to UNTRUSTED to generate
* BPF_PROBE_MEM/BPF_PROBE_MEMSX.
* Same behavior of MEM_RDONLY flag.
*/
*prev_type = PTR_TO_BTF_ID | PTR_UNTRUSTED;
if (is_ptr_to_mem(type) || is_ptr_to_mem(*prev_type))
merged_type = PTR_TO_MEM;
else
merged_type = PTR_TO_BTF_ID;
if ((type & PTR_UNTRUSTED) || (*prev_type & PTR_UNTRUSTED))
merged_type |= PTR_UNTRUSTED;
if ((type & MEM_RDONLY) || (*prev_type & MEM_RDONLY))
merged_type |= MEM_RDONLY;
*prev_type = merged_type;
} else {
verbose(env, "same insn cannot be used with different pointers\n");
return -EINVAL;
Expand Down Expand Up @@ -21203,6 +21255,7 @@ static int convert_ctx_accesses(struct bpf_verifier_env *env)
* for this case.
*/
case PTR_TO_BTF_ID | MEM_ALLOC | PTR_UNTRUSTED:
case PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED:
if (type == BPF_READ) {
if (BPF_MODE(insn->code) == BPF_MEM)
insn->code = BPF_LDX | BPF_PROBE_MEM |
Expand Down Expand Up @@ -24388,6 +24441,8 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3
u32 log_true_size;
bool is_priv;

BTF_TYPE_EMIT(enum bpf_features);

/* no program is valid */
if (ARRAY_SIZE(bpf_verifier_ops) == 0)
return -EINVAL;
Expand Down
68 changes: 20 additions & 48 deletions kernel/events/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -2645,41 +2645,6 @@ void perf_event_disable_inatomic(struct perf_event *event)
static void perf_log_throttle(struct perf_event *event, int enable);
static void perf_log_itrace_start(struct perf_event *event);

static void perf_event_unthrottle(struct perf_event *event, bool start)
{
event->hw.interrupts = 0;
if (start)
event->pmu->start(event, 0);
if (event == event->group_leader)
perf_log_throttle(event, 1);
}

static void perf_event_throttle(struct perf_event *event)
{
event->pmu->stop(event, 0);
event->hw.interrupts = MAX_INTERRUPTS;
if (event == event->group_leader)
perf_log_throttle(event, 0);
}

static void perf_event_unthrottle_group(struct perf_event *event, bool skip_start_event)
{
struct perf_event *sibling, *leader = event->group_leader;

perf_event_unthrottle(leader, skip_start_event ? leader != event : true);
for_each_sibling_event(sibling, leader)
perf_event_unthrottle(sibling, skip_start_event ? sibling != event : true);
}

static void perf_event_throttle_group(struct perf_event *event)
{
struct perf_event *sibling, *leader = event->group_leader;

perf_event_throttle(leader);
for_each_sibling_event(sibling, leader)
perf_event_throttle(sibling);
}

static int
event_sched_in(struct perf_event *event, struct perf_event_context *ctx)
{
Expand Down Expand Up @@ -2708,8 +2673,10 @@ event_sched_in(struct perf_event *event, struct perf_event_context *ctx)
* ticks already, also for a heavily scheduling task there is little
* guarantee it'll get a tick in a timely manner.
*/
if (unlikely(event->hw.interrupts == MAX_INTERRUPTS))
perf_event_unthrottle(event, false);
if (unlikely(event->hw.interrupts == MAX_INTERRUPTS)) {
perf_log_throttle(event, 1);
event->hw.interrupts = 0;
}

perf_pmu_disable(event->pmu);

Expand Down Expand Up @@ -4287,8 +4254,12 @@ static void perf_adjust_freq_unthr_events(struct list_head *event_list)

hwc = &event->hw;

if (hwc->interrupts == MAX_INTERRUPTS)
perf_event_unthrottle_group(event, is_event_in_freq_mode(event));
if (hwc->interrupts == MAX_INTERRUPTS) {
hwc->interrupts = 0;
perf_log_throttle(event, 1);
if (!is_event_in_freq_mode(event))
event->pmu->start(event, 0);
}

if (!is_event_in_freq_mode(event))
continue;
Expand Down Expand Up @@ -6210,21 +6181,21 @@ static void __perf_event_period(struct perf_event *event,
active = (event->state == PERF_EVENT_STATE_ACTIVE);
if (active) {
perf_pmu_disable(event->pmu);
/*
* We could be throttled; unthrottle now to avoid the tick
* trying to unthrottle while we already re-started the event.
*/
if (event->hw.interrupts == MAX_INTERRUPTS) {
event->hw.interrupts = 0;
perf_log_throttle(event, 1);
}
event->pmu->stop(event, PERF_EF_UPDATE);
}

local64_set(&event->hw.period_left, 0);

if (active) {
event->pmu->start(event, PERF_EF_RELOAD);
/*
* Once the period is force-reset, the event starts immediately.
* But the event/group could be throttled. Unthrottle the
* event/group now to avoid the next tick trying to unthrottle
* while we already re-started the event/group.
*/
if (event->hw.interrupts == MAX_INTERRUPTS)
perf_event_unthrottle_group(event, true);
perf_pmu_enable(event->pmu);
}
}
Expand Down Expand Up @@ -10113,7 +10084,8 @@ __perf_event_account_interrupt(struct perf_event *event, int throttle)
if (unlikely(throttle && hwc->interrupts >= max_samples_per_tick)) {
__this_cpu_inc(perf_throttled_count);
tick_dep_set_cpu(smp_processor_id(), TICK_DEP_BIT_PERF_EVENTS);
perf_event_throttle_group(event);
hwc->interrupts = MAX_INTERRUPTS;
perf_log_throttle(event, 0);
ret = 1;
}

Expand Down
8 changes: 8 additions & 0 deletions tools/testing/selftests/bpf/prog_tests/mem_rdonly_untrusted.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: GPL-2.0-only

#include <test_progs.h>
#include "mem_rdonly_untrusted.skel.h"

void test_mem_rdonly_untrusted(void) {
RUN_TESTS(mem_rdonly_untrusted);
}
Loading
Loading