Skip to content

Commit 905415f

Browse files
anakryikoAlexei Starovoitov
authored andcommitted
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: [email protected] Reviewed-by: Eduard Zingerman <[email protected]> Reviewed-by: Jann Horn <[email protected]> Suggested-by: Andi Kleen <[email protected]> Fixes: bd7525d ("bpf: Move stack_map_get_build_id into lib") Signed-off-by: Andrii Nakryiko <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Alexei Starovoitov <[email protected]>
1 parent 58ff04e commit 905415f

File tree

1 file changed

+44
-32
lines changed

1 file changed

+44
-32
lines changed

lib/buildid.c

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,31 +18,37 @@ static int parse_build_id_buf(unsigned char *build_id,
1818
const void *note_start,
1919
Elf32_Word note_size)
2020
{
21-
Elf32_Word note_offs = 0, new_offs;
22-
23-
while (note_offs + sizeof(Elf32_Nhdr) < note_size) {
24-
Elf32_Nhdr *nhdr = (Elf32_Nhdr *)(note_start + note_offs);
21+
const char note_name[] = "GNU";
22+
const size_t note_name_sz = sizeof(note_name);
23+
u64 note_off = 0, new_off, name_sz, desc_sz;
24+
const char *data;
25+
26+
while (note_off + sizeof(Elf32_Nhdr) < note_size &&
27+
note_off + sizeof(Elf32_Nhdr) > note_off /* overflow */) {
28+
Elf32_Nhdr *nhdr = (Elf32_Nhdr *)(note_start + note_off);
29+
30+
name_sz = READ_ONCE(nhdr->n_namesz);
31+
desc_sz = READ_ONCE(nhdr->n_descsz);
32+
33+
new_off = note_off + sizeof(Elf32_Nhdr);
34+
if (check_add_overflow(new_off, ALIGN(name_sz, 4), &new_off) ||
35+
check_add_overflow(new_off, ALIGN(desc_sz, 4), &new_off) ||
36+
new_off > note_size)
37+
break;
2538

2639
if (nhdr->n_type == BUILD_ID &&
27-
nhdr->n_namesz == sizeof("GNU") &&
28-
!strcmp((char *)(nhdr + 1), "GNU") &&
29-
nhdr->n_descsz > 0 &&
30-
nhdr->n_descsz <= BUILD_ID_SIZE_MAX) {
31-
memcpy(build_id,
32-
note_start + note_offs +
33-
ALIGN(sizeof("GNU"), 4) + sizeof(Elf32_Nhdr),
34-
nhdr->n_descsz);
35-
memset(build_id + nhdr->n_descsz, 0,
36-
BUILD_ID_SIZE_MAX - nhdr->n_descsz);
40+
name_sz == note_name_sz &&
41+
memcmp(nhdr + 1, note_name, note_name_sz) == 0 &&
42+
desc_sz > 0 && desc_sz <= BUILD_ID_SIZE_MAX) {
43+
data = note_start + note_off + ALIGN(note_name_sz, 4);
44+
memcpy(build_id, data, desc_sz);
45+
memset(build_id + desc_sz, 0, BUILD_ID_SIZE_MAX - desc_sz);
3746
if (size)
38-
*size = nhdr->n_descsz;
47+
*size = desc_sz;
3948
return 0;
4049
}
41-
new_offs = note_offs + sizeof(Elf32_Nhdr) +
42-
ALIGN(nhdr->n_namesz, 4) + ALIGN(nhdr->n_descsz, 4);
43-
if (new_offs <= note_offs) /* overflow */
44-
break;
45-
note_offs = new_offs;
50+
51+
note_off = new_off;
4652
}
4753

4854
return -EINVAL;
@@ -71,7 +77,7 @@ static int get_build_id_32(const void *page_addr, unsigned char *build_id,
7177
{
7278
Elf32_Ehdr *ehdr = (Elf32_Ehdr *)page_addr;
7379
Elf32_Phdr *phdr;
74-
int i;
80+
__u32 i, phnum;
7581

7682
/*
7783
* FIXME
@@ -80,18 +86,19 @@ static int get_build_id_32(const void *page_addr, unsigned char *build_id,
8086
*/
8187
if (ehdr->e_phoff != sizeof(Elf32_Ehdr))
8288
return -EINVAL;
89+
90+
phnum = READ_ONCE(ehdr->e_phnum);
8391
/* only supports phdr that fits in one page */
84-
if (ehdr->e_phnum >
85-
(PAGE_SIZE - sizeof(Elf32_Ehdr)) / sizeof(Elf32_Phdr))
92+
if (phnum > (PAGE_SIZE - sizeof(Elf32_Ehdr)) / sizeof(Elf32_Phdr))
8693
return -EINVAL;
8794

8895
phdr = (Elf32_Phdr *)(page_addr + sizeof(Elf32_Ehdr));
8996

90-
for (i = 0; i < ehdr->e_phnum; ++i) {
97+
for (i = 0; i < phnum; ++i) {
9198
if (phdr[i].p_type == PT_NOTE &&
9299
!parse_build_id(page_addr, build_id, size,
93-
page_addr + phdr[i].p_offset,
94-
phdr[i].p_filesz))
100+
page_addr + READ_ONCE(phdr[i].p_offset),
101+
READ_ONCE(phdr[i].p_filesz)))
95102
return 0;
96103
}
97104
return -EINVAL;
@@ -103,7 +110,7 @@ static int get_build_id_64(const void *page_addr, unsigned char *build_id,
103110
{
104111
Elf64_Ehdr *ehdr = (Elf64_Ehdr *)page_addr;
105112
Elf64_Phdr *phdr;
106-
int i;
113+
__u32 i, phnum;
107114

108115
/*
109116
* FIXME
@@ -112,18 +119,19 @@ static int get_build_id_64(const void *page_addr, unsigned char *build_id,
112119
*/
113120
if (ehdr->e_phoff != sizeof(Elf64_Ehdr))
114121
return -EINVAL;
122+
123+
phnum = READ_ONCE(ehdr->e_phnum);
115124
/* only supports phdr that fits in one page */
116-
if (ehdr->e_phnum >
117-
(PAGE_SIZE - sizeof(Elf64_Ehdr)) / sizeof(Elf64_Phdr))
125+
if (phnum > (PAGE_SIZE - sizeof(Elf64_Ehdr)) / sizeof(Elf64_Phdr))
118126
return -EINVAL;
119127

120128
phdr = (Elf64_Phdr *)(page_addr + sizeof(Elf64_Ehdr));
121129

122-
for (i = 0; i < ehdr->e_phnum; ++i) {
130+
for (i = 0; i < phnum; ++i) {
123131
if (phdr[i].p_type == PT_NOTE &&
124132
!parse_build_id(page_addr, build_id, size,
125-
page_addr + phdr[i].p_offset,
126-
phdr[i].p_filesz))
133+
page_addr + READ_ONCE(phdr[i].p_offset),
134+
READ_ONCE(phdr[i].p_filesz)))
127135
return 0;
128136
}
129137
return -EINVAL;
@@ -152,6 +160,10 @@ int build_id_parse(struct vm_area_struct *vma, unsigned char *build_id,
152160
page = find_get_page(vma->vm_file->f_mapping, 0);
153161
if (!page)
154162
return -EFAULT; /* page not mapped */
163+
if (!PageUptodate(page)) {
164+
put_page(page);
165+
return -EFAULT;
166+
}
155167

156168
ret = -EINVAL;
157169
page_addr = kmap_local_page(page);

0 commit comments

Comments
 (0)