diff options
Diffstat (limited to 'libdwfl/dwfl_segment_report_module.c')
-rw-r--r-- | libdwfl/dwfl_segment_report_module.c | 182 |
1 files changed, 131 insertions, 51 deletions
diff --git a/libdwfl/dwfl_segment_report_module.c b/libdwfl/dwfl_segment_report_module.c index ee9cfa2e..1461ae26 100644 --- a/libdwfl/dwfl_segment_report_module.c +++ b/libdwfl/dwfl_segment_report_module.c @@ -1,5 +1,6 @@ /* Sniff out modules from ELF headers visible in memory segments. Copyright (C) 2008-2012, 2014, 2015, 2018 Red Hat, Inc. + Copyright (C) 2021 Mark J. Wielaard <mark@klomp.org> This file is part of elfutils. This file is free software; you can redistribute it and/or modify @@ -294,6 +295,7 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, void *memory_callback_arg, Dwfl_Module_Callback *read_eagerly, void *read_eagerly_arg, + size_t maxread, const void *note_file, size_t note_file_size, const struct r_debug_info *r_debug_info) { @@ -331,6 +333,12 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, here so we can always safely free it. */ void *phdrsp = NULL; + /* Collect the build ID bits here. */ + struct elf_build_id build_id; + build_id.memory = NULL; + build_id.len = 0; + build_id.vaddr = 0; + if (! (*memory_callback) (dwfl, ndx, &buffer, &buffer_available, start, sizeof (Elf64_Ehdr), memory_callback_arg) || memcmp (buffer, ELFMAG, SELFMAG) != 0) @@ -366,6 +374,20 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, e_ident = ((const unsigned char *) buffer); ei_class = e_ident[EI_CLASS]; ei_data = e_ident[EI_DATA]; + /* buffer may be unaligned, in which case xlatetom would not work. + xlatetom does work when the in and out d_buf are equal (but not + for any other overlap). */ + size_t ehdr_align = (ei_class == ELFCLASS32 + ? __alignof__ (Elf32_Ehdr) + : __alignof__ (Elf64_Ehdr)); + if (((uintptr_t) buffer & (ehdr_align - 1)) != 0) + { + memcpy (&ehdr, buffer, + (ei_class == ELFCLASS32 + ? sizeof (Elf32_Ehdr) + : sizeof (Elf64_Ehdr))); + xlatefrom.d_buf = &ehdr; + } switch (ei_class) { case ELFCLASS32: @@ -383,7 +405,7 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, zero sh_size field. We ignore this here because getting shdrs is just a nice bonus (see below in the type == PT_LOAD case where we trim the last segment). */ - shdrs_end = ehdr.e32.e_shoff + ehdr.e32.e_shnum * ehdr.e32.e_shentsize; + shdrs_end = ehdr.e32.e_shoff + ehdr.e32.e_shnum * sizeof (Elf32_Shdr); break; case ELFCLASS64: @@ -397,7 +419,7 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, if (phentsize != sizeof (Elf64_Phdr)) goto out; /* See the NOTE above for shdrs_end and ehdr.e32.e_shnum. */ - shdrs_end = ehdr.e64.e_shoff + ehdr.e64.e_shnum * ehdr.e64.e_shentsize; + shdrs_end = ehdr.e64.e_shoff + ehdr.e64.e_shnum * sizeof (Elf64_Shdr); break; default: @@ -425,7 +447,12 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, buffer, otherwise it will be the size of the new buffer that could be read. */ if (ph_buffer_size != 0) - xlatefrom.d_size = ph_buffer_size; + { + phnum = ph_buffer_size / phentsize; + if (phnum == 0) + goto out; + xlatefrom.d_size = ph_buffer_size; + } xlatefrom.d_buf = ph_buffer; @@ -441,6 +468,18 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, xlateto.d_buf = phdrsp; xlateto.d_size = phdrsp_bytes; + /* ph_ buffer may be unaligned, in which case xlatetom would not work. + xlatetom does work when the in and out d_buf are equal (but not + for any other overlap). */ + size_t phdr_align = (class32 + ? __alignof__ (Elf32_Phdr) + : __alignof__ (Elf64_Phdr)); + if (((uintptr_t) ph_buffer & (phdr_align - 1)) != 0) + { + memcpy (phdrsp, ph_buffer, phdrsp_bytes); + xlatefrom.d_buf = phdrsp; + } + /* Track the bounds of the file visible in memory. */ GElf_Off file_trimmed_end = 0; /* Proper p_vaddr + p_filesz end. */ GElf_Off file_end = 0; /* Rounded up to effective page size. */ @@ -460,12 +499,6 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, GElf_Addr dyn_vaddr = 0; GElf_Xword dyn_filesz = 0; - /* Collect the build ID bits here. */ - struct elf_build_id build_id; - build_id.memory = NULL; - build_id.len = 0; - build_id.vaddr =0; - Elf32_Phdr *p32 = phdrsp; Elf64_Phdr *p64 = phdrsp; if ((ei_class == ELFCLASS32 @@ -514,15 +547,23 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, if (data_size != 0) filesz = data_size; + if (filesz > SIZE_MAX / sizeof (Elf32_Nhdr)) + continue; + assert (sizeof (Elf32_Nhdr) == sizeof (Elf64_Nhdr)); void *notes; - if (ei_data == MY_ELFDATA) + if (ei_data == MY_ELFDATA + && (uintptr_t) data == (align == 8 + ? NOTE_ALIGN8 ((uintptr_t) data) + : NOTE_ALIGN4 ((uintptr_t) data))) notes = data; else { const unsigned int xencoding = ehdr.e32.e_ident[EI_DATA]; + if (filesz > SIZE_MAX / sizeof (Elf32_Nhdr)) + continue; notes = malloc (filesz); if (unlikely (notes == NULL)) continue; /* Next header */ @@ -533,6 +574,18 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, xlatefrom.d_size = filesz; xlateto.d_buf = notes; xlateto.d_size = filesz; + + /* data may be unaligned, in which case xlatetom would not work. + xlatetom does work when the in and out d_buf are equal (but not + for any other overlap). */ + if ((uintptr_t) data != (align == 8 + ? NOTE_ALIGN8 ((uintptr_t) data) + : NOTE_ALIGN4 ((uintptr_t) data))) + { + memcpy (notes, data, filesz); + xlatefrom.d_buf = notes; + } + if (elf32_xlatetom (&xlateto, &xlatefrom, xencoding) == NULL) { free (notes); @@ -543,40 +596,48 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, const GElf_Nhdr *nh = notes; size_t len = 0; - while (filesz > len + sizeof (*nh)) + while (filesz - len > sizeof (*nh)) { - const void *note_name; - const void *note_desc; - - len += sizeof (*nh); - note_name = notes + len; - - len += nh->n_namesz; - len = align == 8 ? NOTE_ALIGN8 (len) : NOTE_ALIGN4 (len); - note_desc = notes + len; - - if (unlikely (filesz < len + nh->n_descsz)) - break; - - if (nh->n_type == NT_GNU_BUILD_ID - && nh->n_descsz > 0 - && nh->n_namesz == sizeof "GNU" - && !memcmp (note_name, "GNU", sizeof "GNU")) - { - build_id.vaddr = (note_desc + len += sizeof (*nh); + + size_t namesz = nh->n_namesz; + namesz = align == 8 ? NOTE_ALIGN8 (namesz) : NOTE_ALIGN4 (namesz); + if (namesz > filesz - len || len + namesz < namesz) + break; + + void *note_name = notes + len; + len += namesz; + + size_t descsz = nh->n_descsz; + descsz = align == 8 ? NOTE_ALIGN8 (descsz) : NOTE_ALIGN4 (descsz); + if (descsz > filesz - len || len + descsz < descsz) + break; + + void *note_desc = notes + len; + len += descsz; + + /* We don't handle very short or really large build-ids. We need at + at least 3 and allow for up to 64 (normally ids are 20 long). */ +#define MIN_BUILD_ID_BYTES 3 +#define MAX_BUILD_ID_BYTES 64 + if (nh->n_type == NT_GNU_BUILD_ID + && nh->n_descsz >= MIN_BUILD_ID_BYTES + && nh->n_descsz <= MAX_BUILD_ID_BYTES + && nh->n_namesz == sizeof "GNU" + && !memcmp (note_name, "GNU", sizeof "GNU")) + { + build_id.vaddr = (note_desc - (const void *) notes + note_vaddr); - build_id.len = nh->n_descsz; - build_id.memory = malloc (build_id.len); - if (likely (build_id.memory != NULL)) - memcpy (build_id.memory, note_desc, build_id.len); - break; - } - - len += nh->n_descsz; - len = align == 8 ? NOTE_ALIGN8 (len) : NOTE_ALIGN4 (len); - nh = (void *) notes + len; - } + build_id.len = nh->n_descsz; + build_id.memory = malloc (build_id.len); + if (likely (build_id.memory != NULL)) + memcpy (build_id.memory, note_desc, build_id.len); + break; + } + + nh = (void *) notes + len; + } if (notes != data) free (notes); @@ -641,10 +702,7 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, /* We must have seen the segment covering offset 0, or else the ELF header we read at START was not produced by these program headers. */ if (unlikely (!found_bias)) - { - free (build_id.memory); - goto out; - } + goto out; /* Now we know enough to report a module for sure: its bounds. */ module_start += bias; @@ -712,10 +770,7 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, } } if (skip_this_module) - { - free (build_id.memory); - goto out; - } + goto out; } const char *file_note_name = handle_file_note (module_start, module_end, @@ -774,6 +829,9 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, if (dyn_data_size != 0) dyn_filesz = dyn_data_size; + if ((dyn_filesz / dyn_entsize) == 0 + || dyn_filesz > (SIZE_MAX / dyn_entsize)) + goto out; void *dyns = malloc (dyn_filesz); Elf32_Dyn *d32 = dyns; Elf64_Dyn *d64 = dyns; @@ -786,7 +844,19 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, xlateto.d_buf = dyns; xlateto.d_size = dyn_filesz; + /* dyn_data may be unaligned, in which case xlatetom would not work. + xlatetom does work when the in and out d_buf are equal (but not + for any other overlap). */ bool is32 = (ei_class == ELFCLASS32); + size_t dyn_align = (is32 + ? __alignof__ (Elf32_Dyn) + : __alignof__ (Elf64_Dyn)); + if (((uintptr_t) dyn_data & (dyn_align - 1)) != 0) + { + memcpy (dyns, dyn_data, dyn_filesz); + xlatefrom.d_buf = dyns; + } + if ((is32 && elf32_xlatetom (&xlateto, &xlatefrom, ei_data) != NULL) || (!is32 && elf64_xlatetom (&xlateto, &xlatefrom, ei_data) != NULL)) { @@ -877,6 +947,7 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, /* At this point we do not need BUILD_ID or NAME any more. They have been copied. */ free (build_id.memory); + build_id.memory = NULL; finish_portion (&read_state, &soname, &soname_size); if (unlikely (mod == NULL)) @@ -904,6 +975,9 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, /* The caller wants to read the whole file in right now, but hasn't done it for us. Fill in a local image of the virtual file. */ + if (file_trimmed_end > maxread) + file_trimmed_end = maxread; + void *contents = calloc (1, file_trimmed_end); if (unlikely (contents == NULL)) goto out; @@ -924,8 +998,12 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, GElf_Off offset = is32 ? p32[i].p_offset : p64[i].p_offset; GElf_Xword filesz = is32 ? p32[i].p_filesz : p64[i].p_filesz; + /* Don't try to read beyond the actual end of file. */ + if (offset >= file_trimmed_end) + continue; + void *into = contents + offset; - size_t read_size = filesz; + size_t read_size = MIN (filesz, file_trimmed_end - offset); (*memory_callback) (dwfl, addr_segndx (dwfl, segment, vaddr + bias, false), &into, &read_size, vaddr + bias, read_size, @@ -959,7 +1037,7 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, elf->flags |= ELF_F_MALLOCED; } - if (elf != NULL) + if (elf != NULL && mod->main.elf == NULL) { /* Install the file in the module. */ mod->main.elf = elf; @@ -972,6 +1050,8 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, } out: + if (build_id.memory != NULL) + free (build_id.memory); free (phdrsp); if (buffer != NULL) (*memory_callback) (dwfl, -1, &buffer, &buffer_available, 0, 0, |