lib/buildid: harden build ID parsing logic
Harden build ID parsing logic, adding explicit READ_ONCE() where it's
important to have a consistent value read and validated just once.
Also, as pointed out by Andi Kleen, we need to make sure that entire ELF
note is within a page bounds, so move the overflow check up and add an
extra note_size boundaries validation.
Fixes tag below points to the code that moved this code into
lib/buildid.c, and then subsequently was used in perf subsystem, making
this code exposed to perf_event_open() users in v5.12+.
Cc: stable@vger.kernel.org
Reviewed-by: Eduard Zingerman <eddyz87@gmail.com>
Reviewed-by: Jann Horn <jannh@google.com>
Suggested-by: Andi Kleen <ak@linux.intel.com>
Fixes: bd7525dacd
("bpf: Move stack_map_get_build_id into lib")
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/r/20240829174232.3133883-2-andrii@kernel.org
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
parent
58ff04e2e2
commit
905415ff3f
@ -18,31 +18,37 @@ static int parse_build_id_buf(unsigned char *build_id,
|
||||
const void *note_start,
|
||||
Elf32_Word note_size)
|
||||
{
|
||||
Elf32_Word note_offs = 0, new_offs;
|
||||
const char note_name[] = "GNU";
|
||||
const size_t note_name_sz = sizeof(note_name);
|
||||
u64 note_off = 0, new_off, name_sz, desc_sz;
|
||||
const char *data;
|
||||
|
||||
while (note_offs + sizeof(Elf32_Nhdr) < note_size) {
|
||||
Elf32_Nhdr *nhdr = (Elf32_Nhdr *)(note_start + note_offs);
|
||||
while (note_off + sizeof(Elf32_Nhdr) < note_size &&
|
||||
note_off + sizeof(Elf32_Nhdr) > note_off /* overflow */) {
|
||||
Elf32_Nhdr *nhdr = (Elf32_Nhdr *)(note_start + note_off);
|
||||
|
||||
name_sz = READ_ONCE(nhdr->n_namesz);
|
||||
desc_sz = READ_ONCE(nhdr->n_descsz);
|
||||
|
||||
new_off = note_off + sizeof(Elf32_Nhdr);
|
||||
if (check_add_overflow(new_off, ALIGN(name_sz, 4), &new_off) ||
|
||||
check_add_overflow(new_off, ALIGN(desc_sz, 4), &new_off) ||
|
||||
new_off > note_size)
|
||||
break;
|
||||
|
||||
if (nhdr->n_type == BUILD_ID &&
|
||||
nhdr->n_namesz == sizeof("GNU") &&
|
||||
!strcmp((char *)(nhdr + 1), "GNU") &&
|
||||
nhdr->n_descsz > 0 &&
|
||||
nhdr->n_descsz <= BUILD_ID_SIZE_MAX) {
|
||||
memcpy(build_id,
|
||||
note_start + note_offs +
|
||||
ALIGN(sizeof("GNU"), 4) + sizeof(Elf32_Nhdr),
|
||||
nhdr->n_descsz);
|
||||
memset(build_id + nhdr->n_descsz, 0,
|
||||
BUILD_ID_SIZE_MAX - nhdr->n_descsz);
|
||||
name_sz == note_name_sz &&
|
||||
memcmp(nhdr + 1, note_name, note_name_sz) == 0 &&
|
||||
desc_sz > 0 && desc_sz <= BUILD_ID_SIZE_MAX) {
|
||||
data = note_start + note_off + ALIGN(note_name_sz, 4);
|
||||
memcpy(build_id, data, desc_sz);
|
||||
memset(build_id + desc_sz, 0, BUILD_ID_SIZE_MAX - desc_sz);
|
||||
if (size)
|
||||
*size = nhdr->n_descsz;
|
||||
*size = desc_sz;
|
||||
return 0;
|
||||
}
|
||||
new_offs = note_offs + sizeof(Elf32_Nhdr) +
|
||||
ALIGN(nhdr->n_namesz, 4) + ALIGN(nhdr->n_descsz, 4);
|
||||
if (new_offs <= note_offs) /* overflow */
|
||||
break;
|
||||
note_offs = new_offs;
|
||||
|
||||
note_off = new_off;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
@ -71,7 +77,7 @@ static int get_build_id_32(const void *page_addr, unsigned char *build_id,
|
||||
{
|
||||
Elf32_Ehdr *ehdr = (Elf32_Ehdr *)page_addr;
|
||||
Elf32_Phdr *phdr;
|
||||
int i;
|
||||
__u32 i, phnum;
|
||||
|
||||
/*
|
||||
* FIXME
|
||||
@ -80,18 +86,19 @@ static int get_build_id_32(const void *page_addr, unsigned char *build_id,
|
||||
*/
|
||||
if (ehdr->e_phoff != sizeof(Elf32_Ehdr))
|
||||
return -EINVAL;
|
||||
|
||||
phnum = READ_ONCE(ehdr->e_phnum);
|
||||
/* only supports phdr that fits in one page */
|
||||
if (ehdr->e_phnum >
|
||||
(PAGE_SIZE - sizeof(Elf32_Ehdr)) / sizeof(Elf32_Phdr))
|
||||
if (phnum > (PAGE_SIZE - sizeof(Elf32_Ehdr)) / sizeof(Elf32_Phdr))
|
||||
return -EINVAL;
|
||||
|
||||
phdr = (Elf32_Phdr *)(page_addr + sizeof(Elf32_Ehdr));
|
||||
|
||||
for (i = 0; i < ehdr->e_phnum; ++i) {
|
||||
for (i = 0; i < phnum; ++i) {
|
||||
if (phdr[i].p_type == PT_NOTE &&
|
||||
!parse_build_id(page_addr, build_id, size,
|
||||
page_addr + phdr[i].p_offset,
|
||||
phdr[i].p_filesz))
|
||||
page_addr + READ_ONCE(phdr[i].p_offset),
|
||||
READ_ONCE(phdr[i].p_filesz)))
|
||||
return 0;
|
||||
}
|
||||
return -EINVAL;
|
||||
@ -103,7 +110,7 @@ static int get_build_id_64(const void *page_addr, unsigned char *build_id,
|
||||
{
|
||||
Elf64_Ehdr *ehdr = (Elf64_Ehdr *)page_addr;
|
||||
Elf64_Phdr *phdr;
|
||||
int i;
|
||||
__u32 i, phnum;
|
||||
|
||||
/*
|
||||
* FIXME
|
||||
@ -112,18 +119,19 @@ static int get_build_id_64(const void *page_addr, unsigned char *build_id,
|
||||
*/
|
||||
if (ehdr->e_phoff != sizeof(Elf64_Ehdr))
|
||||
return -EINVAL;
|
||||
|
||||
phnum = READ_ONCE(ehdr->e_phnum);
|
||||
/* only supports phdr that fits in one page */
|
||||
if (ehdr->e_phnum >
|
||||
(PAGE_SIZE - sizeof(Elf64_Ehdr)) / sizeof(Elf64_Phdr))
|
||||
if (phnum > (PAGE_SIZE - sizeof(Elf64_Ehdr)) / sizeof(Elf64_Phdr))
|
||||
return -EINVAL;
|
||||
|
||||
phdr = (Elf64_Phdr *)(page_addr + sizeof(Elf64_Ehdr));
|
||||
|
||||
for (i = 0; i < ehdr->e_phnum; ++i) {
|
||||
for (i = 0; i < phnum; ++i) {
|
||||
if (phdr[i].p_type == PT_NOTE &&
|
||||
!parse_build_id(page_addr, build_id, size,
|
||||
page_addr + phdr[i].p_offset,
|
||||
phdr[i].p_filesz))
|
||||
page_addr + READ_ONCE(phdr[i].p_offset),
|
||||
READ_ONCE(phdr[i].p_filesz)))
|
||||
return 0;
|
||||
}
|
||||
return -EINVAL;
|
||||
@ -152,6 +160,10 @@ int build_id_parse(struct vm_area_struct *vma, unsigned char *build_id,
|
||||
page = find_get_page(vma->vm_file->f_mapping, 0);
|
||||
if (!page)
|
||||
return -EFAULT; /* page not mapped */
|
||||
if (!PageUptodate(page)) {
|
||||
put_page(page);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
ret = -EINVAL;
|
||||
page_addr = kmap_local_page(page);
|
||||
|
Loading…
Reference in New Issue
Block a user