Skip to content

Commit d7a2527

Browse files
anakryikoborkmann
authored andcommitted
libbpf: Improve handling of failed CO-RE relocations
Previously, if libbpf failed to resolve CO-RE relocation for some instructions, it would either return error immediately, or, if .relaxed_core_relocs option was set, would replace relocatable offset/imm part of an instruction with a bogus value (-1). Neither approach is good, because there are many possible scenarios where relocation is expected to fail (e.g., when some field knowingly can be missing on specific kernel versions). On the other hand, replacing offset with invalid one can hide programmer errors, if this relocation failue wasn't anticipated. This patch deprecates .relaxed_core_relocs option and changes the approach to always replacing instruction, for which relocation failed, with invalid BPF helper call instruction. For cases where this is expected, BPF program should already ensure that that instruction is unreachable, in which case this invalid instruction is going to be silently ignored. But if instruction wasn't guarded, BPF program will be rejected at verification step with verifier log pointing precisely to the place in assembly where the problem is. Signed-off-by: Andrii Nakryiko <[email protected]> Signed-off-by: Daniel Borkmann <[email protected]> Acked-by: Martin KaFai Lau <[email protected]> Link: https://lore.kernel.org/bpf/[email protected]
1 parent 51bad0f commit d7a2527

File tree

2 files changed

+61
-40
lines changed

2 files changed

+61
-40
lines changed

tools/lib/bpf/libbpf.c

Lines changed: 56 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,6 @@ struct bpf_object {
345345

346346
bool loaded;
347347
bool has_pseudo_calls;
348-
bool relaxed_core_relocs;
349348

350349
/*
351350
* Information when doing elf related work. Only valid if fd
@@ -4238,25 +4237,38 @@ static int bpf_core_calc_field_relo(const struct bpf_program *prog,
42384237
*/
42394238
static int bpf_core_reloc_insn(struct bpf_program *prog,
42404239
const struct bpf_field_reloc *relo,
4240+
int relo_idx,
42414241
const struct bpf_core_spec *local_spec,
42424242
const struct bpf_core_spec *targ_spec)
42434243
{
4244-
bool failed = false, validate = true;
42454244
__u32 orig_val, new_val;
42464245
struct bpf_insn *insn;
4246+
bool validate = true;
42474247
int insn_idx, err;
42484248
__u8 class;
42494249

42504250
if (relo->insn_off % sizeof(struct bpf_insn))
42514251
return -EINVAL;
42524252
insn_idx = relo->insn_off / sizeof(struct bpf_insn);
4253+
insn = &prog->insns[insn_idx];
4254+
class = BPF_CLASS(insn->code);
42534255

42544256
if (relo->kind == BPF_FIELD_EXISTS) {
42554257
orig_val = 1; /* can't generate EXISTS relo w/o local field */
42564258
new_val = targ_spec ? 1 : 0;
42574259
} else if (!targ_spec) {
4258-
failed = true;
4259-
new_val = (__u32)-1;
4260+
pr_debug("prog '%s': relo #%d: substituting insn #%d w/ invalid insn\n",
4261+
bpf_program__title(prog, false), relo_idx, insn_idx);
4262+
insn->code = BPF_JMP | BPF_CALL;
4263+
insn->dst_reg = 0;
4264+
insn->src_reg = 0;
4265+
insn->off = 0;
4266+
/* if this instruction is reachable (not a dead code),
4267+
* verifier will complain with the following message:
4268+
* invalid func unknown#195896080
4269+
*/
4270+
insn->imm = 195896080; /* => 0xbad2310 => "bad relo" */
4271+
return 0;
42604272
} else {
42614273
err = bpf_core_calc_field_relo(prog, relo, local_spec,
42624274
&orig_val, &validate);
@@ -4268,50 +4280,47 @@ static int bpf_core_reloc_insn(struct bpf_program *prog,
42684280
return err;
42694281
}
42704282

4271-
insn = &prog->insns[insn_idx];
4272-
class = BPF_CLASS(insn->code);
4273-
42744283
switch (class) {
42754284
case BPF_ALU:
42764285
case BPF_ALU64:
42774286
if (BPF_SRC(insn->code) != BPF_K)
42784287
return -EINVAL;
4279-
if (!failed && validate && insn->imm != orig_val) {
4280-
pr_warn("prog '%s': unexpected insn #%d (ALU/ALU64) value: got %u, exp %u -> %u\n",
4281-
bpf_program__title(prog, false), insn_idx,
4282-
insn->imm, orig_val, new_val);
4288+
if (validate && insn->imm != orig_val) {
4289+
pr_warn("prog '%s': relo #%d: unexpected insn #%d (ALU/ALU64) value: got %u, exp %u -> %u\n",
4290+
bpf_program__title(prog, false), relo_idx,
4291+
insn_idx, insn->imm, orig_val, new_val);
42834292
return -EINVAL;
42844293
}
42854294
orig_val = insn->imm;
42864295
insn->imm = new_val;
4287-
pr_debug("prog '%s': patched insn #%d (ALU/ALU64)%s imm %u -> %u\n",
4288-
bpf_program__title(prog, false), insn_idx,
4289-
failed ? " w/ failed reloc" : "", orig_val, new_val);
4296+
pr_debug("prog '%s': relo #%d: patched insn #%d (ALU/ALU64) imm %u -> %u\n",
4297+
bpf_program__title(prog, false), relo_idx, insn_idx,
4298+
orig_val, new_val);
42904299
break;
42914300
case BPF_LDX:
42924301
case BPF_ST:
42934302
case BPF_STX:
4294-
if (!failed && validate && insn->off != orig_val) {
4295-
pr_warn("prog '%s': unexpected insn #%d (LD/LDX/ST/STX) value: got %u, exp %u -> %u\n",
4296-
bpf_program__title(prog, false), insn_idx,
4297-
insn->off, orig_val, new_val);
4303+
if (validate && insn->off != orig_val) {
4304+
pr_warn("prog '%s': relo #%d: unexpected insn #%d (LD/LDX/ST/STX) value: got %u, exp %u -> %u\n",
4305+
bpf_program__title(prog, false), relo_idx,
4306+
insn_idx, insn->off, orig_val, new_val);
42984307
return -EINVAL;
42994308
}
43004309
if (new_val > SHRT_MAX) {
4301-
pr_warn("prog '%s': insn #%d (LD/LDX/ST/STX) value too big: %u\n",
4302-
bpf_program__title(prog, false), insn_idx,
4303-
new_val);
4310+
pr_warn("prog '%s': relo #%d: insn #%d (LDX/ST/STX) value too big: %u\n",
4311+
bpf_program__title(prog, false), relo_idx,
4312+
insn_idx, new_val);
43044313
return -ERANGE;
43054314
}
43064315
orig_val = insn->off;
43074316
insn->off = new_val;
4308-
pr_debug("prog '%s': patched insn #%d (LD/LDX/ST/STX)%s off %u -> %u\n",
4309-
bpf_program__title(prog, false), insn_idx,
4310-
failed ? " w/ failed reloc" : "", orig_val, new_val);
4317+
pr_debug("prog '%s': relo #%d: patched insn #%d (LDX/ST/STX) off %u -> %u\n",
4318+
bpf_program__title(prog, false), relo_idx, insn_idx,
4319+
orig_val, new_val);
43114320
break;
43124321
default:
4313-
pr_warn("prog '%s': trying to relocate unrecognized insn #%d, code:%x, src:%x, dst:%x, off:%x, imm:%x\n",
4314-
bpf_program__title(prog, false),
4322+
pr_warn("prog '%s': relo #%d: trying to relocate unrecognized insn #%d, code:%x, src:%x, dst:%x, off:%x, imm:%x\n",
4323+
bpf_program__title(prog, false), relo_idx,
43154324
insn_idx, insn->code, insn->src_reg, insn->dst_reg,
43164325
insn->off, insn->imm);
43174326
return -EINVAL;
@@ -4510,24 +4519,33 @@ static int bpf_core_reloc_field(struct bpf_program *prog,
45104519
}
45114520

45124521
/*
4513-
* For BPF_FIELD_EXISTS relo or when relaxed CO-RE reloc mode is
4514-
* requested, it's expected that we might not find any candidates.
4515-
* In this case, if field wasn't found in any candidate, the list of
4516-
* candidates shouldn't change at all, we'll just handle relocating
4517-
* appropriately, depending on relo's kind.
4522+
* For BPF_FIELD_EXISTS relo or when used BPF program has field
4523+
* existence checks or kernel version/config checks, it's expected
4524+
* that we might not find any candidates. In this case, if field
4525+
* wasn't found in any candidate, the list of candidates shouldn't
4526+
* change at all, we'll just handle relocating appropriately,
4527+
* depending on relo's kind.
45184528
*/
45194529
if (j > 0)
45204530
cand_ids->len = j;
45214531

4522-
if (j == 0 && !prog->obj->relaxed_core_relocs &&
4523-
relo->kind != BPF_FIELD_EXISTS) {
4524-
pr_warn("prog '%s': relo #%d: no matching targets found for [%d] %s + %s\n",
4525-
prog_name, relo_idx, local_id, local_name, spec_str);
4526-
return -ESRCH;
4527-
}
4532+
/*
4533+
* If no candidates were found, it might be both a programmer error,
4534+
* as well as expected case, depending whether instruction w/
4535+
* relocation is guarded in some way that makes it unreachable (dead
4536+
* code) if relocation can't be resolved. This is handled in
4537+
* bpf_core_reloc_insn() uniformly by replacing that instruction with
4538+
* BPF helper call insn (using invalid helper ID). If that instruction
4539+
* is indeed unreachable, then it will be ignored and eliminated by
4540+
* verifier. If it was an error, then verifier will complain and point
4541+
* to a specific instruction number in its log.
4542+
*/
4543+
if (j == 0)
4544+
pr_debug("prog '%s': relo #%d: no matching targets found for [%d] %s + %s\n",
4545+
prog_name, relo_idx, local_id, local_name, spec_str);
45284546

45294547
/* bpf_core_reloc_insn should know how to handle missing targ_spec */
4530-
err = bpf_core_reloc_insn(prog, relo, &local_spec,
4548+
err = bpf_core_reloc_insn(prog, relo, relo_idx, &local_spec,
45314549
j ? &targ_spec : NULL);
45324550
if (err) {
45334551
pr_warn("prog '%s': relo #%d: failed to patch insn at offset %d: %d\n",
@@ -5057,7 +5075,6 @@ __bpf_object__open(const char *path, const void *obj_buf, size_t obj_buf_sz,
50575075
if (IS_ERR(obj))
50585076
return obj;
50595077

5060-
obj->relaxed_core_relocs = OPTS_GET(opts, relaxed_core_relocs, false);
50615078
kconfig = OPTS_GET(opts, kconfig, NULL);
50625079
if (kconfig) {
50635080
obj->kconfig = strdup(kconfig);

tools/lib/bpf/libbpf.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,11 @@ struct bpf_object_open_opts {
7777
const char *object_name;
7878
/* parse map definitions non-strictly, allowing extra attributes/data */
7979
bool relaxed_maps;
80-
/* process CO-RE relocations non-strictly, allowing them to fail */
80+
/* DEPRECATED: handle CO-RE relocations non-strictly, allowing failures.
81+
* Value is ignored. Relocations always are processed non-strictly.
82+
* Non-relocatable instructions are replaced with invalid ones to
83+
* prevent accidental errors.
84+
* */
8185
bool relaxed_core_relocs;
8286
/* maps that set the 'pinning' attribute in their definition will have
8387
* their pin_path attribute set to a file in this directory, and be

0 commit comments

Comments
 (0)