diff mbox series

[bpf-next,v4] bpftool: bpf skeletons assert type sizes

Message ID f562455d7b3cf338e59a7976f4690ec5a0057f7f.camel@fb.com (mailing list archive)
State Accepted
Commit 08d4dba6ae77aaec0e0c79dcfcb0613cb7426b2c
Delegated to: BPF
Headers show
Series [bpf-next,v4] bpftool: bpf skeletons assert type sizes | expand

Checks

Context Check Description
netdev/tree_selection success Clearly marked for bpf-next
netdev/fixes_present success Fixes tag not required for -next series
netdev/subject_prefix success Link
netdev/cover_letter success Single patches do not need cover letters
netdev/patch_count success Link
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 0 this patch: 0
netdev/cc_maintainers warning 7 maintainers not CCed: kpsingh@kernel.org mauricio@kinvolk.io john.fastabend@gmail.com kafai@fb.com songliubraving@fb.com yhs@fb.com netdev@vger.kernel.org
netdev/build_clang success Errors and warnings before: 0 this patch: 0
netdev/module_param success Was 0 now: 0
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 0 this patch: 0
netdev/checkpatch warning CHECK: Please don't use multiple blank lines WARNING: Avoid line continuations in quoted strings WARNING: Avoid unnecessary line continuations WARNING: __always_unused or __maybe_unused is preferred over __attribute__((__unused__)) WARNING: line length of 117 exceeds 80 columns WARNING: line length of 82 exceeds 80 columns WARNING: line length of 83 exceeds 80 columns WARNING: line length of 87 exceeds 80 columns WARNING: line length of 89 exceeds 80 columns
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
bpf/vmtest-bpf-next success VM_Test
bpf/vmtest-bpf-next-PR success PR summary

Commit Message

Delyan Kratunov Feb. 23, 2022, 10:01 p.m. UTC
When emitting type declarations in skeletons, bpftool will now also emit
static assertions on the size of the data/bss/rodata/etc fields. This
ensures that in situations where userspace and kernel types have the same
name but differ in size we do not silently produce incorrect results but
instead break the build.

This was reported in [1] and as expected the repro in [2] fails to build
on the new size assert after this change.

  [1]: Closes: https://github.com/libbpf/libbpf/issues/433
  [2]: https://github.com/fuweid/iovisor-bcc-pr-3777

Signed-off-by: Delyan Kratunov <delyank@fb.com>
Acked-by: Hengqi Chen <hengqi.chen@gmail.com>
Tested-by: Hengqi Chen <hengqi.chen@gmail.com>
---
v3 -> v4:
 - rebase
 - style fixes

v2 -> v3:
 - group all static asserts in one function at the end of the file
 - only use macros in C++ mode

v1 -> v2:
 - drop the stdint approach in favor of static asserts right after the structs

 tools/bpf/bpftool/gen.c | 133 +++++++++++++++++++++++++++++++++-------
 1 file changed, 111 insertions(+), 22 deletions(-)

--
2.34.1

Comments

Andrii Nakryiko Feb. 24, 2022, 1:34 a.m. UTC | #1
On Wed, Feb 23, 2022 at 2:02 PM Delyan Kratunov <delyank@fb.com> wrote:
>
> When emitting type declarations in skeletons, bpftool will now also emit
> static assertions on the size of the data/bss/rodata/etc fields. This
> ensures that in situations where userspace and kernel types have the same
> name but differ in size we do not silently produce incorrect results but
> instead break the build.
>
> This was reported in [1] and as expected the repro in [2] fails to build
> on the new size assert after this change.
>
>   [1]: Closes: https://github.com/libbpf/libbpf/issues/433
>   [2]: https://github.com/fuweid/iovisor-bcc-pr-3777
>
> Signed-off-by: Delyan Kratunov <delyank@fb.com>
> Acked-by: Hengqi Chen <hengqi.chen@gmail.com>
> Tested-by: Hengqi Chen <hengqi.chen@gmail.com>
> ---
> v3 -> v4:
>  - rebase
>  - style fixes
>

I added a few tweaks (see below) and applied to bpf-next. Thanks!

> v2 -> v3:
>  - group all static asserts in one function at the end of the file
>  - only use macros in C++ mode
>
> v1 -> v2:
>  - drop the stdint approach in favor of static asserts right after the structs
>
>  tools/bpf/bpftool/gen.c | 133 +++++++++++++++++++++++++++++++++-------
>  1 file changed, 111 insertions(+), 22 deletions(-)
>
> diff --git a/tools/bpf/bpftool/gen.c b/tools/bpf/bpftool/gen.c
> index f8c1413523a3..a42545bcb12d 100644
> --- a/tools/bpf/bpftool/gen.c
> +++ b/tools/bpf/bpftool/gen.c
> @@ -209,15 +209,38 @@ static int codegen_datasec_def(struct bpf_object *obj,
>         return 0;
>  }
>
> +static const struct btf_type *find_type_for_map(struct bpf_object *obj,
> +                                               const char *map_ident)

this doesn't need entire bpf_object, it's enough to pass struct btf
directly, I changed it to take just btf

> +{
> +       struct btf *btf = bpf_object__btf(obj);
> +       int n = btf__type_cnt(btf), i;
> +       char sec_ident[256];
> +

[...]

> +/* Emit type size asserts for all top-level fields in memory-mapped internal maps.
> + */
> +static void codegen_asserts(struct bpf_object *obj, const char *obj_name)
> +{
> +       struct btf *btf = bpf_object__btf(obj);
> +       struct bpf_map *map;
> +       struct btf_var_secinfo *sec_var;
> +       int i, vlen;
> +       const struct btf_type *sec;
> +       char map_ident[256], var_ident[256];
> +
> +       codegen("\
> +               \n\
> +                                                                           \n\
> +               #ifdef __cplusplus                                          \n\
> +               #define _Static_assert static_assert                        \n\
> +               #endif                                                      \n\

I moved this _Static_assert business inside the <skel>__type_asserts()
function. I also thought that if in the future we want to add some
other asserts, then having a more generic "<skel>__assert()" name
would be more appropriate, so I renamed it to just "<skel>__assert".

> +                                                                           \n\
> +               __attribute__((unused)) static void                         \n\
> +               %1$s__type_asserts(struct %1$s *s)                          \n\
> +               {                                                           \n\
> +               ", obj_name);
> +

[...]

> +                       var_ident[0] = '\0';
> +                       strncat(var_ident, var_name, sizeof(var_ident) - 1);
> +                       sanitize_identifier(var_ident);
> +
> +                       printf("\t_Static_assert(sizeof(s->%1$s->%2$s) == %3$lld, \"unexpected size of '%2$s'\");\n",
> +                              map_ident, var_ident, var_size);

__s64 isn't %lld on each supported architecture. I just used long and
%ld instead of __s64 to avoid compilation warnings.

> +               }
> +       }
> +       codegen("\
> +               \n\
> +               }                                                           \n\
> +                                                                           \n\
> +               #ifdef __cplusplus                                          \n\
> +               #undef _Static_assert                                       \n\
> +               #endif                                                      \n\
> +               ");
> +}
> +
> +

[...]
patchwork-bot+netdevbpf@kernel.org Feb. 24, 2022, 1:40 a.m. UTC | #2
Hello:

This patch was applied to bpf/bpf-next.git (master)
by Andrii Nakryiko <andrii@kernel.org>:

On Wed, 23 Feb 2022 22:01:58 +0000 you wrote:
> When emitting type declarations in skeletons, bpftool will now also emit
> static assertions on the size of the data/bss/rodata/etc fields. This
> ensures that in situations where userspace and kernel types have the same
> name but differ in size we do not silently produce incorrect results but
> instead break the build.
> 
> This was reported in [1] and as expected the repro in [2] fails to build
> on the new size assert after this change.
> 
> [...]

Here is the summary with links:
  - [bpf-next,v4] bpftool: bpf skeletons assert type sizes
    https://git.kernel.org/bpf/bpf-next/c/08d4dba6ae77

You are awesome, thank you!
diff mbox series

Patch

diff --git a/tools/bpf/bpftool/gen.c b/tools/bpf/bpftool/gen.c
index f8c1413523a3..a42545bcb12d 100644
--- a/tools/bpf/bpftool/gen.c
+++ b/tools/bpf/bpftool/gen.c
@@ -209,15 +209,38 @@  static int codegen_datasec_def(struct bpf_object *obj,
 	return 0;
 }

+static const struct btf_type *find_type_for_map(struct bpf_object *obj,
+						const char *map_ident)
+{
+	struct btf *btf = bpf_object__btf(obj);
+	int n = btf__type_cnt(btf), i;
+	char sec_ident[256];
+
+	for (i = 1; i < n; i++) {
+		const struct btf_type *t = btf__type_by_id(btf, i);
+		const char *name;
+
+		if (!btf_is_datasec(t))
+			continue;
+
+		name = btf__str_by_offset(btf, t->name_off);
+		if (!get_datasec_ident(name, sec_ident, sizeof(sec_ident)))
+			continue;
+
+		if (strcmp(sec_ident, map_ident) == 0)
+			return t;
+	}
+	return NULL;
+}
+
 static int codegen_datasecs(struct bpf_object *obj, const char *obj_name)
 {
 	struct btf *btf = bpf_object__btf(obj);
-	int n = btf__type_cnt(btf);
 	struct btf_dump *d;
 	struct bpf_map *map;
 	const struct btf_type *sec;
-	char sec_ident[256], map_ident[256];
-	int i, err = 0;
+	char map_ident[256];
+	int err = 0;

 	d = btf_dump__new(btf, codegen_btf_dump_printf, NULL, NULL);
 	err = libbpf_get_error(d);
@@ -234,23 +257,7 @@  static int codegen_datasecs(struct bpf_object *obj, const char *obj_name)
 		if (!get_map_ident(map, map_ident, sizeof(map_ident)))
 			continue;

-		sec = NULL;
-		for (i = 1; i < n; i++) {
-			const struct btf_type *t = btf__type_by_id(btf, i);
-			const char *name;
-
-			if (!btf_is_datasec(t))
-				continue;
-
-			name = btf__str_by_offset(btf, t->name_off);
-			if (!get_datasec_ident(name, sec_ident, sizeof(sec_ident)))
-				continue;
-
-			if (strcmp(sec_ident, map_ident) == 0) {
-				sec = t;
-				break;
-			}
-		}
+		sec = find_type_for_map(obj, map_ident);

 		/* In some cases (e.g., sections like .rodata.cst16 containing
 		 * compiler allocated string constants only) there will be
@@ -363,6 +370,78 @@  static size_t bpf_map_mmap_sz(const struct bpf_map *map)
 	return map_sz;
 }

+/* Emit type size asserts for all top-level fields in memory-mapped internal maps.
+ */
+static void codegen_asserts(struct bpf_object *obj, const char *obj_name)
+{
+	struct btf *btf = bpf_object__btf(obj);
+	struct bpf_map *map;
+	struct btf_var_secinfo *sec_var;
+	int i, vlen;
+	const struct btf_type *sec;
+	char map_ident[256], var_ident[256];
+
+	codegen("\
+		\n\
+									    \n\
+		#ifdef __cplusplus					    \n\
+		#define _Static_assert static_assert			    \n\
+		#endif							    \n\
+									    \n\
+		__attribute__((unused)) static void			    \n\
+		%1$s__type_asserts(struct %1$s *s)			    \n\
+		{							    \n\
+		", obj_name);
+
+	bpf_object__for_each_map(map, obj) {
+		if (!bpf_map__is_internal(map))
+			continue;
+		if (!(bpf_map__map_flags(map) & BPF_F_MMAPABLE))
+			continue;
+		if (!get_map_ident(map, map_ident, sizeof(map_ident)))
+			continue;
+
+		sec = find_type_for_map(obj, map_ident);
+		if (!sec) {
+			/* best effort, couldn't find the type for this map */
+			continue;
+		}
+
+		sec_var = btf_var_secinfos(sec);
+		vlen =  btf_vlen(sec);
+
+		for (i = 0; i < vlen; i++, sec_var++) {
+			const struct btf_type *var = btf__type_by_id(btf, sec_var->type);
+			const char *var_name = btf__name_by_offset(btf, var->name_off);
+			__u32 var_type_id = var->type;
+			__s64 var_size = btf__resolve_size(btf, var_type_id);
+
+			if (var_size < 0)
+				continue;
+
+			/* static variables are not exposed through BPF skeleton */
+			if (btf_var(var)->linkage == BTF_VAR_STATIC)
+				continue;
+
+			var_ident[0] = '\0';
+			strncat(var_ident, var_name, sizeof(var_ident) - 1);
+			sanitize_identifier(var_ident);
+
+			printf("\t_Static_assert(sizeof(s->%1$s->%2$s) == %3$lld, \"unexpected size of '%2$s'\");\n",
+			       map_ident, var_ident, var_size);
+		}
+	}
+	codegen("\
+		\n\
+		}							    \n\
+									    \n\
+		#ifdef __cplusplus					    \n\
+		#undef _Static_assert					    \n\
+		#endif							    \n\
+		");
+}
+
+
 static void codegen_attach_detach(struct bpf_object *obj, const char *obj_name)
 {
 	struct bpf_program *prog;
@@ -641,6 +720,8 @@  static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h
 		}							    \n\
 		", obj_name);

+	codegen_asserts(obj, obj_name);
+
 	codegen("\
 		\n\
 									    \n\
@@ -1046,9 +1127,17 @@  static int do_skeleton(int argc, char **argv)
 		const void *%1$s::elf_bytes(size_t *sz) { return %1$s__elf_bytes(sz); } \n\
 		#endif /* __cplusplus */				    \n\
 									    \n\
-		#endif /* %2$s */					    \n\
 		",
-		obj_name, header_guard);
+		obj_name);
+
+	codegen_asserts(obj, obj_name);
+
+	codegen("\
+		\n\
+									    \n\
+		#endif /* %1$s */					    \n\
+		",
+		header_guard);
 	err = 0;
 out:
 	bpf_object__close(obj);