lib/buildid: harden build ID parsing logic
authorAndrii Nakryiko <andrii@kernel.org>
Thu, 29 Aug 2024 17:42:23 +0000 (10:42 -0700)
committerAlexei Starovoitov <ast@kernel.org>
Wed, 11 Sep 2024 16:58:30 +0000 (09:58 -0700)
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: bd7525dacd7e ("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>
lib/buildid.c

index e02b550..26007cc 100644 (file)
@@ -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;
-
-       while (note_offs + sizeof(Elf32_Nhdr) < note_size) {
-               Elf32_Nhdr *nhdr = (Elf32_Nhdr *)(note_start + note_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_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);