mirror of
https://github.com/torvalds/linux.git
synced 2026-07-28 01:55:51 +02:00
perf header: Validate f_attr.ids section before use in perf_session__read_header()
perf_session__read_header() reads f_attr.ids.size from the perf.data file and divides it by sizeof(u64) to compute nr_ids, which is declared as int. No validation is performed on the value before it is used to allocate arrays and drive a read loop. On 32-bit architectures, a crafted f_attr.ids.size of 0x100000000 (4 GB) produces nr_ids = 0x20000000, but the allocation size 1 * 0x20000000 * 8 overflows size_t to 0, so zalloc(0) returns a valid pointer. The subsequent loop writes 0x20000000 IDs into that zero-length buffer, corrupting the heap. On 64-bit, the u64-to-int truncation silently drops high bits, processing fewer IDs than the file claims. While not exploitable, this is a data integrity issue. Add validation before using f_attr.ids: - Cap nr_attrs (attrs.size / attr_size) to MAX_NR_ATTRS (1 << 16) with overflow-safe u64 comparison before assigning to int - Reject ids.size not aligned to sizeof(u64) - Cap ids.size / sizeof(u64) to MAX_IDS_PER_ATTR (1 << 24) to prevent int truncation and size_t overflow on 32-bit - Reject ids sections that extend past the end of the file, guarded by S_ISREG() so non-regular files (block devices, pipes) are not falsely rejected Also fix perf_header__getbuffer64() to set errno = EIO when readn() returns 0 (EOF). Without this, the out_errno path in perf_session__read_header() returns -errno which is 0 (success) on truncated files, causing downstream NULL dereferences. Reported-by: sashiko-bot@kernel.org # Running on a local machine Reviewed-by: Ian Rogers <irogers@google.com> Cc: Jiri Olsa <jolsa@kernel.org> Cc: Namhyung Kim <namhyung@kernel.org> Assisted-by: Claude:claude-opus-4.6-1m Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
parent
7d19f2008a
commit
7685cc0f2e
|
|
@ -64,6 +64,25 @@
|
|||
#include <event-parse.h>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* nr_ids * sizeof(struct perf_sample_id) must not overflow
|
||||
* size_t on 32-bit; the struct is ~104 bytes (32-bit) or
|
||||
* ~184 bytes (64-bit), so 1<<24 (16M) keeps the product
|
||||
* under 2 GB on 32-bit.
|
||||
*
|
||||
* This is a per-attribute cap only — the total across all
|
||||
* attributes is not capped because legitimate high-core-count
|
||||
* workloads (e.g. 5000 tracepoints × 4096 CPUs) can exceed
|
||||
* a single-attribute limit.
|
||||
*/
|
||||
#define MAX_IDS_PER_ATTR (1 << 24)
|
||||
/*
|
||||
* Cap nr_attrs to prevent resource exhaustion from crafted
|
||||
* files. 65536 is well beyond any real workload (perf stat
|
||||
* typically uses < 100 events) but prevents u64-to-int
|
||||
* truncation on the attr count.
|
||||
*/
|
||||
#define MAX_NR_ATTRS (1 << 16)
|
||||
#define MAX_BPF_DATA_LEN (256 * 1024 * 1024)
|
||||
#define MAX_BPF_PROGS 131072
|
||||
#define MAX_CACHE_ENTRIES 32768
|
||||
|
|
@ -4468,8 +4487,13 @@ int perf_session__inject_header(struct perf_session *session,
|
|||
static int perf_header__getbuffer64(struct perf_header *header,
|
||||
int fd, void *buf, size_t size)
|
||||
{
|
||||
if (readn(fd, buf, size) <= 0)
|
||||
ssize_t n = readn(fd, buf, size);
|
||||
|
||||
if (n <= 0) {
|
||||
if (n == 0)
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (header->needs_swap)
|
||||
mem_bswap_64(buf, size);
|
||||
|
|
@ -4803,6 +4827,8 @@ static int read_attr(int fd, struct perf_header *ph,
|
|||
if (ret <= 0) {
|
||||
pr_debug("cannot read %d bytes of header attr\n",
|
||||
PERF_ATTR_SIZE_VER0);
|
||||
if (ret == 0)
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -4903,6 +4929,7 @@ int perf_session__read_header(struct perf_session *session)
|
|||
struct perf_file_header f_header;
|
||||
struct perf_file_attr f_attr;
|
||||
u64 f_id;
|
||||
struct stat input_stat;
|
||||
int nr_attrs, nr_ids, i, j, err = -ENOMEM;
|
||||
int fd = perf_data__fd(data);
|
||||
|
||||
|
|
@ -4951,6 +4978,15 @@ int perf_session__read_header(struct perf_session *session)
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (fstat(fd, &input_stat) < 0)
|
||||
return -errno;
|
||||
|
||||
/* Check before assigning to int to avoid u64-to-int truncation */
|
||||
if (f_header.attrs.size / f_header.attr_size > MAX_NR_ATTRS) {
|
||||
pr_err("Too many attributes: %" PRIu64 " (max %d)\n",
|
||||
f_header.attrs.size / f_header.attr_size, MAX_NR_ATTRS);
|
||||
return -EINVAL;
|
||||
}
|
||||
nr_attrs = f_header.attrs.size / f_header.attr_size;
|
||||
lseek(fd, f_header.attrs.offset, SEEK_SET);
|
||||
|
||||
|
|
@ -4967,6 +5003,45 @@ int perf_session__read_header(struct perf_session *session)
|
|||
perf_event__attr_swap(&f_attr.attr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate ids section: must be aligned to u64, and
|
||||
* the count must fit in an int to avoid truncation in
|
||||
* nr_ids and size_t overflow in perf_evsel__alloc_id()
|
||||
* on 32-bit architectures.
|
||||
*/
|
||||
if (f_attr.ids.size % sizeof(u64)) {
|
||||
pr_err("Invalid ids section size %" PRIu64 " for attr %d, not aligned to u64\n",
|
||||
f_attr.ids.size, i);
|
||||
err = -EINVAL;
|
||||
goto out_delete_evlist;
|
||||
}
|
||||
|
||||
/*
|
||||
* Cap the ID count to avoid int truncation of nr_ids
|
||||
* on 64-bit and size_t overflow in the allocation
|
||||
* paths (nr_ids * sizeof(u64), nr_ids *
|
||||
* sizeof(struct perf_sample_id)) on 32-bit.
|
||||
*/
|
||||
if (f_attr.ids.size / sizeof(u64) > MAX_IDS_PER_ATTR) {
|
||||
pr_err("Invalid ids section size %" PRIu64 " for attr %d, too many IDs\n",
|
||||
f_attr.ids.size, i);
|
||||
err = -EINVAL;
|
||||
goto out_delete_evlist;
|
||||
}
|
||||
|
||||
/*
|
||||
* FIXME: see perf_header__process_sections() — block
|
||||
* devices bypass this check because st_size is 0.
|
||||
*/
|
||||
if (S_ISREG(input_stat.st_mode) &&
|
||||
(f_attr.ids.offset > (u64)input_stat.st_size ||
|
||||
f_attr.ids.size > (u64)input_stat.st_size - f_attr.ids.offset)) {
|
||||
pr_err("Invalid ids section for attr %d: offset=%" PRIu64 " size=%" PRIu64 " exceeds file size %" PRIu64 "\n",
|
||||
i, f_attr.ids.offset, f_attr.ids.size, (u64)input_stat.st_size);
|
||||
err = -EINVAL;
|
||||
goto out_delete_evlist;
|
||||
}
|
||||
|
||||
tmp = lseek(fd, 0, SEEK_CUR);
|
||||
evsel = evsel__new(&f_attr.attr);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user