Commit d7a25270 authored by Andrii Nakryiko's avatar Andrii Nakryiko Committed by Daniel Borkmann

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: default avatarAndrii Nakryiko <andriin@fb.com>
Signed-off-by: default avatarDaniel Borkmann <daniel@iogearbox.net>
Acked-by: default avatarMartin KaFai Lau <kafai@fb.com>
Link: https://lore.kernel.org/bpf/20200124053837.2434679-1-andriin@fb.com
parent 51bad0f0
...@@ -345,7 +345,6 @@ struct bpf_object { ...@@ -345,7 +345,6 @@ struct bpf_object {
bool loaded; bool loaded;
bool has_pseudo_calls; bool has_pseudo_calls;
bool relaxed_core_relocs;
/* /*
* Information when doing elf related work. Only valid if fd * 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, ...@@ -4238,25 +4237,38 @@ static int bpf_core_calc_field_relo(const struct bpf_program *prog,
*/ */
static int bpf_core_reloc_insn(struct bpf_program *prog, static int bpf_core_reloc_insn(struct bpf_program *prog,
const struct bpf_field_reloc *relo, const struct bpf_field_reloc *relo,
int relo_idx,
const struct bpf_core_spec *local_spec, const struct bpf_core_spec *local_spec,
const struct bpf_core_spec *targ_spec) const struct bpf_core_spec *targ_spec)
{ {
bool failed = false, validate = true;
__u32 orig_val, new_val; __u32 orig_val, new_val;
struct bpf_insn *insn; struct bpf_insn *insn;
bool validate = true;
int insn_idx, err; int insn_idx, err;
__u8 class; __u8 class;
if (relo->insn_off % sizeof(struct bpf_insn)) if (relo->insn_off % sizeof(struct bpf_insn))
return -EINVAL; return -EINVAL;
insn_idx = relo->insn_off / sizeof(struct bpf_insn); insn_idx = relo->insn_off / sizeof(struct bpf_insn);
insn = &prog->insns[insn_idx];
class = BPF_CLASS(insn->code);
if (relo->kind == BPF_FIELD_EXISTS) { if (relo->kind == BPF_FIELD_EXISTS) {
orig_val = 1; /* can't generate EXISTS relo w/o local field */ orig_val = 1; /* can't generate EXISTS relo w/o local field */
new_val = targ_spec ? 1 : 0; new_val = targ_spec ? 1 : 0;
} else if (!targ_spec) { } else if (!targ_spec) {
failed = true; pr_debug("prog '%s': relo #%d: substituting insn #%d w/ invalid insn\n",
new_val = (__u32)-1; bpf_program__title(prog, false), relo_idx, insn_idx);
insn->code = BPF_JMP | BPF_CALL;
insn->dst_reg = 0;
insn->src_reg = 0;
insn->off = 0;
/* if this instruction is reachable (not a dead code),
* verifier will complain with the following message:
* invalid func unknown#195896080
*/
insn->imm = 195896080; /* => 0xbad2310 => "bad relo" */
return 0;
} else { } else {
err = bpf_core_calc_field_relo(prog, relo, local_spec, err = bpf_core_calc_field_relo(prog, relo, local_spec,
&orig_val, &validate); &orig_val, &validate);
...@@ -4268,50 +4280,47 @@ static int bpf_core_reloc_insn(struct bpf_program *prog, ...@@ -4268,50 +4280,47 @@ static int bpf_core_reloc_insn(struct bpf_program *prog,
return err; return err;
} }
insn = &prog->insns[insn_idx];
class = BPF_CLASS(insn->code);
switch (class) { switch (class) {
case BPF_ALU: case BPF_ALU:
case BPF_ALU64: case BPF_ALU64:
if (BPF_SRC(insn->code) != BPF_K) if (BPF_SRC(insn->code) != BPF_K)
return -EINVAL; return -EINVAL;
if (!failed && validate && insn->imm != orig_val) { if (validate && insn->imm != orig_val) {
pr_warn("prog '%s': unexpected insn #%d (ALU/ALU64) value: got %u, exp %u -> %u\n", pr_warn("prog '%s': relo #%d: unexpected insn #%d (ALU/ALU64) value: got %u, exp %u -> %u\n",
bpf_program__title(prog, false), insn_idx, bpf_program__title(prog, false), relo_idx,
insn->imm, orig_val, new_val); insn_idx, insn->imm, orig_val, new_val);
return -EINVAL; return -EINVAL;
} }
orig_val = insn->imm; orig_val = insn->imm;
insn->imm = new_val; insn->imm = new_val;
pr_debug("prog '%s': patched insn #%d (ALU/ALU64)%s imm %u -> %u\n", pr_debug("prog '%s': relo #%d: patched insn #%d (ALU/ALU64) imm %u -> %u\n",
bpf_program__title(prog, false), insn_idx, bpf_program__title(prog, false), relo_idx, insn_idx,
failed ? " w/ failed reloc" : "", orig_val, new_val); orig_val, new_val);
break; break;
case BPF_LDX: case BPF_LDX:
case BPF_ST: case BPF_ST:
case BPF_STX: case BPF_STX:
if (!failed && validate && insn->off != orig_val) { if (validate && insn->off != orig_val) {
pr_warn("prog '%s': unexpected insn #%d (LD/LDX/ST/STX) value: got %u, exp %u -> %u\n", pr_warn("prog '%s': relo #%d: unexpected insn #%d (LD/LDX/ST/STX) value: got %u, exp %u -> %u\n",
bpf_program__title(prog, false), insn_idx, bpf_program__title(prog, false), relo_idx,
insn->off, orig_val, new_val); insn_idx, insn->off, orig_val, new_val);
return -EINVAL; return -EINVAL;
} }
if (new_val > SHRT_MAX) { if (new_val > SHRT_MAX) {
pr_warn("prog '%s': insn #%d (LD/LDX/ST/STX) value too big: %u\n", pr_warn("prog '%s': relo #%d: insn #%d (LDX/ST/STX) value too big: %u\n",
bpf_program__title(prog, false), insn_idx, bpf_program__title(prog, false), relo_idx,
new_val); insn_idx, new_val);
return -ERANGE; return -ERANGE;
} }
orig_val = insn->off; orig_val = insn->off;
insn->off = new_val; insn->off = new_val;
pr_debug("prog '%s': patched insn #%d (LD/LDX/ST/STX)%s off %u -> %u\n", pr_debug("prog '%s': relo #%d: patched insn #%d (LDX/ST/STX) off %u -> %u\n",
bpf_program__title(prog, false), insn_idx, bpf_program__title(prog, false), relo_idx, insn_idx,
failed ? " w/ failed reloc" : "", orig_val, new_val); orig_val, new_val);
break; break;
default: default:
pr_warn("prog '%s': trying to relocate unrecognized insn #%d, code:%x, src:%x, dst:%x, off:%x, imm:%x\n", pr_warn("prog '%s': relo #%d: trying to relocate unrecognized insn #%d, code:%x, src:%x, dst:%x, off:%x, imm:%x\n",
bpf_program__title(prog, false), bpf_program__title(prog, false), relo_idx,
insn_idx, insn->code, insn->src_reg, insn->dst_reg, insn_idx, insn->code, insn->src_reg, insn->dst_reg,
insn->off, insn->imm); insn->off, insn->imm);
return -EINVAL; return -EINVAL;
...@@ -4510,24 +4519,33 @@ static int bpf_core_reloc_field(struct bpf_program *prog, ...@@ -4510,24 +4519,33 @@ static int bpf_core_reloc_field(struct bpf_program *prog,
} }
/* /*
* For BPF_FIELD_EXISTS relo or when relaxed CO-RE reloc mode is * For BPF_FIELD_EXISTS relo or when used BPF program has field
* requested, it's expected that we might not find any candidates. * existence checks or kernel version/config checks, it's expected
* In this case, if field wasn't found in any candidate, the list of * that we might not find any candidates. In this case, if field
* candidates shouldn't change at all, we'll just handle relocating * wasn't found in any candidate, the list of candidates shouldn't
* appropriately, depending on relo's kind. * change at all, we'll just handle relocating appropriately,
* depending on relo's kind.
*/ */
if (j > 0) if (j > 0)
cand_ids->len = j; cand_ids->len = j;
if (j == 0 && !prog->obj->relaxed_core_relocs && /*
relo->kind != BPF_FIELD_EXISTS) { * If no candidates were found, it might be both a programmer error,
pr_warn("prog '%s': relo #%d: no matching targets found for [%d] %s + %s\n", * as well as expected case, depending whether instruction w/
* relocation is guarded in some way that makes it unreachable (dead
* code) if relocation can't be resolved. This is handled in
* bpf_core_reloc_insn() uniformly by replacing that instruction with
* BPF helper call insn (using invalid helper ID). If that instruction
* is indeed unreachable, then it will be ignored and eliminated by
* verifier. If it was an error, then verifier will complain and point
* to a specific instruction number in its log.
*/
if (j == 0)
pr_debug("prog '%s': relo #%d: no matching targets found for [%d] %s + %s\n",
prog_name, relo_idx, local_id, local_name, spec_str); prog_name, relo_idx, local_id, local_name, spec_str);
return -ESRCH;
}
/* bpf_core_reloc_insn should know how to handle missing targ_spec */ /* bpf_core_reloc_insn should know how to handle missing targ_spec */
err = bpf_core_reloc_insn(prog, relo, &local_spec, err = bpf_core_reloc_insn(prog, relo, relo_idx, &local_spec,
j ? &targ_spec : NULL); j ? &targ_spec : NULL);
if (err) { if (err) {
pr_warn("prog '%s': relo #%d: failed to patch insn at offset %d: %d\n", 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, ...@@ -5057,7 +5075,6 @@ __bpf_object__open(const char *path, const void *obj_buf, size_t obj_buf_sz,
if (IS_ERR(obj)) if (IS_ERR(obj))
return obj; return obj;
obj->relaxed_core_relocs = OPTS_GET(opts, relaxed_core_relocs, false);
kconfig = OPTS_GET(opts, kconfig, NULL); kconfig = OPTS_GET(opts, kconfig, NULL);
if (kconfig) { if (kconfig) {
obj->kconfig = strdup(kconfig); obj->kconfig = strdup(kconfig);
......
...@@ -77,7 +77,11 @@ struct bpf_object_open_opts { ...@@ -77,7 +77,11 @@ struct bpf_object_open_opts {
const char *object_name; const char *object_name;
/* parse map definitions non-strictly, allowing extra attributes/data */ /* parse map definitions non-strictly, allowing extra attributes/data */
bool relaxed_maps; bool relaxed_maps;
/* process CO-RE relocations non-strictly, allowing them to fail */ /* DEPRECATED: handle CO-RE relocations non-strictly, allowing failures.
* Value is ignored. Relocations always are processed non-strictly.
* Non-relocatable instructions are replaced with invalid ones to
* prevent accidental errors.
* */
bool relaxed_core_relocs; bool relaxed_core_relocs;
/* maps that set the 'pinning' attribute in their definition will have /* maps that set the 'pinning' attribute in their definition will have
* their pin_path attribute set to a file in this directory, and be * their pin_path attribute set to a file in this directory, and be
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment