mirror of
https://github.com/raspberrypi/linux.git
synced 2025-12-16 06:51:42 +00:00
In get_bpf_prog_info_linear two calls to bpf_obj_get_info_by_fd are
made, the first to compute memory requirements for a struct perf_bpil
and the second to fill it in. Previously the code would warn when the
second call didn't match the first. Such races can be common place in
things like perf test, whose perf trace tests will frequently load BPF
programs. Rather than a debug message, return actual errors for this
case. Out of paranoia also validate the read bpf_prog_info array
value. Change the type of ptr to avoid mismatched pointer type
compiler warnings. Add some additional debug print outs and sanity
asserts.
Closes: https://lore.kernel.org/lkml/CAP-5=fWJQcmUOP7MuCA2ihKnDAHUCOBLkQFEkQES-1ZZTrgf8Q@mail.gmail.com/
Fixes: 6ac22d036f ("perf bpf: Pull in bpf_program__get_prog_info_linear()")
Reviewed-by: Namhyung Kim <namhyung@kernel.org>
Signed-off-by: Ian Rogers <irogers@google.com>
Link: https://lore.kernel.org/r/20250902181713.309797-4-irogers@google.com
Signed-off-by: Namhyung Kim <namhyung@kernel.org>
278 lines
7.8 KiB
C
278 lines
7.8 KiB
C
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
|
|
|
#ifndef _GNU_SOURCE
|
|
#define _GNU_SOURCE
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <linux/err.h>
|
|
#include <linux/kernel.h>
|
|
#include <bpf/bpf.h>
|
|
#include "bpf-utils.h"
|
|
#include "debug.h"
|
|
|
|
struct bpil_array_desc {
|
|
int array_offset; /* e.g. offset of jited_prog_insns */
|
|
int count_offset; /* e.g. offset of jited_prog_len */
|
|
int size_offset; /* > 0: offset of rec size,
|
|
* < 0: fix size of -size_offset
|
|
*/
|
|
};
|
|
|
|
static const struct bpil_array_desc bpil_array_desc[] = {
|
|
[PERF_BPIL_JITED_INSNS] = {
|
|
offsetof(struct bpf_prog_info, jited_prog_insns),
|
|
offsetof(struct bpf_prog_info, jited_prog_len),
|
|
-1,
|
|
},
|
|
[PERF_BPIL_XLATED_INSNS] = {
|
|
offsetof(struct bpf_prog_info, xlated_prog_insns),
|
|
offsetof(struct bpf_prog_info, xlated_prog_len),
|
|
-1,
|
|
},
|
|
[PERF_BPIL_MAP_IDS] = {
|
|
offsetof(struct bpf_prog_info, map_ids),
|
|
offsetof(struct bpf_prog_info, nr_map_ids),
|
|
-(int)sizeof(__u32),
|
|
},
|
|
[PERF_BPIL_JITED_KSYMS] = {
|
|
offsetof(struct bpf_prog_info, jited_ksyms),
|
|
offsetof(struct bpf_prog_info, nr_jited_ksyms),
|
|
-(int)sizeof(__u64),
|
|
},
|
|
[PERF_BPIL_JITED_FUNC_LENS] = {
|
|
offsetof(struct bpf_prog_info, jited_func_lens),
|
|
offsetof(struct bpf_prog_info, nr_jited_func_lens),
|
|
-(int)sizeof(__u32),
|
|
},
|
|
[PERF_BPIL_FUNC_INFO] = {
|
|
offsetof(struct bpf_prog_info, func_info),
|
|
offsetof(struct bpf_prog_info, nr_func_info),
|
|
offsetof(struct bpf_prog_info, func_info_rec_size),
|
|
},
|
|
[PERF_BPIL_LINE_INFO] = {
|
|
offsetof(struct bpf_prog_info, line_info),
|
|
offsetof(struct bpf_prog_info, nr_line_info),
|
|
offsetof(struct bpf_prog_info, line_info_rec_size),
|
|
},
|
|
[PERF_BPIL_JITED_LINE_INFO] = {
|
|
offsetof(struct bpf_prog_info, jited_line_info),
|
|
offsetof(struct bpf_prog_info, nr_jited_line_info),
|
|
offsetof(struct bpf_prog_info, jited_line_info_rec_size),
|
|
},
|
|
[PERF_BPIL_PROG_TAGS] = {
|
|
offsetof(struct bpf_prog_info, prog_tags),
|
|
offsetof(struct bpf_prog_info, nr_prog_tags),
|
|
-(int)sizeof(__u8) * BPF_TAG_SIZE,
|
|
},
|
|
|
|
};
|
|
|
|
static __u32 bpf_prog_info_read_offset_u32(struct bpf_prog_info *info,
|
|
int offset)
|
|
{
|
|
__u32 *array = (__u32 *)info;
|
|
|
|
if (offset >= 0)
|
|
return array[offset / sizeof(__u32)];
|
|
return -(int)offset;
|
|
}
|
|
|
|
static __u64 bpf_prog_info_read_offset_u64(struct bpf_prog_info *info,
|
|
int offset)
|
|
{
|
|
__u64 *array = (__u64 *)info;
|
|
|
|
if (offset >= 0)
|
|
return array[offset / sizeof(__u64)];
|
|
return -(int)offset;
|
|
}
|
|
|
|
static void bpf_prog_info_set_offset_u32(struct bpf_prog_info *info, int offset,
|
|
__u32 val)
|
|
{
|
|
__u32 *array = (__u32 *)info;
|
|
|
|
if (offset >= 0)
|
|
array[offset / sizeof(__u32)] = val;
|
|
}
|
|
|
|
static void bpf_prog_info_set_offset_u64(struct bpf_prog_info *info, int offset,
|
|
__u64 val)
|
|
{
|
|
__u64 *array = (__u64 *)info;
|
|
|
|
if (offset >= 0)
|
|
array[offset / sizeof(__u64)] = val;
|
|
}
|
|
|
|
struct perf_bpil *
|
|
get_bpf_prog_info_linear(int fd, __u64 arrays)
|
|
{
|
|
struct bpf_prog_info info = {};
|
|
struct perf_bpil *info_linear;
|
|
__u32 info_len = sizeof(info);
|
|
__u32 data_len = 0;
|
|
int i, err;
|
|
__u8 *ptr;
|
|
|
|
if (arrays >> PERF_BPIL_LAST_ARRAY)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
/* step 1: get array dimensions */
|
|
err = bpf_obj_get_info_by_fd(fd, &info, &info_len);
|
|
if (err) {
|
|
pr_debug("can't get prog info: %s", strerror(errno));
|
|
return ERR_PTR(-EFAULT);
|
|
}
|
|
if (info.type >= __MAX_BPF_PROG_TYPE)
|
|
pr_debug("%s:%d: unexpected program type %u\n", __func__, __LINE__, info.type);
|
|
|
|
/* step 2: calculate total size of all arrays */
|
|
for (i = PERF_BPIL_FIRST_ARRAY; i < PERF_BPIL_LAST_ARRAY; ++i) {
|
|
const struct bpil_array_desc *desc = &bpil_array_desc[i];
|
|
bool include_array = (arrays & (1UL << i)) > 0;
|
|
__u32 count, size;
|
|
|
|
/* kernel is too old to support this field */
|
|
if (info_len < desc->array_offset + sizeof(__u32) ||
|
|
info_len < desc->count_offset + sizeof(__u32) ||
|
|
(desc->size_offset > 0 && info_len < (__u32)desc->size_offset))
|
|
include_array = false;
|
|
|
|
if (!include_array) {
|
|
arrays &= ~(1UL << i); /* clear the bit */
|
|
continue;
|
|
}
|
|
|
|
count = bpf_prog_info_read_offset_u32(&info, desc->count_offset);
|
|
size = bpf_prog_info_read_offset_u32(&info, desc->size_offset);
|
|
|
|
data_len += roundup(count * size, sizeof(__u64));
|
|
}
|
|
|
|
/* step 3: allocate continuous memory */
|
|
info_linear = malloc(sizeof(struct perf_bpil) + data_len);
|
|
if (!info_linear)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
/* step 4: fill data to info_linear->info */
|
|
info_linear->arrays = arrays;
|
|
memset(&info_linear->info, 0, sizeof(info));
|
|
ptr = info_linear->data;
|
|
|
|
for (i = PERF_BPIL_FIRST_ARRAY; i < PERF_BPIL_LAST_ARRAY; ++i) {
|
|
const struct bpil_array_desc *desc = &bpil_array_desc[i];
|
|
__u32 count, size;
|
|
|
|
if ((arrays & (1UL << i)) == 0)
|
|
continue;
|
|
|
|
count = bpf_prog_info_read_offset_u32(&info, desc->count_offset);
|
|
size = bpf_prog_info_read_offset_u32(&info, desc->size_offset);
|
|
bpf_prog_info_set_offset_u32(&info_linear->info,
|
|
desc->count_offset, count);
|
|
bpf_prog_info_set_offset_u32(&info_linear->info,
|
|
desc->size_offset, size);
|
|
assert(ptr >= info_linear->data);
|
|
assert(ptr < &info_linear->data[data_len]);
|
|
bpf_prog_info_set_offset_u64(&info_linear->info,
|
|
desc->array_offset,
|
|
ptr_to_u64(ptr));
|
|
ptr += roundup(count * size, sizeof(__u64));
|
|
}
|
|
|
|
/* step 5: call syscall again to get required arrays */
|
|
err = bpf_obj_get_info_by_fd(fd, &info_linear->info, &info_len);
|
|
if (err) {
|
|
pr_debug("can't get prog info: %s", strerror(errno));
|
|
free(info_linear);
|
|
return ERR_PTR(-EFAULT);
|
|
}
|
|
if (info_linear->info.type >= __MAX_BPF_PROG_TYPE) {
|
|
pr_debug("%s:%d: unexpected program type %u\n",
|
|
__func__, __LINE__, info_linear->info.type);
|
|
}
|
|
|
|
/* step 6: verify the data */
|
|
ptr = info_linear->data;
|
|
for (i = PERF_BPIL_FIRST_ARRAY; i < PERF_BPIL_LAST_ARRAY; ++i) {
|
|
const struct bpil_array_desc *desc = &bpil_array_desc[i];
|
|
__u32 count1, count2, size1, size2;
|
|
__u64 ptr2;
|
|
|
|
if ((arrays & (1UL << i)) == 0)
|
|
continue;
|
|
|
|
count1 = bpf_prog_info_read_offset_u32(&info, desc->count_offset);
|
|
count2 = bpf_prog_info_read_offset_u32(&info_linear->info,
|
|
desc->count_offset);
|
|
if (count1 != count2) {
|
|
pr_warning("%s: mismatch in element count %u vs %u\n", __func__, count1, count2);
|
|
free(info_linear);
|
|
return ERR_PTR(-ERANGE);
|
|
}
|
|
|
|
size1 = bpf_prog_info_read_offset_u32(&info, desc->size_offset);
|
|
size2 = bpf_prog_info_read_offset_u32(&info_linear->info,
|
|
desc->size_offset);
|
|
if (size1 != size2) {
|
|
pr_warning("%s: mismatch in rec size %u vs %u\n", __func__, size1, size2);
|
|
free(info_linear);
|
|
return ERR_PTR(-ERANGE);
|
|
}
|
|
ptr2 = bpf_prog_info_read_offset_u64(&info_linear->info, desc->array_offset);
|
|
if (ptr_to_u64(ptr) != ptr2) {
|
|
pr_warning("%s: mismatch in array %p vs %llx\n", __func__, ptr, ptr2);
|
|
free(info_linear);
|
|
return ERR_PTR(-ERANGE);
|
|
}
|
|
ptr += roundup(count1 * size1, sizeof(__u64));
|
|
}
|
|
|
|
/* step 7: update info_len and data_len */
|
|
info_linear->info_len = sizeof(struct bpf_prog_info);
|
|
info_linear->data_len = data_len;
|
|
|
|
return info_linear;
|
|
}
|
|
|
|
void bpil_addr_to_offs(struct perf_bpil *info_linear)
|
|
{
|
|
int i;
|
|
|
|
for (i = PERF_BPIL_FIRST_ARRAY; i < PERF_BPIL_LAST_ARRAY; ++i) {
|
|
const struct bpil_array_desc *desc = &bpil_array_desc[i];
|
|
__u64 addr, offs;
|
|
|
|
if ((info_linear->arrays & (1UL << i)) == 0)
|
|
continue;
|
|
|
|
addr = bpf_prog_info_read_offset_u64(&info_linear->info,
|
|
desc->array_offset);
|
|
offs = addr - ptr_to_u64(info_linear->data);
|
|
bpf_prog_info_set_offset_u64(&info_linear->info,
|
|
desc->array_offset, offs);
|
|
}
|
|
}
|
|
|
|
void bpil_offs_to_addr(struct perf_bpil *info_linear)
|
|
{
|
|
int i;
|
|
|
|
for (i = PERF_BPIL_FIRST_ARRAY; i < PERF_BPIL_LAST_ARRAY; ++i) {
|
|
const struct bpil_array_desc *desc = &bpil_array_desc[i];
|
|
__u64 addr, offs;
|
|
|
|
if ((info_linear->arrays & (1UL << i)) == 0)
|
|
continue;
|
|
|
|
offs = bpf_prog_info_read_offset_u64(&info_linear->info,
|
|
desc->array_offset);
|
|
addr = offs + ptr_to_u64(info_linear->data);
|
|
bpf_prog_info_set_offset_u64(&info_linear->info,
|
|
desc->array_offset, addr);
|
|
}
|
|
}
|