Skip to content

Commit 353c788

Browse files
anakryikoAlexei Starovoitov
authored andcommitted
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 <[email protected]> Signed-off-by: Alexei Starovoitov <[email protected]> Link: https://lore.kernel.org/bpf/[email protected]
1 parent 28b93c6 commit 353c788

File tree

1 file changed

+124
-46
lines changed

1 file changed

+124
-46
lines changed

tools/lib/bpf/libbpf.c

Lines changed: 124 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4616,14 +4616,25 @@ static int bpf_core_calc_field_relo(const struct bpf_program *prog,
46164616
const struct bpf_core_spec *spec,
46174617
__u32 *val, bool *validate)
46184618
{
4619-
const struct bpf_core_accessor *acc = &spec->spec[spec->len - 1];
4620-
const struct btf_type *t = btf__type_by_id(spec->btf, acc->type_id);
4619+
const struct bpf_core_accessor *acc;
4620+
const struct btf_type *t;
46214621
__u32 byte_off, byte_sz, bit_off, bit_sz;
46224622
const struct btf_member *m;
46234623
const struct btf_type *mt;
46244624
bool bitfield;
46254625
__s64 sz;
46264626

4627+
if (relo->kind == BPF_FIELD_EXISTS) {
4628+
*val = spec ? 1 : 0;
4629+
return 0;
4630+
}
4631+
4632+
if (!spec)
4633+
return -EUCLEAN; /* request instruction poisoning */
4634+
4635+
acc = &spec->spec[spec->len - 1];
4636+
t = btf__type_by_id(spec->btf, acc->type_id);
4637+
46274638
/* a[n] accessor needs special handling */
46284639
if (!acc->name) {
46294640
if (relo->kind == BPF_FIELD_BYTE_OFFSET) {
@@ -4709,21 +4720,88 @@ static int bpf_core_calc_field_relo(const struct bpf_program *prog,
47094720
break;
47104721
case BPF_FIELD_EXISTS:
47114722
default:
4712-
pr_warn("prog '%s': unknown relo %d at insn #%d\n",
4713-
bpf_program__title(prog, false),
4714-
relo->kind, relo->insn_off / 8);
4715-
return -EINVAL;
4723+
return -EOPNOTSUPP;
47164724
}
47174725

47184726
return 0;
47194727
}
47204728

4729+
struct bpf_core_relo_res
4730+
{
4731+
/* expected value in the instruction, unless validate == false */
4732+
__u32 orig_val;
4733+
/* new value that needs to be patched up to */
4734+
__u32 new_val;
4735+
/* relocation unsuccessful, poison instruction, but don't fail load */
4736+
bool poison;
4737+
/* some relocations can't be validated against orig_val */
4738+
bool validate;
4739+
};
4740+
4741+
/* Calculate original and target relocation values, given local and target
4742+
* specs and relocation kind. These values are calculated for each candidate.
4743+
* If there are multiple candidates, resulting values should all be consistent
4744+
* with each other. Otherwise, libbpf will refuse to proceed due to ambiguity.
4745+
* If instruction has to be poisoned, *poison will be set to true.
4746+
*/
4747+
static int bpf_core_calc_relo(const struct bpf_program *prog,
4748+
const struct bpf_core_relo *relo,
4749+
int relo_idx,
4750+
const struct bpf_core_spec *local_spec,
4751+
const struct bpf_core_spec *targ_spec,
4752+
struct bpf_core_relo_res *res)
4753+
{
4754+
int err = -EOPNOTSUPP;
4755+
4756+
res->orig_val = 0;
4757+
res->new_val = 0;
4758+
res->poison = false;
4759+
res->validate = true;
4760+
4761+
if (core_relo_is_field_based(relo->kind)) {
4762+
err = bpf_core_calc_field_relo(prog, relo, local_spec, &res->orig_val, &res->validate);
4763+
err = err ?: bpf_core_calc_field_relo(prog, relo, targ_spec, &res->new_val, NULL);
4764+
}
4765+
4766+
if (err == -EUCLEAN) {
4767+
/* EUCLEAN is used to signal instruction poisoning request */
4768+
res->poison = true;
4769+
err = 0;
4770+
} else if (err == -EOPNOTSUPP) {
4771+
/* EOPNOTSUPP means unknown/unsupported relocation */
4772+
pr_warn("prog '%s': relo #%d: unrecognized CO-RE relocation %s (%d) at insn #%d\n",
4773+
bpf_program__title(prog, false), relo_idx,
4774+
core_relo_kind_str(relo->kind), relo->kind, relo->insn_off / 8);
4775+
}
4776+
4777+
return err;
4778+
}
4779+
4780+
/*
4781+
* Turn instruction for which CO_RE relocation failed into invalid one with
4782+
* distinct signature.
4783+
*/
4784+
static void bpf_core_poison_insn(struct bpf_program *prog, int relo_idx,
4785+
int insn_idx, struct bpf_insn *insn)
4786+
{
4787+
pr_debug("prog '%s': relo #%d: substituting insn #%d w/ invalid insn\n",
4788+
bpf_program__title(prog, false), relo_idx, insn_idx);
4789+
insn->code = BPF_JMP | BPF_CALL;
4790+
insn->dst_reg = 0;
4791+
insn->src_reg = 0;
4792+
insn->off = 0;
4793+
/* if this instruction is reachable (not a dead code),
4794+
* verifier will complain with the following message:
4795+
* invalid func unknown#195896080
4796+
*/
4797+
insn->imm = 195896080; /* => 0xbad2310 => "bad relo" */
4798+
}
4799+
47214800
/*
47224801
* Patch relocatable BPF instruction.
47234802
*
47244803
* Patched value is determined by relocation kind and target specification.
4725-
* For field existence relocation target spec will be NULL if field is not
4726-
* found.
4804+
* For existence relocations target spec will be NULL if field/type is not found.
47274805
* Expected insn->imm value is determined using relocation kind and local
47284806
* spec, and is checked before patching instruction. If actual insn->imm value
47294807
* is wrong, bail out with error.
@@ -4732,16 +4810,14 @@ static int bpf_core_calc_field_relo(const struct bpf_program *prog,
47324810
* 1. rX = <imm> (assignment with immediate operand);
47334811
* 2. rX += <imm> (arithmetic operations with immediate operand);
47344812
*/
4735-
static int bpf_core_reloc_insn(struct bpf_program *prog,
4813+
static int bpf_core_patch_insn(struct bpf_program *prog,
47364814
const struct bpf_core_relo *relo,
47374815
int relo_idx,
4738-
const struct bpf_core_spec *local_spec,
4739-
const struct bpf_core_spec *targ_spec)
4816+
const struct bpf_core_relo_res *res)
47404817
{
47414818
__u32 orig_val, new_val;
47424819
struct bpf_insn *insn;
4743-
bool validate = true;
4744-
int insn_idx, err;
4820+
int insn_idx;
47454821
__u8 class;
47464822

47474823
if (relo->insn_off % sizeof(struct bpf_insn))
@@ -4750,39 +4826,20 @@ static int bpf_core_reloc_insn(struct bpf_program *prog,
47504826
insn = &prog->insns[insn_idx];
47514827
class = BPF_CLASS(insn->code);
47524828

4753-
if (relo->kind == BPF_FIELD_EXISTS) {
4754-
orig_val = 1; /* can't generate EXISTS relo w/o local field */
4755-
new_val = targ_spec ? 1 : 0;
4756-
} else if (!targ_spec) {
4757-
pr_debug("prog '%s': relo #%d: substituting insn #%d w/ invalid insn\n",
4758-
bpf_program__title(prog, false), relo_idx, insn_idx);
4759-
insn->code = BPF_JMP | BPF_CALL;
4760-
insn->dst_reg = 0;
4761-
insn->src_reg = 0;
4762-
insn->off = 0;
4763-
/* if this instruction is reachable (not a dead code),
4764-
* verifier will complain with the following message:
4765-
* invalid func unknown#195896080
4766-
*/
4767-
insn->imm = 195896080; /* => 0xbad2310 => "bad relo" */
4829+
if (res->poison) {
4830+
bpf_core_poison_insn(prog, relo_idx, insn_idx, insn);
47684831
return 0;
4769-
} else {
4770-
err = bpf_core_calc_field_relo(prog, relo, local_spec,
4771-
&orig_val, &validate);
4772-
if (err)
4773-
return err;
4774-
err = bpf_core_calc_field_relo(prog, relo, targ_spec,
4775-
&new_val, NULL);
4776-
if (err)
4777-
return err;
47784832
}
47794833

4834+
orig_val = res->orig_val;
4835+
new_val = res->new_val;
4836+
47804837
switch (class) {
47814838
case BPF_ALU:
47824839
case BPF_ALU64:
47834840
if (BPF_SRC(insn->code) != BPF_K)
47844841
return -EINVAL;
4785-
if (validate && insn->imm != orig_val) {
4842+
if (res->validate && insn->imm != orig_val) {
47864843
pr_warn("prog '%s': relo #%d: unexpected insn #%d (ALU/ALU64) value: got %u, exp %u -> %u\n",
47874844
bpf_program__title(prog, false), relo_idx,
47884845
insn_idx, insn->imm, orig_val, new_val);
@@ -4797,7 +4854,7 @@ static int bpf_core_reloc_insn(struct bpf_program *prog,
47974854
case BPF_LDX:
47984855
case BPF_ST:
47994856
case BPF_STX:
4800-
if (validate && insn->off != orig_val) {
4857+
if (res->validate && insn->off != orig_val) {
48014858
pr_warn("prog '%s': relo #%d: unexpected insn #%d (LD/LDX/ST/STX) value: got %u, exp %u -> %u\n",
48024859
bpf_program__title(prog, false), relo_idx,
48034860
insn_idx, insn->off, orig_val, new_val);
@@ -4938,6 +4995,7 @@ static int bpf_core_reloc_field(struct bpf_program *prog,
49384995
const char *prog_name = bpf_program__title(prog, false);
49394996
struct bpf_core_spec local_spec, cand_spec, targ_spec;
49404997
const void *type_key = u32_as_hash_key(relo->type_id);
4998+
struct bpf_core_relo_res cand_res, targ_res;
49414999
const struct btf_type *local_type;
49425000
const char *local_name;
49435001
struct ids_vec *cand_ids;
@@ -5005,16 +5063,31 @@ static int bpf_core_reloc_field(struct bpf_program *prog,
50055063
if (err == 0)
50065064
continue;
50075065

5066+
err = bpf_core_calc_relo(prog, relo, relo_idx, &local_spec, &cand_spec, &cand_res);
5067+
if (err)
5068+
return err;
5069+
50085070
if (j == 0) {
5071+
targ_res = cand_res;
50095072
targ_spec = cand_spec;
50105073
} else if (cand_spec.bit_offset != targ_spec.bit_offset) {
5011-
/* if there are many candidates, they should all
5012-
* resolve to the same bit offset
5074+
/* if there are many field relo candidates, they
5075+
* should all resolve to the same bit offset
50135076
*/
5014-
pr_warn("prog '%s': relo #%d: offset ambiguity: %u != %u\n",
5077+
pr_warn("prog '%s': relo #%d: field offset ambiguity: %u != %u\n",
50155078
prog_name, relo_idx, cand_spec.bit_offset,
50165079
targ_spec.bit_offset);
50175080
return -EINVAL;
5081+
} else if (cand_res.poison != targ_res.poison || cand_res.new_val != targ_res.new_val) {
5082+
/* all candidates should result in the same relocation
5083+
* decision and value, otherwise it's dangerous to
5084+
* proceed due to ambiguity
5085+
*/
5086+
pr_warn("prog '%s': relo #%d: relocation decision ambiguity: %s %u != %s %u\n",
5087+
prog_name, relo_idx,
5088+
cand_res.poison ? "failure" : "success", cand_res.new_val,
5089+
targ_res.poison ? "failure" : "success", targ_res.new_val);
5090+
return -EINVAL;
50185091
}
50195092

50205093
cand_ids->data[j++] = cand_spec.spec[0].type_id;
@@ -5042,13 +5115,18 @@ static int bpf_core_reloc_field(struct bpf_program *prog,
50425115
* verifier. If it was an error, then verifier will complain and point
50435116
* to a specific instruction number in its log.
50445117
*/
5045-
if (j == 0)
5118+
if (j == 0) {
50465119
pr_debug("prog '%s': relo #%d: no matching targets found\n",
50475120
prog_name, relo_idx);
50485121

5049-
/* bpf_core_reloc_insn should know how to handle missing targ_spec */
5050-
err = bpf_core_reloc_insn(prog, relo, relo_idx, &local_spec,
5051-
j ? &targ_spec : NULL);
5122+
/* calculate single target relo result explicitly */
5123+
err = bpf_core_calc_relo(prog, relo, relo_idx, &local_spec, NULL, &targ_res);
5124+
if (err)
5125+
return err;
5126+
}
5127+
5128+
/* bpf_core_patch_insn() should know how to handle missing targ_spec */
5129+
err = bpf_core_patch_insn(prog, relo, relo_idx, &targ_res);
50525130
if (err) {
50535131
pr_warn("prog '%s': relo #%d: failed to patch insn at offset %d: %d\n",
50545132
prog_name, relo_idx, relo->insn_off, err);

0 commit comments

Comments
 (0)