lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <aH_cGvgC20iD8qs9@gmail.com>
Date: Tue, 22 Jul 2025 20:44:42 +0200
From: Mahe Tardy <mahe.tardy@...il.com>
To: netdev@...r.kernel.org
Cc: Yonghong Song <yhs@...com>, Alexei Starovoitov <ast@...nel.org>,
	Daniel Borkmann <daniel@...earbox.net>
Subject: bpf: LLVM BTF inner map struct type def missing

Hello,

While writing a BPF prog using map of maps I bumped into this compiler
bug that GitHub user thediveo and Isovalent colleague Timo Beckers
already discussed in the cilium/ebpf discussions [^1].

The issue is that a struct only used in a inner map is not included in
the program BTF, so it needs a dummy declaration elsewhere to work.

For example such program:

	#include "vmlinux.h"
	#include <bpf/bpf_helpers.h>

	struct missing_type {
		uint64_t foo;
	};

	// struct missing_type bar; // commented on purpose

	struct {
		__uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);
		__type(key, uint32_t);
		__type(value, uint32_t);
		__uint(max_entries, 16);
		__array(
			values, struct {
				__uint(type, BPF_MAP_TYPE_HASH);
				__type(key, uint64_t);
				__type(value, struct missing_type);
				__uint(max_entries, 32);
			});
	} outer_map SEC(".maps");

Then do:

	bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
	clang -target bpf -g -O2 -c prog.c -o prog.o
	bpftool btf dump file prog.o

Will result in:

	[1] PTR '(anon)' type_id=3
	[2] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED
	[3] ARRAY '(anon)' type_id=2 index_type_id=4 nr_elems=12
	[4] INT '__ARRAY_SIZE_TYPE__' size=4 bits_offset=0 nr_bits=32 encoding=(none)
	[5] PTR '(anon)' type_id=6
	[6] TYPEDEF 'uint32_t' type_id=7
	[7] TYPEDEF 'u32' type_id=8
	[8] TYPEDEF '__u32' type_id=9
	[9] INT 'unsigned int' size=4 bits_offset=0 nr_bits=32 encoding=(none)
	[10] PTR '(anon)' type_id=11
	[11] ARRAY '(anon)' type_id=2 index_type_id=4 nr_elems=16
	[12] PTR '(anon)' type_id=13
	[13] STRUCT '(anon)' size=32 vlen=4
		'type' type_id=14 bits_offset=0
		'key' type_id=16 bits_offset=64
		'value' type_id=21 bits_offset=128
		'max_entries' type_id=22 bits_offset=192
	[14] PTR '(anon)' type_id=15
	[15] ARRAY '(anon)' type_id=2 index_type_id=4 nr_elems=1
	[16] PTR '(anon)' type_id=17
	[17] TYPEDEF 'uint64_t' type_id=18
	[18] TYPEDEF 'u64' type_id=19
	[19] TYPEDEF '__u64' type_id=20
	[20] INT 'unsigned long long' size=8 bits_offset=0 nr_bits=64 encoding=(none)
	[21] PTR '(anon)' type_id=28
	[22] PTR '(anon)' type_id=23
	[23] ARRAY '(anon)' type_id=2 index_type_id=4 nr_elems=32
	[24] ARRAY '(anon)' type_id=12 index_type_id=4 nr_elems=0
	[25] STRUCT '(anon)' size=32 vlen=5
		'type' type_id=1 bits_offset=0
		'key' type_id=5 bits_offset=64
		'value' type_id=5 bits_offset=128
		'max_entries' type_id=10 bits_offset=192
		'values' type_id=24 bits_offset=256
	[26] VAR 'outer_map' type_id=25, linkage=global
	[27] DATASEC '.maps' size=0 vlen=1
		type_id=26 offset=0 size=32 (VAR 'outer_map')
	[28] FWD 'missing_type' fwd_kind=struct

You can see that the outer map is [25], with values [24] with type to
[12] thus [13] and then the value of [13] is [21] which points to type
[28]. And [28] is a forward declaration. Thus if we try to load this
program (there's no program but the libbpf error msg is explicit):

	bpftool prog load prog.o /sys/fs/bpf/prog

Output is

	libbpf: map 'outer_map.inner': can't determine value size for type [28]: -22.

Now if you uncomment the commented line in the example (or use this type
in a function as suggested by Timo), the BTF looks like this:

	[1] PTR '(anon)' type_id=3
	[2] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED
	[3] ARRAY '(anon)' type_id=2 index_type_id=4 nr_elems=12
	[4] INT '__ARRAY_SIZE_TYPE__' size=4 bits_offset=0 nr_bits=32 encoding=(none)
	[5] PTR '(anon)' type_id=6
	[6] TYPEDEF 'uint32_t' type_id=7
	[7] TYPEDEF 'u32' type_id=8
	[8] TYPEDEF '__u32' type_id=9
	[9] INT 'unsigned int' size=4 bits_offset=0 nr_bits=32 encoding=(none)
	[10] PTR '(anon)' type_id=11
	[11] ARRAY '(anon)' type_id=2 index_type_id=4 nr_elems=16
	[12] PTR '(anon)' type_id=13
	[13] STRUCT '(anon)' size=32 vlen=4
		'type' type_id=14 bits_offset=0
		'key' type_id=16 bits_offset=64
		'value' type_id=21 bits_offset=128
		'max_entries' type_id=22 bits_offset=192
	[14] PTR '(anon)' type_id=15
	[15] ARRAY '(anon)' type_id=2 index_type_id=4 nr_elems=1
	[16] PTR '(anon)' type_id=17
	[17] TYPEDEF 'uint64_t' type_id=18
	[18] TYPEDEF 'u64' type_id=19
	[19] TYPEDEF '__u64' type_id=20
	[20] INT 'unsigned long long' size=8 bits_offset=0 nr_bits=64 encoding=(none)
	[21] PTR '(anon)' type_id=27
	[22] PTR '(anon)' type_id=23
	[23] ARRAY '(anon)' type_id=2 index_type_id=4 nr_elems=32
	[24] ARRAY '(anon)' type_id=12 index_type_id=4 nr_elems=0
	[25] STRUCT '(anon)' size=32 vlen=5
		'type' type_id=1 bits_offset=0
		'key' type_id=5 bits_offset=64
		'value' type_id=5 bits_offset=128
		'max_entries' type_id=10 bits_offset=192
		'values' type_id=24 bits_offset=256
	[26] VAR 'outer_map' type_id=25, linkage=global
	[27] STRUCT 'missing_type' size=8 vlen=1
		'foo' type_id=17 bits_offset=0
	[28] VAR 'bar' type_id=27, linkage=global
	[29] DATASEC '.bss' size=0 vlen=1
		type_id=28 offset=0 size=8 (VAR 'bar')
	[30] DATASEC '.maps' size=0 vlen=1
		type_id=26 offset=0 size=32 (VAR 'outer_map')

And then the type [27] exists, loading can now proceed.

I tested it with latest LLVM-project head when writing this e789f8bdf369
("[libc][math] Add Generic Comparison Operations for floating point
types (#144983)").

If you think it's reasonable to fix, I would be interested looking into
this.

[^1]: https://github.com/cilium/ebpf/discussions/1658#discussioncomment-12491339

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ