mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 16:48:26 +02:00 
			
		
		
		
	libbpf: enforce strict libbpf 1.0 behaviors
Remove support for legacy features and behaviors that previously had to
be disabled by calling libbpf_set_strict_mode():
  - legacy BPF map definitions are not supported now;
  - RLIMIT_MEMLOCK auto-setting, if necessary, is always on (but see
    libbpf_set_memlock_rlim());
  - program name is used for program pinning (instead of section name);
  - cleaned up error returning logic;
  - entry BPF programs should have SEC() always.
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/r/20220627211527.2245459-15-andrii@kernel.org
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
			
			
This commit is contained in:
		
							parent
							
								
									31e4272197
								
							
						
					
					
						commit
						bd054102a8
					
				
					 5 changed files with 36 additions and 260 deletions
				
			
		|  | @ -147,10 +147,6 @@ int bump_rlimit_memlock(void) | |||
| { | ||||
| 	struct rlimit rlim; | ||||
| 
 | ||||
| 	/* this the default in libbpf 1.0, but for now user has to opt-in explicitly */ | ||||
| 	if (!(libbpf_mode & LIBBPF_STRICT_AUTO_RLIMIT_MEMLOCK)) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	/* if kernel supports memcg-based accounting, skip bumping RLIMIT_MEMLOCK */ | ||||
| 	if (memlock_bumped || kernel_supports(NULL, FEAT_MEMCG_ACCOUNT)) | ||||
| 		return 0; | ||||
|  |  | |||
|  | @ -278,12 +278,9 @@ static inline __u64 ptr_to_u64(const void *ptr) | |||
| 	return (__u64) (unsigned long) ptr; | ||||
| } | ||||
| 
 | ||||
| /* this goes away in libbpf 1.0 */ | ||||
| enum libbpf_strict_mode libbpf_mode = LIBBPF_STRICT_NONE; | ||||
| 
 | ||||
| int libbpf_set_strict_mode(enum libbpf_strict_mode mode) | ||||
| { | ||||
| 	libbpf_mode = mode; | ||||
| 	/* as of v1.0 libbpf_set_strict_mode() is a no-op */ | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
|  | @ -367,9 +364,10 @@ struct bpf_sec_def { | |||
|  * linux/filter.h. | ||||
|  */ | ||||
| struct bpf_program { | ||||
| 	const struct bpf_sec_def *sec_def; | ||||
| 	char *name; | ||||
| 	char *sec_name; | ||||
| 	size_t sec_idx; | ||||
| 	const struct bpf_sec_def *sec_def; | ||||
| 	/* this program's instruction offset (in number of instructions)
 | ||||
| 	 * within its containing ELF section | ||||
| 	 */ | ||||
|  | @ -389,12 +387,6 @@ struct bpf_program { | |||
| 	 */ | ||||
| 	size_t sub_insn_off; | ||||
| 
 | ||||
| 	char *name; | ||||
| 	/* name with / replaced by _; makes recursive pinning
 | ||||
| 	 * in bpf_object__pin_programs easier | ||||
| 	 */ | ||||
| 	char *pin_name; | ||||
| 
 | ||||
| 	/* instructions that belong to BPF program; insns[0] is located at
 | ||||
| 	 * sec_insn_off instruction within its ELF section in ELF file, so | ||||
| 	 * when mapping ELF file instruction index to the local instruction, | ||||
|  | @ -695,7 +687,6 @@ static void bpf_program__exit(struct bpf_program *prog) | |||
| 	bpf_program__unload(prog); | ||||
| 	zfree(&prog->name); | ||||
| 	zfree(&prog->sec_name); | ||||
| 	zfree(&prog->pin_name); | ||||
| 	zfree(&prog->insns); | ||||
| 	zfree(&prog->reloc_desc); | ||||
| 
 | ||||
|  | @ -704,26 +695,6 @@ static void bpf_program__exit(struct bpf_program *prog) | |||
| 	prog->sec_idx = -1; | ||||
| } | ||||
| 
 | ||||
| static char *__bpf_program__pin_name(struct bpf_program *prog) | ||||
| { | ||||
| 	char *name, *p; | ||||
| 
 | ||||
| 	if (libbpf_mode & LIBBPF_STRICT_SEC_NAME) | ||||
| 		name = strdup(prog->name); | ||||
| 	else | ||||
| 		name = strdup(prog->sec_name); | ||||
| 
 | ||||
| 	if (!name) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	p = name; | ||||
| 
 | ||||
| 	while ((p = strchr(p, '/'))) | ||||
| 		*p = '_'; | ||||
| 
 | ||||
| 	return name; | ||||
| } | ||||
| 
 | ||||
| static bool insn_is_subprog_call(const struct bpf_insn *insn) | ||||
| { | ||||
| 	return BPF_CLASS(insn->code) == BPF_JMP && | ||||
|  | @ -790,10 +761,6 @@ bpf_object__init_prog(struct bpf_object *obj, struct bpf_program *prog, | |||
| 	if (!prog->name) | ||||
| 		goto errout; | ||||
| 
 | ||||
| 	prog->pin_name = __bpf_program__pin_name(prog); | ||||
| 	if (!prog->pin_name) | ||||
| 		goto errout; | ||||
| 
 | ||||
| 	prog->insns = malloc(insn_data_sz); | ||||
| 	if (!prog->insns) | ||||
| 		goto errout; | ||||
|  | @ -2007,143 +1974,6 @@ static int bpf_object__init_kconfig_map(struct bpf_object *obj) | |||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int bpf_object__init_user_maps(struct bpf_object *obj, bool strict) | ||||
| { | ||||
| 	Elf_Data *symbols = obj->efile.symbols; | ||||
| 	int i, map_def_sz = 0, nr_maps = 0, nr_syms; | ||||
| 	Elf_Data *data = NULL; | ||||
| 	Elf_Scn *scn; | ||||
| 
 | ||||
| 	if (obj->efile.maps_shndx < 0) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	if (libbpf_mode & LIBBPF_STRICT_MAP_DEFINITIONS) { | ||||
| 		pr_warn("legacy map definitions in SEC(\"maps\") are not supported\n"); | ||||
| 		return -EOPNOTSUPP; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!symbols) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	scn = elf_sec_by_idx(obj, obj->efile.maps_shndx); | ||||
| 	data = elf_sec_data(obj, scn); | ||||
| 	if (!scn || !data) { | ||||
| 		pr_warn("elf: failed to get legacy map definitions for %s\n", | ||||
| 			obj->path); | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Count number of maps. Each map has a name. | ||||
| 	 * Array of maps is not supported: only the first element is | ||||
| 	 * considered. | ||||
| 	 * | ||||
| 	 * TODO: Detect array of map and report error. | ||||
| 	 */ | ||||
| 	nr_syms = symbols->d_size / sizeof(Elf64_Sym); | ||||
| 	for (i = 0; i < nr_syms; i++) { | ||||
| 		Elf64_Sym *sym = elf_sym_by_idx(obj, i); | ||||
| 
 | ||||
| 		if (sym->st_shndx != obj->efile.maps_shndx) | ||||
| 			continue; | ||||
| 		if (ELF64_ST_TYPE(sym->st_info) == STT_SECTION) | ||||
| 			continue; | ||||
| 		nr_maps++; | ||||
| 	} | ||||
| 	/* Assume equally sized map definitions */ | ||||
| 	pr_debug("elf: found %d legacy map definitions (%zd bytes) in %s\n", | ||||
| 		 nr_maps, data->d_size, obj->path); | ||||
| 
 | ||||
| 	if (!data->d_size || nr_maps == 0 || (data->d_size % nr_maps) != 0) { | ||||
| 		pr_warn("elf: unable to determine legacy map definition size in %s\n", | ||||
| 			obj->path); | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
| 	map_def_sz = data->d_size / nr_maps; | ||||
| 
 | ||||
| 	/* Fill obj->maps using data in "maps" section.  */ | ||||
| 	for (i = 0; i < nr_syms; i++) { | ||||
| 		Elf64_Sym *sym = elf_sym_by_idx(obj, i); | ||||
| 		const char *map_name; | ||||
| 		struct bpf_map_def *def; | ||||
| 		struct bpf_map *map; | ||||
| 
 | ||||
| 		if (sym->st_shndx != obj->efile.maps_shndx) | ||||
| 			continue; | ||||
| 		if (ELF64_ST_TYPE(sym->st_info) == STT_SECTION) | ||||
| 			continue; | ||||
| 
 | ||||
| 		map = bpf_object__add_map(obj); | ||||
| 		if (IS_ERR(map)) | ||||
| 			return PTR_ERR(map); | ||||
| 
 | ||||
| 		map_name = elf_sym_str(obj, sym->st_name); | ||||
| 		if (!map_name) { | ||||
| 			pr_warn("failed to get map #%d name sym string for obj %s\n", | ||||
| 				i, obj->path); | ||||
| 			return -LIBBPF_ERRNO__FORMAT; | ||||
| 		} | ||||
| 
 | ||||
| 		pr_warn("map '%s' (legacy): legacy map definitions are deprecated, use BTF-defined maps instead\n", map_name); | ||||
| 
 | ||||
| 		if (ELF64_ST_BIND(sym->st_info) == STB_LOCAL) { | ||||
| 			pr_warn("map '%s' (legacy): static maps are not supported\n", map_name); | ||||
| 			return -ENOTSUP; | ||||
| 		} | ||||
| 
 | ||||
| 		map->libbpf_type = LIBBPF_MAP_UNSPEC; | ||||
| 		map->sec_idx = sym->st_shndx; | ||||
| 		map->sec_offset = sym->st_value; | ||||
| 		pr_debug("map '%s' (legacy): at sec_idx %d, offset %zu.\n", | ||||
| 			 map_name, map->sec_idx, map->sec_offset); | ||||
| 		if (sym->st_value + map_def_sz > data->d_size) { | ||||
| 			pr_warn("corrupted maps section in %s: last map \"%s\" too small\n", | ||||
| 				obj->path, map_name); | ||||
| 			return -EINVAL; | ||||
| 		} | ||||
| 
 | ||||
| 		map->name = strdup(map_name); | ||||
| 		if (!map->name) { | ||||
| 			pr_warn("map '%s': failed to alloc map name\n", map_name); | ||||
| 			return -ENOMEM; | ||||
| 		} | ||||
| 		pr_debug("map %d is \"%s\"\n", i, map->name); | ||||
| 		def = (struct bpf_map_def *)(data->d_buf + sym->st_value); | ||||
| 		/*
 | ||||
| 		 * If the definition of the map in the object file fits in | ||||
| 		 * bpf_map_def, copy it.  Any extra fields in our version | ||||
| 		 * of bpf_map_def will default to zero as a result of the | ||||
| 		 * calloc above. | ||||
| 		 */ | ||||
| 		if (map_def_sz <= sizeof(struct bpf_map_def)) { | ||||
| 			memcpy(&map->def, def, map_def_sz); | ||||
| 		} else { | ||||
| 			/*
 | ||||
| 			 * Here the map structure being read is bigger than what | ||||
| 			 * we expect, truncate if the excess bits are all zero. | ||||
| 			 * If they are not zero, reject this map as | ||||
| 			 * incompatible. | ||||
| 			 */ | ||||
| 			char *b; | ||||
| 
 | ||||
| 			for (b = ((char *)def) + sizeof(struct bpf_map_def); | ||||
| 			     b < ((char *)def) + map_def_sz; b++) { | ||||
| 				if (*b != 0) { | ||||
| 					pr_warn("maps section in %s: \"%s\" has unrecognized, non-zero options\n", | ||||
| 						obj->path, map_name); | ||||
| 					if (strict) | ||||
| 						return -EINVAL; | ||||
| 				} | ||||
| 			} | ||||
| 			memcpy(&map->def, def, sizeof(struct bpf_map_def)); | ||||
| 		} | ||||
| 
 | ||||
| 		/* btf info may not exist but fill it in if it does exist */ | ||||
| 		(void) bpf_map_find_btf_info(obj, map); | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| const struct btf_type * | ||||
| skip_mods_and_typedefs(const struct btf *btf, __u32 id, __u32 *res_id) | ||||
| { | ||||
|  | @ -2700,12 +2530,11 @@ static int bpf_object__init_maps(struct bpf_object *obj, | |||
| { | ||||
| 	const char *pin_root_path; | ||||
| 	bool strict; | ||||
| 	int err; | ||||
| 	int err = 0; | ||||
| 
 | ||||
| 	strict = !OPTS_GET(opts, relaxed_maps, false); | ||||
| 	pin_root_path = OPTS_GET(opts, pin_root_path, NULL); | ||||
| 
 | ||||
| 	err = bpf_object__init_user_maps(obj, strict); | ||||
| 	err = err ?: bpf_object__init_user_btf_maps(obj, strict, pin_root_path); | ||||
| 	err = err ?: bpf_object__init_global_data_maps(obj); | ||||
| 	err = err ?: bpf_object__init_kconfig_map(obj); | ||||
|  | @ -3979,28 +3808,8 @@ static int bpf_object__collect_externs(struct bpf_object *obj) | |||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static bool prog_is_subprog(const struct bpf_object *obj, | ||||
| 			    const struct bpf_program *prog) | ||||
| static bool prog_is_subprog(const struct bpf_object *obj, const struct bpf_program *prog) | ||||
| { | ||||
| 	/* For legacy reasons, libbpf supports an entry-point BPF programs
 | ||||
| 	 * without SEC() attribute, i.e., those in the .text section. But if | ||||
| 	 * there are 2 or more such programs in the .text section, they all | ||||
| 	 * must be subprograms called from entry-point BPF programs in | ||||
| 	 * designated SEC()'tions, otherwise there is no way to distinguish | ||||
| 	 * which of those programs should be loaded vs which are a subprogram. | ||||
| 	 * Similarly, if there is a function/program in .text and at least one | ||||
| 	 * other BPF program with custom SEC() attribute, then we just assume | ||||
| 	 * .text programs are subprograms (even if they are not called from | ||||
| 	 * other programs), because libbpf never explicitly supported mixing | ||||
| 	 * SEC()-designated BPF programs and .text entry-point BPF programs. | ||||
| 	 * | ||||
| 	 * In libbpf 1.0 strict mode, we always consider .text | ||||
| 	 * programs to be subprograms. | ||||
| 	 */ | ||||
| 
 | ||||
| 	if (libbpf_mode & LIBBPF_STRICT_SEC_NAME) | ||||
| 		return prog->sec_idx == obj->efile.text_shndx; | ||||
| 
 | ||||
| 	return prog->sec_idx == obj->efile.text_shndx && obj->nr_programs > 1; | ||||
| } | ||||
| 
 | ||||
|  | @ -8163,8 +7972,7 @@ int bpf_object__pin_programs(struct bpf_object *obj, const char *path) | |||
| 		char buf[PATH_MAX]; | ||||
| 		int len; | ||||
| 
 | ||||
| 		len = snprintf(buf, PATH_MAX, "%s/%s", path, | ||||
| 			       prog->pin_name); | ||||
| 		len = snprintf(buf, PATH_MAX, "%s/%s", path, prog->name); | ||||
| 		if (len < 0) { | ||||
| 			err = -EINVAL; | ||||
| 			goto err_unpin_programs; | ||||
|  | @ -8185,8 +7993,7 @@ int bpf_object__pin_programs(struct bpf_object *obj, const char *path) | |||
| 		char buf[PATH_MAX]; | ||||
| 		int len; | ||||
| 
 | ||||
| 		len = snprintf(buf, PATH_MAX, "%s/%s", path, | ||||
| 			       prog->pin_name); | ||||
| 		len = snprintf(buf, PATH_MAX, "%s/%s", path, prog->name); | ||||
| 		if (len < 0) | ||||
| 			continue; | ||||
| 		else if (len >= PATH_MAX) | ||||
|  | @ -8210,8 +8017,7 @@ int bpf_object__unpin_programs(struct bpf_object *obj, const char *path) | |||
| 		char buf[PATH_MAX]; | ||||
| 		int len; | ||||
| 
 | ||||
| 		len = snprintf(buf, PATH_MAX, "%s/%s", path, | ||||
| 			       prog->pin_name); | ||||
| 		len = snprintf(buf, PATH_MAX, "%s/%s", path, prog->name); | ||||
| 		if (len < 0) | ||||
| 			return libbpf_err(-EINVAL); | ||||
| 		else if (len >= PATH_MAX) | ||||
|  |  | |||
|  | @ -886,40 +886,6 @@ LIBBPF_API int bpf_map__lookup_and_delete_elem(const struct bpf_map *map, | |||
| LIBBPF_API int bpf_map__get_next_key(const struct bpf_map *map, | ||||
| 				     const void *cur_key, void *next_key, size_t key_sz); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief **libbpf_get_error()** extracts the error code from the passed | ||||
|  * pointer | ||||
|  * @param ptr pointer returned from libbpf API function | ||||
|  * @return error code; or 0 if no error occured | ||||
|  * | ||||
|  * Many libbpf API functions which return pointers have logic to encode error | ||||
|  * codes as pointers, and do not return NULL. Meaning **libbpf_get_error()** | ||||
|  * should be used on the return value from these functions immediately after | ||||
|  * calling the API function, with no intervening calls that could clobber the | ||||
|  * `errno` variable. Consult the individual functions documentation to verify | ||||
|  * if this logic applies should be used. | ||||
|  * | ||||
|  * For these API functions, if `libbpf_set_strict_mode(LIBBPF_STRICT_CLEAN_PTRS)` | ||||
|  * is enabled, NULL is returned on error instead. | ||||
|  * | ||||
|  * If ptr is NULL, then errno should be already set by the failing | ||||
|  * API, because libbpf never returns NULL on success and it now always | ||||
|  * sets errno on error. | ||||
|  * | ||||
|  * Example usage: | ||||
|  * | ||||
|  *   struct perf_buffer *pb; | ||||
|  * | ||||
|  *   pb = perf_buffer__new(bpf_map__fd(obj->maps.events), PERF_BUFFER_PAGES, &opts); | ||||
|  *   err = libbpf_get_error(pb); | ||||
|  *   if (err) { | ||||
|  *	  pb = NULL; | ||||
|  *	  fprintf(stderr, "failed to open perf buffer: %d\n", err); | ||||
|  *	  goto cleanup; | ||||
|  *   } | ||||
|  */ | ||||
| LIBBPF_API long libbpf_get_error(const void *ptr); | ||||
| 
 | ||||
| struct bpf_xdp_set_link_opts { | ||||
| 	size_t sz; | ||||
| 	int old_fd; | ||||
|  |  | |||
|  | @ -15,7 +15,6 @@ | |||
| #include <linux/err.h> | ||||
| #include <fcntl.h> | ||||
| #include <unistd.h> | ||||
| #include "libbpf_legacy.h" | ||||
| #include "relo_core.h" | ||||
| 
 | ||||
| /* make sure libbpf doesn't use kernel-only integer typedefs */ | ||||
|  | @ -478,8 +477,6 @@ int btf_ext_visit_str_offs(struct btf_ext *btf_ext, str_off_visit_fn visit, void | |||
| __s32 btf__find_by_name_kind_own(const struct btf *btf, const char *type_name, | ||||
| 				 __u32 kind); | ||||
| 
 | ||||
| extern enum libbpf_strict_mode libbpf_mode; | ||||
| 
 | ||||
| typedef int (*kallsyms_cb_t)(unsigned long long sym_addr, char sym_type, | ||||
| 			     const char *sym_name, void *ctx); | ||||
| 
 | ||||
|  | @ -498,12 +495,8 @@ static inline int libbpf_err(int ret) | |||
|  */ | ||||
| static inline int libbpf_err_errno(int ret) | ||||
| { | ||||
| 	if (libbpf_mode & LIBBPF_STRICT_DIRECT_ERRS) | ||||
| 	/* errno is already assumed to be set on error */ | ||||
| 	return ret < 0 ? -errno : ret; | ||||
| 
 | ||||
| 	/* legacy: on error return -1 directly and don't touch errno */ | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| /* handle error for pointer-returning APIs, err is assumed to be < 0 always */ | ||||
|  | @ -511,12 +504,7 @@ static inline void *libbpf_err_ptr(int err) | |||
| { | ||||
| 	/* set errno on error, this doesn't break anything */ | ||||
| 	errno = -err; | ||||
| 
 | ||||
| 	if (libbpf_mode & LIBBPF_STRICT_CLEAN_PTRS) | ||||
| 	return NULL; | ||||
| 
 | ||||
| 	/* legacy: encode err as ptr */ | ||||
| 	return ERR_PTR(err); | ||||
| } | ||||
| 
 | ||||
| /* handle pointer-returning APIs' error handling */ | ||||
|  | @ -526,11 +514,7 @@ static inline void *libbpf_ptr(void *ret) | |||
| 	if (IS_ERR(ret)) | ||||
| 		errno = -PTR_ERR(ret); | ||||
| 
 | ||||
| 	if (libbpf_mode & LIBBPF_STRICT_CLEAN_PTRS) | ||||
| 	return IS_ERR(ret) ? NULL : ret; | ||||
| 
 | ||||
| 	/* legacy: pass-through original pointer */ | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static inline bool str_is_empty(const char *s) | ||||
|  |  | |||
|  | @ -20,6 +20,11 @@ | |||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /* As of libbpf 1.0 libbpf_set_strict_mode() and enum libbpf_struct_mode have
 | ||||
|  * no effect. But they are left in libbpf_legacy.h so that applications that | ||||
|  * prepared for libbpf 1.0 before final release by using | ||||
|  * libbpf_set_strict_mode() still work with libbpf 1.0+ without any changes. | ||||
|  */ | ||||
| enum libbpf_strict_mode { | ||||
| 	/* Turn on all supported strict features of libbpf to simulate libbpf
 | ||||
| 	 * v1.0 behavior. | ||||
|  | @ -88,6 +93,25 @@ enum libbpf_strict_mode { | |||
| 
 | ||||
| LIBBPF_API int libbpf_set_strict_mode(enum libbpf_strict_mode mode); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief **libbpf_get_error()** extracts the error code from the passed | ||||
|  * pointer | ||||
|  * @param ptr pointer returned from libbpf API function | ||||
|  * @return error code; or 0 if no error occured | ||||
|  * | ||||
|  * Note, as of libbpf 1.0 this function is not necessary and not recommended | ||||
|  * to be used. Libbpf doesn't return error code embedded into the pointer | ||||
|  * itself. Instead, NULL is returned on error and error code is passed through | ||||
|  * thread-local errno variable. **libbpf_get_error()** is just returning -errno | ||||
|  * value if it receives NULL, which is correct only if errno hasn't been | ||||
|  * modified between libbpf API call and corresponding **libbpf_get_error()** | ||||
|  * call. Prefer to check return for NULL and use errno directly. | ||||
|  * | ||||
|  * This API is left in libbpf 1.0 to allow applications that were 1.0-ready | ||||
|  * before final libbpf 1.0 without needing to change them. | ||||
|  */ | ||||
| LIBBPF_API long libbpf_get_error(const void *ptr); | ||||
| 
 | ||||
| #define DECLARE_LIBBPF_OPTS LIBBPF_OPTS | ||||
| 
 | ||||
| /* "Discouraged" APIs which don't follow consistent libbpf naming patterns.
 | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Andrii Nakryiko
						Andrii Nakryiko