Commit a2c70dbc authored by Alexei Starovoitov's avatar Alexei Starovoitov
Browse files

Merge branch 'libbpf: allow to opt-out from BPF map creation'



Andrii Nakryiko says:

====================

Add bpf_map__set_autocreate() API which is a BPF map counterpart of
bpf_program__set_autoload() and serves similar goal of allowing to build more
flexible CO-RE applications. See patch #3 for example scenarios in which the
need for such API came up previously.

Patch #1 is a follow-up patch to previous patch set adding verifier log fixup
logic, making sure bpf_core_format_spec()'s return result is used for
something useful.

Patch #2 is a small refactoring to avoid unnecessary verbose memory management
around obj->maps array.

Patch #3 adds and API and corresponding BPF verifier log fix up logic to
provide human-comprehensible error message with useful details.

Patch #4 adds a simple selftest validating both the API itself and libbpf's
log fixup logic for it.
====================

Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
parents 32c03c49 68964e15
Loading
Loading
Loading
Loading
+122 −47
Original line number Diff line number Diff line
@@ -357,6 +357,7 @@ enum libbpf_map_type {
};

struct bpf_map {
	struct bpf_object *obj;
	char *name;
	/* real_name is defined for special internal maps (.rodata*,
	 * .data*, .bss, .kconfig) and preserves their original ELF section
@@ -386,7 +387,7 @@ struct bpf_map {
	char *pin_path;
	bool pinned;
	bool reused;
	bool skipped;
	bool autocreate;
	__u64 map_extra;
};

@@ -1433,36 +1434,21 @@ static int find_elf_var_offset(const struct bpf_object *obj, const char *name, _

static struct bpf_map *bpf_object__add_map(struct bpf_object *obj)
{
	struct bpf_map *new_maps;
	size_t new_cap;
	int i;

	if (obj->nr_maps < obj->maps_cap)
		return &obj->maps[obj->nr_maps++];

	new_cap = max((size_t)4, obj->maps_cap * 3 / 2);
	new_maps = libbpf_reallocarray(obj->maps, new_cap, sizeof(*obj->maps));
	if (!new_maps) {
		pr_warn("alloc maps for object failed\n");
		return ERR_PTR(-ENOMEM);
	}
	struct bpf_map *map;
	int err;

	obj->maps_cap = new_cap;
	obj->maps = new_maps;
	err = libbpf_ensure_mem((void **)&obj->maps, &obj->maps_cap,
				sizeof(*obj->maps), obj->nr_maps + 1);
	if (err)
		return ERR_PTR(err);

	/* zero out new maps */
	memset(obj->maps + obj->nr_maps, 0,
	       (obj->maps_cap - obj->nr_maps) * sizeof(*obj->maps));
	/*
	 * fill all fd with -1 so won't close incorrect fd (fd=0 is stdin)
	 * when failure (zclose won't close negative fd)).
	 */
	for (i = obj->nr_maps; i < obj->maps_cap; i++) {
		obj->maps[i].fd = -1;
		obj->maps[i].inner_map_fd = -1;
	}
	map = &obj->maps[obj->nr_maps++];
	map->obj = obj;
	map->fd = -1;
	map->inner_map_fd = -1;
	map->autocreate = true;

	return &obj->maps[obj->nr_maps++];
	return map;
}

static size_t bpf_map_mmap_sz(const struct bpf_map *map)
@@ -4324,6 +4310,20 @@ static int bpf_get_map_info_from_fdinfo(int fd, struct bpf_map_info *info)
	return 0;
}

bool bpf_map__autocreate(const struct bpf_map *map)
{
	return map->autocreate;
}

int bpf_map__set_autocreate(struct bpf_map *map, bool autocreate)
{
	if (map->obj->loaded)
		return libbpf_err(-EBUSY);

	map->autocreate = autocreate;
	return 0;
}

int bpf_map__reuse_fd(struct bpf_map *map, int fd)
{
	struct bpf_map_info info = {};
@@ -5180,9 +5180,11 @@ bpf_object__create_maps(struct bpf_object *obj)
		 * bpf_object loading will succeed just fine even on old
		 * kernels.
		 */
		if (bpf_map__is_internal(map) &&
		    !kernel_supports(obj, FEAT_GLOBAL_DATA)) {
			map->skipped = true;
		if (bpf_map__is_internal(map) && !kernel_supports(obj, FEAT_GLOBAL_DATA))
			map->autocreate = false;

		if (!map->autocreate) {
			pr_debug("map '%s': skipped auto-creating...\n", map->name);
			continue;
		}

@@ -5805,6 +5807,36 @@ bpf_object__relocate_core(struct bpf_object *obj, const char *targ_btf_path)
	return err;
}

/* base map load ldimm64 special constant, used also for log fixup logic */
#define MAP_LDIMM64_POISON_BASE 2001000000
#define MAP_LDIMM64_POISON_PFX "200100"

static void poison_map_ldimm64(struct bpf_program *prog, int relo_idx,
			       int insn_idx, struct bpf_insn *insn,
			       int map_idx, const struct bpf_map *map)
{
	int i;

	pr_debug("prog '%s': relo #%d: poisoning insn #%d that loads map #%d '%s'\n",
		 prog->name, relo_idx, insn_idx, map_idx, map->name);

	/* we turn single ldimm64 into two identical invalid calls */
	for (i = 0; i < 2; i++) {
		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 something like:
		 * invalid func unknown#2001000123
		 * where lower 123 is map index into obj->maps[] array
		 */
		insn->imm = MAP_LDIMM64_POISON_BASE + map_idx;

		insn++;
	}
}

/* Relocate data references within program code:
 *  - map references;
 *  - global variable references;
@@ -5818,33 +5850,35 @@ bpf_object__relocate_data(struct bpf_object *obj, struct bpf_program *prog)
	for (i = 0; i < prog->nr_reloc; i++) {
		struct reloc_desc *relo = &prog->reloc_desc[i];
		struct bpf_insn *insn = &prog->insns[relo->insn_idx];
		const struct bpf_map *map;
		struct extern_desc *ext;

		switch (relo->type) {
		case RELO_LD64:
			map = &obj->maps[relo->map_idx];
			if (obj->gen_loader) {
				insn[0].src_reg = BPF_PSEUDO_MAP_IDX;
				insn[0].imm = relo->map_idx;
			} else {
			} else if (map->autocreate) {
				insn[0].src_reg = BPF_PSEUDO_MAP_FD;
				insn[0].imm = obj->maps[relo->map_idx].fd;
				insn[0].imm = map->fd;
			} else {
				poison_map_ldimm64(prog, i, relo->insn_idx, insn,
						   relo->map_idx, map);
			}
			break;
		case RELO_DATA:
			map = &obj->maps[relo->map_idx];
			insn[1].imm = insn[0].imm + relo->sym_off;
			if (obj->gen_loader) {
				insn[0].src_reg = BPF_PSEUDO_MAP_IDX_VALUE;
				insn[0].imm = relo->map_idx;
			} else {
				const struct bpf_map *map = &obj->maps[relo->map_idx];

				if (map->skipped) {
					pr_warn("prog '%s': relo #%d: kernel doesn't support global data\n",
						prog->name, i);
					return -ENOTSUP;
				}
			} else if (map->autocreate) {
				insn[0].src_reg = BPF_PSEUDO_MAP_VALUE;
				insn[0].imm = obj->maps[relo->map_idx].fd;
				insn[0].imm = map->fd;
			} else {
				poison_map_ldimm64(prog, i, relo->insn_idx, insn,
						   relo->map_idx, map);
			}
			break;
		case RELO_EXTERN_VAR:
@@ -6962,7 +6996,7 @@ static void fixup_log_failed_core_relo(struct bpf_program *prog,
	const struct bpf_core_relo *relo;
	struct bpf_core_spec spec;
	char patch[512], spec_buf[256];
	int insn_idx, err;
	int insn_idx, err, spec_len;

	if (sscanf(line1, "%d: (%*d) call unknown#195896080\n", &insn_idx) != 1)
		return;
@@ -6975,11 +7009,44 @@ static void fixup_log_failed_core_relo(struct bpf_program *prog,
	if (err)
		return;

	bpf_core_format_spec(spec_buf, sizeof(spec_buf), &spec);
	spec_len = bpf_core_format_spec(spec_buf, sizeof(spec_buf), &spec);
	snprintf(patch, sizeof(patch),
		 "%d: <invalid CO-RE relocation>\n"
		 "failed to resolve CO-RE relocation %s\n",
		 insn_idx, spec_buf);
		 "failed to resolve CO-RE relocation %s%s\n",
		 insn_idx, spec_buf, spec_len >= sizeof(spec_buf) ? "..." : "");

	patch_log(buf, buf_sz, log_sz, line1, line3 - line1, patch);
}

static void fixup_log_missing_map_load(struct bpf_program *prog,
				       char *buf, size_t buf_sz, size_t log_sz,
				       char *line1, char *line2, char *line3)
{
	/* Expected log for failed and not properly guarded CO-RE relocation:
	 * line1 -> 123: (85) call unknown#2001000345
	 * line2 -> invalid func unknown#2001000345
	 * line3 -> <anything else or end of buffer>
	 *
	 * "123" is the index of the instruction that was poisoned.
	 * "345" in "2001000345" are map index in obj->maps to fetch map name.
	 */
	struct bpf_object *obj = prog->obj;
	const struct bpf_map *map;
	int insn_idx, map_idx;
	char patch[128];

	if (sscanf(line1, "%d: (%*d) call unknown#%d\n", &insn_idx, &map_idx) != 2)
		return;

	map_idx -= MAP_LDIMM64_POISON_BASE;
	if (map_idx < 0 || map_idx >= obj->nr_maps)
		return;
	map = &obj->maps[map_idx];

	snprintf(patch, sizeof(patch),
		 "%d: <invalid BPF map reference>\n"
		 "BPF map '%s' is referenced but wasn't created\n",
		 insn_idx, map->name);

	patch_log(buf, buf_sz, log_sz, line1, line3 - line1, patch);
}
@@ -7012,6 +7079,14 @@ static void fixup_verifier_log(struct bpf_program *prog, char *buf, size_t buf_s
			fixup_log_failed_core_relo(prog, buf, buf_sz, log_sz,
						   prev_line, cur_line, next_line);
			return;
		} else if (str_has_pfx(cur_line, "invalid func unknown#"MAP_LDIMM64_POISON_PFX)) {
			prev_line = find_prev_line(buf, cur_line);
			if (!prev_line)
				continue;

			fixup_log_missing_map_load(prog, buf, buf_sz, log_sz,
						   prev_line, cur_line, next_line);
			return;
		}
	}
}
@@ -8200,7 +8275,7 @@ int bpf_object__pin_maps(struct bpf_object *obj, const char *path)
		char *pin_path = NULL;
		char buf[PATH_MAX];

		if (map->skipped)
		if (!map->autocreate)
			continue;

		if (path) {
+22 −0
Original line number Diff line number Diff line
@@ -866,6 +866,28 @@ struct bpf_map *bpf_map__prev(const struct bpf_map *map, const struct bpf_object
LIBBPF_API struct bpf_map *
bpf_object__prev_map(const struct bpf_object *obj, const struct bpf_map *map);

/**
 * @brief **bpf_map__set_autocreate()** sets whether libbpf has to auto-create
 * BPF map during BPF object load phase.
 * @param map the BPF map instance
 * @param autocreate whether to create BPF map during BPF object load
 * @return 0 on success; -EBUSY if BPF object was already loaded
 *
 * **bpf_map__set_autocreate()** allows to opt-out from libbpf auto-creating
 * BPF map. By default, libbpf will attempt to create every single BPF map
 * defined in BPF object file using BPF_MAP_CREATE command of bpf() syscall
 * and fill in map FD in BPF instructions.
 *
 * This API allows to opt-out of this process for specific map instance. This
 * can be useful if host kernel doesn't support such BPF map type or used
 * combination of flags and user application wants to avoid creating such
 * a map in the first place. User is still responsible to make sure that their
 * BPF-side code that expects to use such missing BPF map is recognized by BPF
 * verifier as dead code, otherwise BPF verifier will reject such BPF program.
 */
LIBBPF_API int bpf_map__set_autocreate(struct bpf_map *map, bool autocreate);
LIBBPF_API bool bpf_map__autocreate(const struct bpf_map *map);

/**
 * @brief **bpf_map__fd()** gets the file descriptor of the passed
 * BPF map
+3 −1
Original line number Diff line number Diff line
@@ -442,10 +442,12 @@ LIBBPF_0.7.0 {

LIBBPF_0.8.0 {
	global:
		bpf_map__autocreate;
		bpf_map__set_autocreate;
		bpf_object__destroy_subskeleton;
		bpf_object__open_subskeleton;
		bpf_program__attach_kprobe_multi_opts;
		bpf_program__attach_usdt;
		libbpf_register_prog_handler;
		libbpf_unregister_prog_handler;
		bpf_program__attach_kprobe_multi_opts;
} LIBBPF_0.7.0;
+36 −1
Original line number Diff line number Diff line
@@ -85,7 +85,6 @@ static void bad_core_relo_subprog(void)
	if (!ASSERT_ERR(err, "load_fail"))
		goto cleanup;

	/* there should be no prog loading log because we specified per-prog log buf */
	ASSERT_HAS_SUBSTR(log_buf,
			  ": <invalid CO-RE relocation>\n"
			  "failed to resolve CO-RE relocation <byte_off> ",
@@ -101,6 +100,40 @@ static void bad_core_relo_subprog(void)
	test_log_fixup__destroy(skel);
}

static void missing_map(void)
{
	char log_buf[8 * 1024];
	struct test_log_fixup* skel;
	int err;

	skel = test_log_fixup__open();
	if (!ASSERT_OK_PTR(skel, "skel_open"))
		return;

	bpf_map__set_autocreate(skel->maps.missing_map, false);

	bpf_program__set_autoload(skel->progs.use_missing_map, true);
	bpf_program__set_log_buf(skel->progs.use_missing_map, log_buf, sizeof(log_buf));

	err = test_log_fixup__load(skel);
	if (!ASSERT_ERR(err, "load_fail"))
		goto cleanup;

	ASSERT_TRUE(bpf_map__autocreate(skel->maps.existing_map), "existing_map_autocreate");
	ASSERT_FALSE(bpf_map__autocreate(skel->maps.missing_map), "missing_map_autocreate");

	ASSERT_HAS_SUBSTR(log_buf,
			  "8: <invalid BPF map reference>\n"
			  "BPF map 'missing_map' is referenced but wasn't created\n",
			  "log_buf");

	if (env.verbosity > VERBOSE_NONE)
		printf("LOG:   \n=================\n%s=================\n", log_buf);

cleanup:
	test_log_fixup__destroy(skel);
}

void test_log_fixup(void)
{
	if (test__start_subtest("bad_core_relo_trunc_none"))
@@ -111,4 +144,6 @@ void test_log_fixup(void)
		bad_core_relo(250, TRUNC_FULL  /* truncate also libbpf's message patch */);
	if (test__start_subtest("bad_core_relo_subprog"))
		bad_core_relo_subprog();
	if (test__start_subtest("missing_map"))
		missing_map();
}
+26 −0
Original line number Diff line number Diff line
@@ -35,4 +35,30 @@ int bad_relo_subprog(const void *ctx)
	return bad_subprog() + bpf_core_field_size(t->pid);
}

struct {
	__uint(type, BPF_MAP_TYPE_ARRAY);
	__uint(max_entries, 1);
	__type(key, int);
	__type(value, int);
} existing_map SEC(".maps");

struct {
	__uint(type, BPF_MAP_TYPE_ARRAY);
	__uint(max_entries, 1);
	__type(key, int);
	__type(value, int);
} missing_map SEC(".maps");

SEC("?raw_tp/sys_enter")
int use_missing_map(const void *ctx)
{
	int zero = 0, *value;

	value = bpf_map_lookup_elem(&existing_map, &zero);

	value = bpf_map_lookup_elem(&missing_map, &zero);

	return value != NULL;
}

char _license[] SEC("license") = "GPL";