diff mbox series

[v1,bpf-next,3/4] btf: Descend into structs and arrays during special field search

Message ID 20231023220030.2556229-4-davemarchevsky@fb.com (mailing list archive)
State Changes Requested
Delegated to: BPF
Headers show
Series Descend into struct, array types when searching for fields | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for bpf-next, async
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 1366 this patch: 1366
netdev/cc_maintainers warning 8 maintainers not CCed: song@kernel.org yonghong.song@linux.dev jolsa@kernel.org kpsingh@kernel.org john.fastabend@gmail.com sdf@google.com haoluo@google.com martin.lau@linux.dev
netdev/build_clang fail Errors and warnings before: 1386 this patch: 1389
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 1391 this patch: 1391
netdev/checkpatch warning WARNING: line length of 81 exceeds 80 columns WARNING: line length of 83 exceeds 80 columns WARNING: line length of 84 exceeds 80 columns WARNING: line length of 86 exceeds 80 columns WARNING: line length of 93 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-PR success PR summary
bpf/vmtest-bpf-next-VM_Test-32 success Logs for x86_64-llvm-16 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-33 success Logs for x86_64-llvm-16 / veristat
bpf/vmtest-bpf-next-VM_Test-30 success Logs for x86_64-llvm-16 / test (test_progs_no_alu32_parallel, true, 30) / test_progs_no_alu32_parallel on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-31 success Logs for x86_64-llvm-16 / test (test_progs_parallel, true, 30) / test_progs_parallel on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-3 success Logs for aarch64-gcc / build / build for aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-8 success Logs for aarch64-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-0 success Logs for Lint
bpf/vmtest-bpf-next-VM_Test-1 success Logs for ShellCheck
bpf/vmtest-bpf-next-VM_Test-2 success Logs for Validate matrix.py
bpf/vmtest-bpf-next-VM_Test-4 success Logs for aarch64-gcc / test (test_maps, false, 360) / test_maps on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-7 success Logs for aarch64-gcc / test (test_verifier, false, 360) / test_verifier on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-5 success Logs for aarch64-gcc / test (test_progs, false, 360) / test_progs on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-6 success Logs for aarch64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-24 success Logs for x86_64-llvm-16 / build / build for x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-25 success Logs for x86_64-llvm-16 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-9 success Logs for s390x-gcc / build / build for s390x with gcc
bpf/vmtest-bpf-next-VM_Test-15 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-19 success Logs for x86_64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-23 success Logs for x86_64-gcc / veristat / veristat on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-26 success Logs for x86_64-llvm-16 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-16 success Logs for x86_64-gcc / build / build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-14 success Logs for s390x-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-20 success Logs for x86_64-gcc / test (test_progs_no_alu32_parallel, true, 30) / test_progs_no_alu32_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-17 success Logs for x86_64-gcc / test (test_maps, false, 360) / test_maps on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-21 success Logs for x86_64-gcc / test (test_progs_parallel, true, 30) / test_progs_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-18 success Logs for x86_64-gcc / test (test_progs, false, 360) / test_progs on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-22 success Logs for x86_64-gcc / test (test_verifier, false, 360) / test_verifier on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-27 success Logs for x86_64-llvm-16 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-28 success Logs for x86_64-llvm-16 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-29 success Logs for x86_64-llvm-16 / veristat
bpf/vmtest-bpf-next-VM_Test-13 success Logs for s390x-gcc / test (test_verifier, false, 360) / test_verifier on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-12 success Logs for s390x-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-11 success Logs for s390x-gcc / test (test_progs, false, 360) / test_progs on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-10 success Logs for s390x-gcc / test (test_maps, false, 360) / test_maps on s390x with gcc

Commit Message

Dave Marchevsky Oct. 23, 2023, 10 p.m. UTC
Structs and arrays are aggregate types which contain some inner
type(s) - members and elements - at various offsets. Currently, when
examining a struct or datasec for special fields, the verifier does
not look into the inner type of the structs or arrays it contains.
This patch adds logic to descend into struct and array types when
searching for special fields.

If we have struct x containing an array:

struct x {
  int a;
  u64 b[10];
};

we can construct some struct y with no array or struct members that
has the same types at the same offsets:

struct y {
  int a;
  u64 b1;
  u64 b2;
  /* ... */
  u64 b10;
};

Similarly for a struct containing a struct:

struct x {
  char a;
  struct {
    int b;
    u64 c;
  } inner;
};

there's a struct y with no aggregate members and same types/offsets:

struct y {
  char a;
  int inner_b __attribute__ ((aligned (8))); /* See [0] */
  u64 inner_c __attribute__ ((aligned (8)));
};

This patch takes advantage of this equivalence to 'flatten' the
field info found while descending into struct or array members into
the btf_field_info result array of the original type being examined.
The resultant btf_record of the original type being searched will
have the correct fields at the correct offsets, but without any
differentiation between "this field is one of my members" and "this
field is actually in my some struct / array member".

For now this descendant search logic looks for kptr fields only.

Implementation notes:
  * Search starts at btf_find_field - we're either looking at a struct
    that's the type of some mapval (btf_find_struct_field), or a
    datasec representing a .bss or .data map (btf_find_datasec_var).
    Newly-added btf_find_aggregate_field is a "disambiguation helper"
    like btf_find_field, but is meant to be called from one of the
    starting points of the search - btf_find_{struct_field,
    datasec_var}.
    * btf_find_aggregate_field may itself call btf_find_struct_field,
      so there's some recursive digging possible here

  * Newly-added btf_flatten_array_field handles array fields by
    finding the type of their element and continuing the dig based on
    elem type.

  [0]:  Structs have the alignment of their largest field, so the
        explicit alignment is necessary here. Luckily this patch's
        changes don't need to care about alignment / padding, since
	the BTF created during compilation is being searched, and
	it already has the correct information.

Signed-off-by: Dave Marchevsky <davemarchevsky@fb.com>
---
 kernel/bpf/btf.c | 151 ++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 142 insertions(+), 9 deletions(-)

Comments

kernel test robot Oct. 26, 2023, 1:24 a.m. UTC | #1
Hi Dave,

kernel test robot noticed the following build warnings:

[auto build test WARNING on bpf-next/master]

url:    https://github.com/intel-lab-lkp/linux/commits/Dave-Marchevsky/bpf-Fix-btf_get_field_type-to-fail-for-multiple-bpf_refcount-fields/20231024-060227
base:   https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git master
patch link:    https://lore.kernel.org/r/20231023220030.2556229-4-davemarchevsky%40fb.com
patch subject: [PATCH v1 bpf-next 3/4] btf: Descend into structs and arrays during special field search
config: x86_64-rhel-8.3-rust (https://download.01.org/0day-ci/archive/20231026/202310260952.C9Gb9Avi-lkp@intel.com/config)
compiler: clang version 16.0.4 (https://github.com/llvm/llvm-project.git ae42196bc493ffe877a7e3dff8be32035dea4d07)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231026/202310260952.C9Gb9Avi-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202310260952.C9Gb9Avi-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> kernel/bpf/btf.c:3634:70: warning: variable 'off' is uninitialized when used here [-Wuninitialized]
           ret = btf_find_struct_field(btf, elem_type, srch, array_field_off + off, rec);
                                                                               ^~~
   kernel/bpf/btf.c:3625:15: note: initialize the variable 'off' to silence this warning
           u32 i, j, off, nelems;
                        ^
                         = 0
   1 warning generated.


vim +/off +3634 kernel/bpf/btf.c

  3616	
  3617	static int btf_flatten_array_field(const struct btf *btf,
  3618					   const struct btf_type *t,
  3619					   struct btf_field_info_search *srch,
  3620					   int array_field_off, int rec)
  3621	{
  3622		int ret, start_idx, elem_field_cnt;
  3623		const struct btf_type *elem_type;
  3624		struct btf_field_info *info;
  3625		u32 i, j, off, nelems;
  3626	
  3627		if (!btf_type_is_array(t))
  3628			return -EINVAL;
  3629		nelems = __multi_dim_elem_type_nelems(btf, t, &elem_type);
  3630		if (!nelems || !__btf_type_is_struct(elem_type))
  3631			return srch->idx;
  3632	
  3633		start_idx = srch->idx;
> 3634		ret = btf_find_struct_field(btf, elem_type, srch, array_field_off + off, rec);
  3635		if (ret < 0)
  3636			return ret;
  3637	
  3638		/* No btf_field_info's added */
  3639		if (srch->idx == start_idx)
  3640			return srch->idx;
  3641	
  3642		elem_field_cnt = srch->idx - start_idx;
  3643		info = __next_field_infos(srch, elem_field_cnt * (nelems - 1));
  3644		if (IS_ERR_OR_NULL(info))
  3645			return PTR_ERR(info);
  3646	
  3647		/* Array elems after the first can copy first elem's btf_field_infos
  3648		 * and adjust offset
  3649		 */
  3650		for (i = 1; i < nelems; i++) {
  3651			memcpy(info, &srch->infos[start_idx],
  3652			       elem_field_cnt * sizeof(struct btf_field_info));
  3653			for (j = 0; j < elem_field_cnt; j++) {
  3654				info->off += (i * elem_type->size);
  3655				info++;
  3656			}
  3657		}
  3658		return srch->idx;
  3659	}
  3660
Jiri Olsa Oct. 30, 2023, 12:56 p.m. UTC | #2
On Mon, Oct 23, 2023 at 03:00:29PM -0700, Dave Marchevsky wrote:

SNIP

> -			ret = btf_maybe_find_kptr(btf, member_type, off, srch);
> +			ret = btf_find_aggregate_field(btf, member_type, srch,
> +						       struct_field_off + off,
> +						       rec);
>  			if (ret < 0)
>  				return ret;
>  			continue;
> @@ -3541,15 +3587,17 @@ static int btf_find_struct_field(const struct btf *btf,
>  		case BPF_LIST_NODE:
>  		case BPF_RB_NODE:
>  		case BPF_REFCOUNT:
> -			ret = btf_find_struct(btf, member_type, off, sz, field_type,
> -					      srch);
> +			ret = btf_find_struct(btf, member_type,
> +					      struct_field_off + off,
> +					      sz, field_type, srch);
>  			if (ret < 0)
>  				return ret;
>  			break;
>  		case BPF_LIST_HEAD:
>  		case BPF_RB_ROOT:
>  			ret = btf_find_graph_root(btf, t, member_type,
> -						  i, off, sz, srch, field_type);
> +						  i, struct_field_off + off, sz,
> +						  srch, field_type);
>  			if (ret < 0)
>  				return ret;
>  			break;
> @@ -3566,6 +3614,82 @@ static int btf_find_struct_field(const struct btf *btf,
>  	return srch->idx;
>  }
>  
> +static int btf_flatten_array_field(const struct btf *btf,
> +				   const struct btf_type *t,
> +				   struct btf_field_info_search *srch,
> +				   int array_field_off, int rec)
> +{
> +	int ret, start_idx, elem_field_cnt;
> +	const struct btf_type *elem_type;
> +	struct btf_field_info *info;
> +	u32 i, j, off, nelems;
> +
> +	if (!btf_type_is_array(t))
> +		return -EINVAL;

seems this check is not needed, it's called only for
btf_type_is_array(t)

> +	nelems = __multi_dim_elem_type_nelems(btf, t, &elem_type);
> +	if (!nelems || !__btf_type_is_struct(elem_type))
> +		return srch->idx;
> +
> +	start_idx = srch->idx;
> +	ret = btf_find_struct_field(btf, elem_type, srch, array_field_off + off, rec);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* No btf_field_info's added */
> +	if (srch->idx == start_idx)
> +		return srch->idx;
> +
> +	elem_field_cnt = srch->idx - start_idx;
> +	info = __next_field_infos(srch, elem_field_cnt * (nelems - 1));
> +	if (IS_ERR_OR_NULL(info))
> +		return PTR_ERR(info);
> +
> +	/* Array elems after the first can copy first elem's btf_field_infos
> +	 * and adjust offset
> +	 */
> +	for (i = 1; i < nelems; i++) {
> +		memcpy(info, &srch->infos[start_idx],
> +		       elem_field_cnt * sizeof(struct btf_field_info));
> +		for (j = 0; j < elem_field_cnt; j++) {
> +			info->off += (i * elem_type->size);
> +			info++;
> +		}
> +	}
> +	return srch->idx;
> +}
> +
> +static int btf_find_aggregate_field(const struct btf *btf,
> +				    const struct btf_type *t,
> +				    struct btf_field_info_search *srch,
> +				    int field_off, int rec)
> +{
> +	u32 orig_field_mask;
> +	int ret;
> +
> +	/* Dig up to 4 levels deep */
> +	if (rec >= 4)
> +		return -E2BIG;

do we need to fails in here? should we just stop descend?
and continue the search in upper layers

> +
> +	orig_field_mask = srch->field_mask;
> +	srch->field_mask &= BPF_KPTR;
> +
> +	if (!srch->field_mask) {
> +		ret = 0;
> +		goto reset_field_mask;
> +	}

could this be just

	if (!(srch->field_mask & BPF_KPTR))
		return 0;

but I don't understand why there's the BPF_KPTR restriction in here


jirka

> +
> +	if (__btf_type_is_struct(t))
> +		ret = btf_find_struct_field(btf, t, srch, field_off, rec + 1);
> +	else if (btf_type_is_array(t))
> +		ret = btf_flatten_array_field(btf, t, srch, field_off, rec + 1);
> +	else
> +		ret = -EINVAL;
> +
> +reset_field_mask:
> +	srch->field_mask = orig_field_mask;
> +	return ret;
> +}
> +
>  static int __datasec_vsi_check_align_sz(const struct btf_var_secinfo *vsi,
>  					enum btf_field_type field_type,
>  					u32 expected_sz)
> @@ -3605,10 +3729,19 @@ static int btf_find_datasec_var(const struct btf *btf, const struct btf_type *t,
>  			 * btf_maybe_find_kptr will find actual kptr type
>  			 */
>  			sz = btf_field_type_size(BPF_KPTR_REF);
> -			if (__datasec_vsi_check_align_sz(vsi, BPF_KPTR_REF, sz))
> +			if (srch->field_mask & BPF_KPTR &&
> +			    !__datasec_vsi_check_align_sz(vsi, BPF_KPTR_REF, sz)) {
> +				ret = btf_maybe_find_kptr(btf, var_type, off, srch);
> +				if (ret < 0)
> +					return ret;
> +				if (ret == BTF_FIELD_FOUND)
> +					continue;
> +			}
> +
> +			if (!(btf_type_is_array(var_type) || __btf_type_is_struct(var_type)))
>  				continue;
>  
> -			ret = btf_maybe_find_kptr(btf, var_type, off, srch);
> +			ret = btf_find_aggregate_field(btf, var_type, srch, off, 0);
>  			if (ret < 0)
>  				return ret;
>  			continue;
> @@ -3655,7 +3788,7 @@ static int btf_find_field(const struct btf *btf, const struct btf_type *t,
>  			  struct btf_field_info_search *srch)
>  {
>  	if (__btf_type_is_struct(t))
> -		return btf_find_struct_field(btf, t, srch);
> +		return btf_find_struct_field(btf, t, srch, 0, 0);
>  	else if (btf_type_is_datasec(t))
>  		return btf_find_datasec_var(btf, t, srch);
>  	return -EINVAL;
> -- 
> 2.34.1
> 
>
Yonghong Song Oct. 30, 2023, 8:56 p.m. UTC | #3
On 10/23/23 3:00 PM, Dave Marchevsky wrote:
> Structs and arrays are aggregate types which contain some inner
> type(s) - members and elements - at various offsets. Currently, when
> examining a struct or datasec for special fields, the verifier does
> not look into the inner type of the structs or arrays it contains.
> This patch adds logic to descend into struct and array types when
> searching for special fields.
This indeed a great improvement. Thanks!
>
> If we have struct x containing an array:
>
> struct x {
>    int a;
>    u64 b[10];
> };
>
> we can construct some struct y with no array or struct members that
> has the same types at the same offsets:
>
> struct y {
>    int a;
>    u64 b1;
>    u64 b2;
>    /* ... */
>    u64 b10;
> };
>
> Similarly for a struct containing a struct:
>
> struct x {
>    char a;
>    struct {
>      int b;
>      u64 c;
>    } inner;
> };
>
> there's a struct y with no aggregate members and same types/offsets:
>
> struct y {
>    char a;
>    int inner_b __attribute__ ((aligned (8))); /* See [0] */
>    u64 inner_c __attribute__ ((aligned (8)));
> };
>
> This patch takes advantage of this equivalence to 'flatten' the
> field info found while descending into struct or array members into
> the btf_field_info result array of the original type being examined.
> The resultant btf_record of the original type being searched will
> have the correct fields at the correct offsets, but without any
> differentiation between "this field is one of my members" and "this
> field is actually in my some struct / array member".
>
> For now this descendant search logic looks for kptr fields only.
>
> Implementation notes:
>    * Search starts at btf_find_field - we're either looking at a struct
>      that's the type of some mapval (btf_find_struct_field), or a
>      datasec representing a .bss or .data map (btf_find_datasec_var).
>      Newly-added btf_find_aggregate_field is a "disambiguation helper"
>      like btf_find_field, but is meant to be called from one of the
>      starting points of the search - btf_find_{struct_field,
>      datasec_var}.
>      * btf_find_aggregate_field may itself call btf_find_struct_field,
>        so there's some recursive digging possible here
>
>    * Newly-added btf_flatten_array_field handles array fields by
>      finding the type of their element and continuing the dig based on
>      elem type.
>
>    [0]:  Structs have the alignment of their largest field, so the
>          explicit alignment is necessary here. Luckily this patch's
>          changes don't need to care about alignment / padding, since
> 	the BTF created during compilation is being searched, and
> 	it already has the correct information.
>
> Signed-off-by: Dave Marchevsky <davemarchevsky@fb.com>
> ---
>   kernel/bpf/btf.c | 151 ++++++++++++++++++++++++++++++++++++++++++++---
>   1 file changed, 142 insertions(+), 9 deletions(-)
>
> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index e999ba85c363..b982bf6fef9d 100644
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c
> @@ -3496,9 +3496,41 @@ static int __struct_member_check_align(u32 off, enum btf_field_type field_type)
>   	return 0;
>   }
>   
> +/* Return number of elems and elem_type of a btf_array
> + *
> + * If the array is multi-dimensional, return elem count of
> + * equivalent single-dimensional array
> + *   e.g. int x[10][10][10] has same layout as int x[1000]
> + */
> +static u32 __multi_dim_elem_type_nelems(const struct btf *btf,
> +					const struct btf_type *t,
> +					const struct btf_type **elem_type)
> +{
> +	u32 nelems = btf_array(t)->nelems;
> +
> +	if (!nelems)
> +		return 0;
> +
> +	*elem_type = btf_type_by_id(btf, btf_array(t)->type);
> +
> +	while (btf_type_is_array(*elem_type)) {
> +		if (!btf_array(*elem_type)->nelems)
> +			return 0;

btf_array(*elem_type)->nelems == 0 is a very rare case.
I guess we do not need it here. If it is indeed 0,
the final nelems will be 0 any way.

> +		nelems *= btf_array(*elem_type)->nelems;
> +		*elem_type = btf_type_by_id(btf, btf_array(*elem_type)->type);
> +	}
> +	return nelems;
> +}
> +
> +static int btf_find_aggregate_field(const struct btf *btf,
> +				    const struct btf_type *t,
> +				    struct btf_field_info_search *srch,
> +				    int field_off, int rec);
> +
>   static int btf_find_struct_field(const struct btf *btf,
>   				 const struct btf_type *t,
> -				 struct btf_field_info_search *srch)
> +				 struct btf_field_info_search *srch,
> +				 int struct_field_off, int rec)
>   {
>   	const struct btf_member *member;
>   	int ret, field_type;
> @@ -3522,10 +3554,24 @@ static int btf_find_struct_field(const struct btf *btf,
>   			 * checks, all ptrs have same align.
>   			 * btf_maybe_find_kptr will find actual kptr type
>   			 */
> -			if (__struct_member_check_align(off, BPF_KPTR_REF))
> +			if (srch->field_mask & BPF_KPTR &&
> +			    !__struct_member_check_align(off, BPF_KPTR_REF)) {
> +				ret = btf_maybe_find_kptr(btf, member_type,
> +							  struct_field_off + off,
> +							  srch);
> +				if (ret < 0)
> +					return ret;
> +				if (ret == BTF_FIELD_FOUND)
> +					continue;
> +			}
> +
> +			if (!(btf_type_is_array(member_type) ||
> +			      __btf_type_is_struct(member_type)))
>   				continue;
>   
> -			ret = btf_maybe_find_kptr(btf, member_type, off, srch);
> +			ret = btf_find_aggregate_field(btf, member_type, srch,
> +						       struct_field_off + off,
> +						       rec);
>   			if (ret < 0)
>   				return ret;
>   			continue;
> @@ -3541,15 +3587,17 @@ static int btf_find_struct_field(const struct btf *btf,
>   		case BPF_LIST_NODE:
>   		case BPF_RB_NODE:
>   		case BPF_REFCOUNT:
> -			ret = btf_find_struct(btf, member_type, off, sz, field_type,
> -					      srch);
> +			ret = btf_find_struct(btf, member_type,
> +					      struct_field_off + off,
> +					      sz, field_type, srch);
>   			if (ret < 0)
>   				return ret;
>   			break;
>   		case BPF_LIST_HEAD:
>   		case BPF_RB_ROOT:
>   			ret = btf_find_graph_root(btf, t, member_type,
> -						  i, off, sz, srch, field_type);
> +						  i, struct_field_off + off, sz,
> +						  srch, field_type);
>   			if (ret < 0)
>   				return ret;
>   			break;
> @@ -3566,6 +3614,82 @@ static int btf_find_struct_field(const struct btf *btf,
>   	return srch->idx;
>   }
>   
> +static int btf_flatten_array_field(const struct btf *btf,
> +				   const struct btf_type *t,
> +				   struct btf_field_info_search *srch,
> +				   int array_field_off, int rec)
> +{
> +	int ret, start_idx, elem_field_cnt;
> +	const struct btf_type *elem_type;
> +	struct btf_field_info *info;
> +	u32 i, j, off, nelems;
> +
> +	if (!btf_type_is_array(t))
> +		return -EINVAL;
> +	nelems = __multi_dim_elem_type_nelems(btf, t, &elem_type);
> +	if (!nelems || !__btf_type_is_struct(elem_type))
> +		return srch->idx;
> +
> +	start_idx = srch->idx;
> +	ret = btf_find_struct_field(btf, elem_type, srch, array_field_off + off, rec);

As kerneltest bot mentioned, 'off' is undefined. The array_field_off
should equal the first array element struct_elem_off, right?
So I think here 'off' can be replaced with 0?

> +	if (ret < 0)
> +		return ret;
> +
> +	/* No btf_field_info's added */
> +	if (srch->idx == start_idx)
> +		return srch->idx;
> +
> +	elem_field_cnt = srch->idx - start_idx;
> +	info = __next_field_infos(srch, elem_field_cnt * (nelems - 1));
> +	if (IS_ERR_OR_NULL(info))
> +		return PTR_ERR(info);

What happens if nelems = 1?


> +
> +	/* Array elems after the first can copy first elem's btf_field_infos
> +	 * and adjust offset
> +	 */
> +	for (i = 1; i < nelems; i++) {
> +		memcpy(info, &srch->infos[start_idx],
> +		       elem_field_cnt * sizeof(struct btf_field_info));
> +		for (j = 0; j < elem_field_cnt; j++) {
> +			info->off += (i * elem_type->size);
> +			info++;
> +		}
> +	}
> +	return srch->idx;
> +}
> +
> [...]
Andrii Nakryiko Nov. 1, 2023, 9:56 p.m. UTC | #4
On Mon, Oct 23, 2023 at 3:00 PM Dave Marchevsky <davemarchevsky@fb.com> wrote:
>
> Structs and arrays are aggregate types which contain some inner
> type(s) - members and elements - at various offsets. Currently, when
> examining a struct or datasec for special fields, the verifier does
> not look into the inner type of the structs or arrays it contains.
> This patch adds logic to descend into struct and array types when
> searching for special fields.
>
> If we have struct x containing an array:
>
> struct x {
>   int a;
>   u64 b[10];
> };
>
> we can construct some struct y with no array or struct members that
> has the same types at the same offsets:
>
> struct y {
>   int a;
>   u64 b1;
>   u64 b2;
>   /* ... */
>   u64 b10;
> };
>
> Similarly for a struct containing a struct:
>
> struct x {
>   char a;
>   struct {
>     int b;
>     u64 c;
>   } inner;
> };
>
> there's a struct y with no aggregate members and same types/offsets:
>
> struct y {
>   char a;
>   int inner_b __attribute__ ((aligned (8))); /* See [0] */
>   u64 inner_c __attribute__ ((aligned (8)));
> };
>
> This patch takes advantage of this equivalence to 'flatten' the
> field info found while descending into struct or array members into
> the btf_field_info result array of the original type being examined.
> The resultant btf_record of the original type being searched will
> have the correct fields at the correct offsets, but without any
> differentiation between "this field is one of my members" and "this
> field is actually in my some struct / array member".
>
> For now this descendant search logic looks for kptr fields only.
>
> Implementation notes:
>   * Search starts at btf_find_field - we're either looking at a struct
>     that's the type of some mapval (btf_find_struct_field), or a
>     datasec representing a .bss or .data map (btf_find_datasec_var).
>     Newly-added btf_find_aggregate_field is a "disambiguation helper"
>     like btf_find_field, but is meant to be called from one of the
>     starting points of the search - btf_find_{struct_field,
>     datasec_var}.
>     * btf_find_aggregate_field may itself call btf_find_struct_field,
>       so there's some recursive digging possible here
>
>   * Newly-added btf_flatten_array_field handles array fields by
>     finding the type of their element and continuing the dig based on
>     elem type.
>
>   [0]:  Structs have the alignment of their largest field, so the
>         explicit alignment is necessary here. Luckily this patch's
>         changes don't need to care about alignment / padding, since
>         the BTF created during compilation is being searched, and
>         it already has the correct information.
>
> Signed-off-by: Dave Marchevsky <davemarchevsky@fb.com>
> ---
>  kernel/bpf/btf.c | 151 ++++++++++++++++++++++++++++++++++++++++++++---
>  1 file changed, 142 insertions(+), 9 deletions(-)
>
> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index e999ba85c363..b982bf6fef9d 100644
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c
> @@ -3496,9 +3496,41 @@ static int __struct_member_check_align(u32 off, enum btf_field_type field_type)
>         return 0;
>  }
>
> +/* Return number of elems and elem_type of a btf_array
> + *
> + * If the array is multi-dimensional, return elem count of
> + * equivalent single-dimensional array
> + *   e.g. int x[10][10][10] has same layout as int x[1000]
> + */
> +static u32 __multi_dim_elem_type_nelems(const struct btf *btf,

What's the purpose of these double underscored names? Are we avoiding
a naming conflict here?

> +                                       const struct btf_type *t,
> +                                       const struct btf_type **elem_type)
> +{
> +       u32 nelems = btf_array(t)->nelems;
> +
> +       if (!nelems)
> +               return 0;
> +
> +       *elem_type = btf_type_by_id(btf, btf_array(t)->type);
> +
> +       while (btf_type_is_array(*elem_type)) {

you need to strip modifiers and typedefs, presumably?

> +               if (!btf_array(*elem_type)->nelems)
> +                       return 0;

I agree with Yonghong, this duplicated nelems == 0 check does look a
bit sloppy and unsure :) I'd rather us handle zero naturally

> +               nelems *= btf_array(*elem_type)->nelems;

check for overflow?

> +               *elem_type = btf_type_by_id(btf, btf_array(*elem_type)->type);

think about skipping modifiers and maybe typedefs?

> +       }
> +       return nelems;
> +}
> +
> +static int btf_find_aggregate_field(const struct btf *btf,
> +                                   const struct btf_type *t,
> +                                   struct btf_field_info_search *srch,
> +                                   int field_off, int rec);
> +
>  static int btf_find_struct_field(const struct btf *btf,
>                                  const struct btf_type *t,
> -                                struct btf_field_info_search *srch)
> +                                struct btf_field_info_search *srch,
> +                                int struct_field_off, int rec)
>  {
>         const struct btf_member *member;
>         int ret, field_type;
> @@ -3522,10 +3554,24 @@ static int btf_find_struct_field(const struct btf *btf,
>                          * checks, all ptrs have same align.
>                          * btf_maybe_find_kptr will find actual kptr type
>                          */
> -                       if (__struct_member_check_align(off, BPF_KPTR_REF))
> +                       if (srch->field_mask & BPF_KPTR &&

nit: () around & operation?


> +                           !__struct_member_check_align(off, BPF_KPTR_REF)) {
> +                               ret = btf_maybe_find_kptr(btf, member_type,
> +                                                         struct_field_off + off,
> +                                                         srch);

nit: does it fit in under 100 characters? If yes, go for it.

> +                               if (ret < 0)
> +                                       return ret;
> +                               if (ret == BTF_FIELD_FOUND)
> +                                       continue;
> +                       }
> +
> +                       if (!(btf_type_is_array(member_type) ||
> +                             __btf_type_is_struct(member_type)))
>                                 continue;
>
> -                       ret = btf_maybe_find_kptr(btf, member_type, off, srch);
> +                       ret = btf_find_aggregate_field(btf, member_type, srch,
> +                                                      struct_field_off + off,
> +                                                      rec);
>                         if (ret < 0)
>                                 return ret;
>                         continue;
> @@ -3541,15 +3587,17 @@ static int btf_find_struct_field(const struct btf *btf,
>                 case BPF_LIST_NODE:
>                 case BPF_RB_NODE:
>                 case BPF_REFCOUNT:
> -                       ret = btf_find_struct(btf, member_type, off, sz, field_type,
> -                                             srch);
> +                       ret = btf_find_struct(btf, member_type,
> +                                             struct_field_off + off,
> +                                             sz, field_type, srch);
>                         if (ret < 0)
>                                 return ret;
>                         break;
>                 case BPF_LIST_HEAD:
>                 case BPF_RB_ROOT:
>                         ret = btf_find_graph_root(btf, t, member_type,
> -                                                 i, off, sz, srch, field_type);
> +                                                 i, struct_field_off + off, sz,
> +                                                 srch, field_type);
>                         if (ret < 0)
>                                 return ret;
>                         break;
> @@ -3566,6 +3614,82 @@ static int btf_find_struct_field(const struct btf *btf,
>         return srch->idx;
>  }
>
> +static int btf_flatten_array_field(const struct btf *btf,
> +                                  const struct btf_type *t,
> +                                  struct btf_field_info_search *srch,
> +                                  int array_field_off, int rec)
> +{
> +       int ret, start_idx, elem_field_cnt;
> +       const struct btf_type *elem_type;
> +       struct btf_field_info *info;
> +       u32 i, j, off, nelems;
> +
> +       if (!btf_type_is_array(t))
> +               return -EINVAL;
> +       nelems = __multi_dim_elem_type_nelems(btf, t, &elem_type);
> +       if (!nelems || !__btf_type_is_struct(elem_type))

and typedef fails this check, so yeah, you do need to strip typedefs

> +               return srch->idx;
> +
> +       start_idx = srch->idx;
> +       ret = btf_find_struct_field(btf, elem_type, srch, array_field_off + off, rec);
> +       if (ret < 0)
> +               return ret;
> +
> +       /* No btf_field_info's added */
> +       if (srch->idx == start_idx)
> +               return srch->idx;
> +
> +       elem_field_cnt = srch->idx - start_idx;
> +       info = __next_field_infos(srch, elem_field_cnt * (nelems - 1));
> +       if (IS_ERR_OR_NULL(info))
> +               return PTR_ERR(info);
> +
> +       /* Array elems after the first can copy first elem's btf_field_infos
> +        * and adjust offset
> +        */
> +       for (i = 1; i < nelems; i++) {
> +               memcpy(info, &srch->infos[start_idx],
> +                      elem_field_cnt * sizeof(struct btf_field_info));
> +               for (j = 0; j < elem_field_cnt; j++) {

nit: instead of memcpy above, why not just

*info = srch->infos[start_idx + j];

inside the loop?

It seems a bit more natural in this case, as you are adjusting each
copied element either way.

Or, you know, if we go with memcpy, then why not single memcpy() with
elem_field_cnt * (nelems - 1) elements?


> +                       info->off += (i * elem_type->size);
> +                       info++;

can you please check if zero-sized structs are handled (and probably
rejected) correctly?

E.g.:

struct my_struct {
    struct fancy_kptr __kptr kptrs[0];
}

> +               }
> +       }
> +       return srch->idx;
> +}
> +

[...]
diff mbox series

Patch

diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index e999ba85c363..b982bf6fef9d 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -3496,9 +3496,41 @@  static int __struct_member_check_align(u32 off, enum btf_field_type field_type)
 	return 0;
 }
 
+/* Return number of elems and elem_type of a btf_array
+ *
+ * If the array is multi-dimensional, return elem count of
+ * equivalent single-dimensional array
+ *   e.g. int x[10][10][10] has same layout as int x[1000]
+ */
+static u32 __multi_dim_elem_type_nelems(const struct btf *btf,
+					const struct btf_type *t,
+					const struct btf_type **elem_type)
+{
+	u32 nelems = btf_array(t)->nelems;
+
+	if (!nelems)
+		return 0;
+
+	*elem_type = btf_type_by_id(btf, btf_array(t)->type);
+
+	while (btf_type_is_array(*elem_type)) {
+		if (!btf_array(*elem_type)->nelems)
+			return 0;
+		nelems *= btf_array(*elem_type)->nelems;
+		*elem_type = btf_type_by_id(btf, btf_array(*elem_type)->type);
+	}
+	return nelems;
+}
+
+static int btf_find_aggregate_field(const struct btf *btf,
+				    const struct btf_type *t,
+				    struct btf_field_info_search *srch,
+				    int field_off, int rec);
+
 static int btf_find_struct_field(const struct btf *btf,
 				 const struct btf_type *t,
-				 struct btf_field_info_search *srch)
+				 struct btf_field_info_search *srch,
+				 int struct_field_off, int rec)
 {
 	const struct btf_member *member;
 	int ret, field_type;
@@ -3522,10 +3554,24 @@  static int btf_find_struct_field(const struct btf *btf,
 			 * checks, all ptrs have same align.
 			 * btf_maybe_find_kptr will find actual kptr type
 			 */
-			if (__struct_member_check_align(off, BPF_KPTR_REF))
+			if (srch->field_mask & BPF_KPTR &&
+			    !__struct_member_check_align(off, BPF_KPTR_REF)) {
+				ret = btf_maybe_find_kptr(btf, member_type,
+							  struct_field_off + off,
+							  srch);
+				if (ret < 0)
+					return ret;
+				if (ret == BTF_FIELD_FOUND)
+					continue;
+			}
+
+			if (!(btf_type_is_array(member_type) ||
+			      __btf_type_is_struct(member_type)))
 				continue;
 
-			ret = btf_maybe_find_kptr(btf, member_type, off, srch);
+			ret = btf_find_aggregate_field(btf, member_type, srch,
+						       struct_field_off + off,
+						       rec);
 			if (ret < 0)
 				return ret;
 			continue;
@@ -3541,15 +3587,17 @@  static int btf_find_struct_field(const struct btf *btf,
 		case BPF_LIST_NODE:
 		case BPF_RB_NODE:
 		case BPF_REFCOUNT:
-			ret = btf_find_struct(btf, member_type, off, sz, field_type,
-					      srch);
+			ret = btf_find_struct(btf, member_type,
+					      struct_field_off + off,
+					      sz, field_type, srch);
 			if (ret < 0)
 				return ret;
 			break;
 		case BPF_LIST_HEAD:
 		case BPF_RB_ROOT:
 			ret = btf_find_graph_root(btf, t, member_type,
-						  i, off, sz, srch, field_type);
+						  i, struct_field_off + off, sz,
+						  srch, field_type);
 			if (ret < 0)
 				return ret;
 			break;
@@ -3566,6 +3614,82 @@  static int btf_find_struct_field(const struct btf *btf,
 	return srch->idx;
 }
 
+static int btf_flatten_array_field(const struct btf *btf,
+				   const struct btf_type *t,
+				   struct btf_field_info_search *srch,
+				   int array_field_off, int rec)
+{
+	int ret, start_idx, elem_field_cnt;
+	const struct btf_type *elem_type;
+	struct btf_field_info *info;
+	u32 i, j, off, nelems;
+
+	if (!btf_type_is_array(t))
+		return -EINVAL;
+	nelems = __multi_dim_elem_type_nelems(btf, t, &elem_type);
+	if (!nelems || !__btf_type_is_struct(elem_type))
+		return srch->idx;
+
+	start_idx = srch->idx;
+	ret = btf_find_struct_field(btf, elem_type, srch, array_field_off + off, rec);
+	if (ret < 0)
+		return ret;
+
+	/* No btf_field_info's added */
+	if (srch->idx == start_idx)
+		return srch->idx;
+
+	elem_field_cnt = srch->idx - start_idx;
+	info = __next_field_infos(srch, elem_field_cnt * (nelems - 1));
+	if (IS_ERR_OR_NULL(info))
+		return PTR_ERR(info);
+
+	/* Array elems after the first can copy first elem's btf_field_infos
+	 * and adjust offset
+	 */
+	for (i = 1; i < nelems; i++) {
+		memcpy(info, &srch->infos[start_idx],
+		       elem_field_cnt * sizeof(struct btf_field_info));
+		for (j = 0; j < elem_field_cnt; j++) {
+			info->off += (i * elem_type->size);
+			info++;
+		}
+	}
+	return srch->idx;
+}
+
+static int btf_find_aggregate_field(const struct btf *btf,
+				    const struct btf_type *t,
+				    struct btf_field_info_search *srch,
+				    int field_off, int rec)
+{
+	u32 orig_field_mask;
+	int ret;
+
+	/* Dig up to 4 levels deep */
+	if (rec >= 4)
+		return -E2BIG;
+
+	orig_field_mask = srch->field_mask;
+	srch->field_mask &= BPF_KPTR;
+
+	if (!srch->field_mask) {
+		ret = 0;
+		goto reset_field_mask;
+	}
+
+	if (__btf_type_is_struct(t))
+		ret = btf_find_struct_field(btf, t, srch, field_off, rec + 1);
+	else if (btf_type_is_array(t))
+		ret = btf_flatten_array_field(btf, t, srch, field_off, rec + 1);
+	else
+		ret = -EINVAL;
+
+reset_field_mask:
+	srch->field_mask = orig_field_mask;
+	return ret;
+}
+
 static int __datasec_vsi_check_align_sz(const struct btf_var_secinfo *vsi,
 					enum btf_field_type field_type,
 					u32 expected_sz)
@@ -3605,10 +3729,19 @@  static int btf_find_datasec_var(const struct btf *btf, const struct btf_type *t,
 			 * btf_maybe_find_kptr will find actual kptr type
 			 */
 			sz = btf_field_type_size(BPF_KPTR_REF);
-			if (__datasec_vsi_check_align_sz(vsi, BPF_KPTR_REF, sz))
+			if (srch->field_mask & BPF_KPTR &&
+			    !__datasec_vsi_check_align_sz(vsi, BPF_KPTR_REF, sz)) {
+				ret = btf_maybe_find_kptr(btf, var_type, off, srch);
+				if (ret < 0)
+					return ret;
+				if (ret == BTF_FIELD_FOUND)
+					continue;
+			}
+
+			if (!(btf_type_is_array(var_type) || __btf_type_is_struct(var_type)))
 				continue;
 
-			ret = btf_maybe_find_kptr(btf, var_type, off, srch);
+			ret = btf_find_aggregate_field(btf, var_type, srch, off, 0);
 			if (ret < 0)
 				return ret;
 			continue;
@@ -3655,7 +3788,7 @@  static int btf_find_field(const struct btf *btf, const struct btf_type *t,
 			  struct btf_field_info_search *srch)
 {
 	if (__btf_type_is_struct(t))
-		return btf_find_struct_field(btf, t, srch);
+		return btf_find_struct_field(btf, t, srch, 0, 0);
 	else if (btf_type_is_datasec(t))
 		return btf_find_datasec_var(btf, t, srch);
 	return -EINVAL;