libbpf: Improve relocation ambiguity detection
Split the instruction patching logic into relocation value calculation and application of relocation to instruction. Using this, evaluate relocation against each matching candidate and validate that all candidates agree on relocated value. If not, report ambiguity and fail load. This logic is necessary to avoid dangerous (however unlikely) accidental match against two incompatible candidate types. Without this change, libbpf will pick a random type as *the* candidate and apply potentially invalid relocation. Signed-off-by: Andrii Nakryiko <andriin@fb.com> Signed-off-by: Alexei Starovoitov <ast@kernel.org> Link: https://lore.kernel.org/bpf/20200818223921.2911963-4-andriin@fb.com
This commit is contained in:
Родитель
28b93c6449
Коммит
353c788c20
|
@ -4616,14 +4616,25 @@ static int bpf_core_calc_field_relo(const struct bpf_program *prog,
|
||||||
const struct bpf_core_spec *spec,
|
const struct bpf_core_spec *spec,
|
||||||
__u32 *val, bool *validate)
|
__u32 *val, bool *validate)
|
||||||
{
|
{
|
||||||
const struct bpf_core_accessor *acc = &spec->spec[spec->len - 1];
|
const struct bpf_core_accessor *acc;
|
||||||
const struct btf_type *t = btf__type_by_id(spec->btf, acc->type_id);
|
const struct btf_type *t;
|
||||||
__u32 byte_off, byte_sz, bit_off, bit_sz;
|
__u32 byte_off, byte_sz, bit_off, bit_sz;
|
||||||
const struct btf_member *m;
|
const struct btf_member *m;
|
||||||
const struct btf_type *mt;
|
const struct btf_type *mt;
|
||||||
bool bitfield;
|
bool bitfield;
|
||||||
__s64 sz;
|
__s64 sz;
|
||||||
|
|
||||||
|
if (relo->kind == BPF_FIELD_EXISTS) {
|
||||||
|
*val = spec ? 1 : 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!spec)
|
||||||
|
return -EUCLEAN; /* request instruction poisoning */
|
||||||
|
|
||||||
|
acc = &spec->spec[spec->len - 1];
|
||||||
|
t = btf__type_by_id(spec->btf, acc->type_id);
|
||||||
|
|
||||||
/* a[n] accessor needs special handling */
|
/* a[n] accessor needs special handling */
|
||||||
if (!acc->name) {
|
if (!acc->name) {
|
||||||
if (relo->kind == BPF_FIELD_BYTE_OFFSET) {
|
if (relo->kind == BPF_FIELD_BYTE_OFFSET) {
|
||||||
|
@ -4709,21 +4720,88 @@ static int bpf_core_calc_field_relo(const struct bpf_program *prog,
|
||||||
break;
|
break;
|
||||||
case BPF_FIELD_EXISTS:
|
case BPF_FIELD_EXISTS:
|
||||||
default:
|
default:
|
||||||
pr_warn("prog '%s': unknown relo %d at insn #%d\n",
|
return -EOPNOTSUPP;
|
||||||
bpf_program__title(prog, false),
|
|
||||||
relo->kind, relo->insn_off / 8);
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct bpf_core_relo_res
|
||||||
|
{
|
||||||
|
/* expected value in the instruction, unless validate == false */
|
||||||
|
__u32 orig_val;
|
||||||
|
/* new value that needs to be patched up to */
|
||||||
|
__u32 new_val;
|
||||||
|
/* relocation unsuccessful, poison instruction, but don't fail load */
|
||||||
|
bool poison;
|
||||||
|
/* some relocations can't be validated against orig_val */
|
||||||
|
bool validate;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Calculate original and target relocation values, given local and target
|
||||||
|
* specs and relocation kind. These values are calculated for each candidate.
|
||||||
|
* If there are multiple candidates, resulting values should all be consistent
|
||||||
|
* with each other. Otherwise, libbpf will refuse to proceed due to ambiguity.
|
||||||
|
* If instruction has to be poisoned, *poison will be set to true.
|
||||||
|
*/
|
||||||
|
static int bpf_core_calc_relo(const struct bpf_program *prog,
|
||||||
|
const struct bpf_core_relo *relo,
|
||||||
|
int relo_idx,
|
||||||
|
const struct bpf_core_spec *local_spec,
|
||||||
|
const struct bpf_core_spec *targ_spec,
|
||||||
|
struct bpf_core_relo_res *res)
|
||||||
|
{
|
||||||
|
int err = -EOPNOTSUPP;
|
||||||
|
|
||||||
|
res->orig_val = 0;
|
||||||
|
res->new_val = 0;
|
||||||
|
res->poison = false;
|
||||||
|
res->validate = true;
|
||||||
|
|
||||||
|
if (core_relo_is_field_based(relo->kind)) {
|
||||||
|
err = bpf_core_calc_field_relo(prog, relo, local_spec, &res->orig_val, &res->validate);
|
||||||
|
err = err ?: bpf_core_calc_field_relo(prog, relo, targ_spec, &res->new_val, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err == -EUCLEAN) {
|
||||||
|
/* EUCLEAN is used to signal instruction poisoning request */
|
||||||
|
res->poison = true;
|
||||||
|
err = 0;
|
||||||
|
} else if (err == -EOPNOTSUPP) {
|
||||||
|
/* EOPNOTSUPP means unknown/unsupported relocation */
|
||||||
|
pr_warn("prog '%s': relo #%d: unrecognized CO-RE relocation %s (%d) at insn #%d\n",
|
||||||
|
bpf_program__title(prog, false), relo_idx,
|
||||||
|
core_relo_kind_str(relo->kind), relo->kind, relo->insn_off / 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Turn instruction for which CO_RE relocation failed into invalid one with
|
||||||
|
* distinct signature.
|
||||||
|
*/
|
||||||
|
static void bpf_core_poison_insn(struct bpf_program *prog, int relo_idx,
|
||||||
|
int insn_idx, struct bpf_insn *insn)
|
||||||
|
{
|
||||||
|
pr_debug("prog '%s': relo #%d: substituting insn #%d w/ invalid insn\n",
|
||||||
|
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" */
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Patch relocatable BPF instruction.
|
* Patch relocatable BPF instruction.
|
||||||
*
|
*
|
||||||
* Patched value is determined by relocation kind and target specification.
|
* Patched value is determined by relocation kind and target specification.
|
||||||
* For field existence relocation target spec will be NULL if field is not
|
* For existence relocations target spec will be NULL if field/type is not found.
|
||||||
* found.
|
|
||||||
* Expected insn->imm value is determined using relocation kind and local
|
* Expected insn->imm value is determined using relocation kind and local
|
||||||
* spec, and is checked before patching instruction. If actual insn->imm value
|
* spec, and is checked before patching instruction. If actual insn->imm value
|
||||||
* is wrong, bail out with error.
|
* is wrong, bail out with error.
|
||||||
|
@ -4732,16 +4810,14 @@ static int bpf_core_calc_field_relo(const struct bpf_program *prog,
|
||||||
* 1. rX = <imm> (assignment with immediate operand);
|
* 1. rX = <imm> (assignment with immediate operand);
|
||||||
* 2. rX += <imm> (arithmetic operations with immediate operand);
|
* 2. rX += <imm> (arithmetic operations with immediate operand);
|
||||||
*/
|
*/
|
||||||
static int bpf_core_reloc_insn(struct bpf_program *prog,
|
static int bpf_core_patch_insn(struct bpf_program *prog,
|
||||||
const struct bpf_core_relo *relo,
|
const struct bpf_core_relo *relo,
|
||||||
int relo_idx,
|
int relo_idx,
|
||||||
const struct bpf_core_spec *local_spec,
|
const struct bpf_core_relo_res *res)
|
||||||
const struct bpf_core_spec *targ_spec)
|
|
||||||
{
|
{
|
||||||
__u32 orig_val, new_val;
|
__u32 orig_val, new_val;
|
||||||
struct bpf_insn *insn;
|
struct bpf_insn *insn;
|
||||||
bool validate = true;
|
int insn_idx;
|
||||||
int insn_idx, err;
|
|
||||||
__u8 class;
|
__u8 class;
|
||||||
|
|
||||||
if (relo->insn_off % sizeof(struct bpf_insn))
|
if (relo->insn_off % sizeof(struct bpf_insn))
|
||||||
|
@ -4750,39 +4826,20 @@ static int bpf_core_reloc_insn(struct bpf_program *prog,
|
||||||
insn = &prog->insns[insn_idx];
|
insn = &prog->insns[insn_idx];
|
||||||
class = BPF_CLASS(insn->code);
|
class = BPF_CLASS(insn->code);
|
||||||
|
|
||||||
if (relo->kind == BPF_FIELD_EXISTS) {
|
if (res->poison) {
|
||||||
orig_val = 1; /* can't generate EXISTS relo w/o local field */
|
bpf_core_poison_insn(prog, relo_idx, insn_idx, insn);
|
||||||
new_val = targ_spec ? 1 : 0;
|
|
||||||
} else if (!targ_spec) {
|
|
||||||
pr_debug("prog '%s': relo #%d: substituting insn #%d w/ invalid insn\n",
|
|
||||||
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;
|
return 0;
|
||||||
} else {
|
|
||||||
err = bpf_core_calc_field_relo(prog, relo, local_spec,
|
|
||||||
&orig_val, &validate);
|
|
||||||
if (err)
|
|
||||||
return err;
|
|
||||||
err = bpf_core_calc_field_relo(prog, relo, targ_spec,
|
|
||||||
&new_val, NULL);
|
|
||||||
if (err)
|
|
||||||
return err;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
orig_val = res->orig_val;
|
||||||
|
new_val = res->new_val;
|
||||||
|
|
||||||
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 (validate && insn->imm != orig_val) {
|
if (res->validate && insn->imm != orig_val) {
|
||||||
pr_warn("prog '%s': relo #%d: 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), relo_idx,
|
bpf_program__title(prog, false), relo_idx,
|
||||||
insn_idx, insn->imm, orig_val, new_val);
|
insn_idx, insn->imm, orig_val, new_val);
|
||||||
|
@ -4797,7 +4854,7 @@ static int bpf_core_reloc_insn(struct bpf_program *prog,
|
||||||
case BPF_LDX:
|
case BPF_LDX:
|
||||||
case BPF_ST:
|
case BPF_ST:
|
||||||
case BPF_STX:
|
case BPF_STX:
|
||||||
if (validate && insn->off != orig_val) {
|
if (res->validate && insn->off != orig_val) {
|
||||||
pr_warn("prog '%s': relo #%d: 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), relo_idx,
|
bpf_program__title(prog, false), relo_idx,
|
||||||
insn_idx, insn->off, orig_val, new_val);
|
insn_idx, insn->off, orig_val, new_val);
|
||||||
|
@ -4938,6 +4995,7 @@ static int bpf_core_reloc_field(struct bpf_program *prog,
|
||||||
const char *prog_name = bpf_program__title(prog, false);
|
const char *prog_name = bpf_program__title(prog, false);
|
||||||
struct bpf_core_spec local_spec, cand_spec, targ_spec;
|
struct bpf_core_spec local_spec, cand_spec, targ_spec;
|
||||||
const void *type_key = u32_as_hash_key(relo->type_id);
|
const void *type_key = u32_as_hash_key(relo->type_id);
|
||||||
|
struct bpf_core_relo_res cand_res, targ_res;
|
||||||
const struct btf_type *local_type;
|
const struct btf_type *local_type;
|
||||||
const char *local_name;
|
const char *local_name;
|
||||||
struct ids_vec *cand_ids;
|
struct ids_vec *cand_ids;
|
||||||
|
@ -5005,16 +5063,31 @@ static int bpf_core_reloc_field(struct bpf_program *prog,
|
||||||
if (err == 0)
|
if (err == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
err = bpf_core_calc_relo(prog, relo, relo_idx, &local_spec, &cand_spec, &cand_res);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
if (j == 0) {
|
if (j == 0) {
|
||||||
|
targ_res = cand_res;
|
||||||
targ_spec = cand_spec;
|
targ_spec = cand_spec;
|
||||||
} else if (cand_spec.bit_offset != targ_spec.bit_offset) {
|
} else if (cand_spec.bit_offset != targ_spec.bit_offset) {
|
||||||
/* if there are many candidates, they should all
|
/* if there are many field relo candidates, they
|
||||||
* resolve to the same bit offset
|
* should all resolve to the same bit offset
|
||||||
*/
|
*/
|
||||||
pr_warn("prog '%s': relo #%d: offset ambiguity: %u != %u\n",
|
pr_warn("prog '%s': relo #%d: field offset ambiguity: %u != %u\n",
|
||||||
prog_name, relo_idx, cand_spec.bit_offset,
|
prog_name, relo_idx, cand_spec.bit_offset,
|
||||||
targ_spec.bit_offset);
|
targ_spec.bit_offset);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
} else if (cand_res.poison != targ_res.poison || cand_res.new_val != targ_res.new_val) {
|
||||||
|
/* all candidates should result in the same relocation
|
||||||
|
* decision and value, otherwise it's dangerous to
|
||||||
|
* proceed due to ambiguity
|
||||||
|
*/
|
||||||
|
pr_warn("prog '%s': relo #%d: relocation decision ambiguity: %s %u != %s %u\n",
|
||||||
|
prog_name, relo_idx,
|
||||||
|
cand_res.poison ? "failure" : "success", cand_res.new_val,
|
||||||
|
targ_res.poison ? "failure" : "success", targ_res.new_val);
|
||||||
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
cand_ids->data[j++] = cand_spec.spec[0].type_id;
|
cand_ids->data[j++] = cand_spec.spec[0].type_id;
|
||||||
|
@ -5042,13 +5115,18 @@ static int bpf_core_reloc_field(struct bpf_program *prog,
|
||||||
* verifier. If it was an error, then verifier will complain and point
|
* verifier. If it was an error, then verifier will complain and point
|
||||||
* to a specific instruction number in its log.
|
* to a specific instruction number in its log.
|
||||||
*/
|
*/
|
||||||
if (j == 0)
|
if (j == 0) {
|
||||||
pr_debug("prog '%s': relo #%d: no matching targets found\n",
|
pr_debug("prog '%s': relo #%d: no matching targets found\n",
|
||||||
prog_name, relo_idx);
|
prog_name, relo_idx);
|
||||||
|
|
||||||
/* bpf_core_reloc_insn should know how to handle missing targ_spec */
|
/* calculate single target relo result explicitly */
|
||||||
err = bpf_core_reloc_insn(prog, relo, relo_idx, &local_spec,
|
err = bpf_core_calc_relo(prog, relo, relo_idx, &local_spec, NULL, &targ_res);
|
||||||
j ? &targ_spec : NULL);
|
if (err)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* bpf_core_patch_insn() should know how to handle missing targ_spec */
|
||||||
|
err = bpf_core_patch_insn(prog, relo, relo_idx, &targ_res);
|
||||||
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",
|
||||||
prog_name, relo_idx, relo->insn_off, err);
|
prog_name, relo_idx, relo->insn_off, err);
|
||||||
|
|
Загрузка…
Ссылка в новой задаче