libdrgn: handle reading data from SHT_NOBITS sections

Peilin Ye reported a couple of related crashes in drgn caused by Linux
kernel modules which had been processed with objcopy --only-keep-debug
(although he notes that since binutils-gdb commit 8c803a2dd7d3
("elf_backend_section_flags and _bfd_elf_init_private_section_data") (in
binutils v2.35), objcopy --only-keep-debug doesn't seem to work for
kernel modules).

If given an SHT_NOBITS section, elf_getdata() returns an Elf_Data with
d_buf = NULL and d_size set to the size in the section header, which is
often non-zero. There are a few places where this can cause us to
dereference a NULL pointer:

* In relocate_elf_sections() for the relocated section data.
* In relocate_elf_sections() for the symbol table section data.
* In get_kernel_module_name_from_modinfo().
* In get_kernel_module_name_from_this_module().

Fix it by checking the section type or directly checking Elf_Data::d_buf
everywhere that could potentially get an SHT_NOBITS section. This is
based on a PR from Peilin Ye.

Closes #145.

Reported-by: Peilin Ye <peilin.ye@bytedance.com>
Signed-off-by: Omar Sandoval <osandov@osandov.com>
This commit is contained in:
Omar Sandoval 2022-01-26 17:51:21 -08:00
parent 8e8e3a4f57
commit 929b7de266
3 changed files with 39 additions and 10 deletions

View File

@ -1628,17 +1628,18 @@ static struct drgn_error *relocate_elf_file(Elf *elf)
Elf_Scn *reloc_scn = NULL;
while ((reloc_scn = elf_nextscn(elf, reloc_scn))) {
GElf_Shdr *shdr, shdr_mem;
shdr = gelf_getshdr(reloc_scn, &shdr_mem);
if (!shdr) {
GElf_Shdr *reloc_shdr, reloc_shdr_mem;
reloc_shdr = gelf_getshdr(reloc_scn, &reloc_shdr_mem);
if (!reloc_shdr) {
err = drgn_error_libelf();
goto out;
}
/* We don't support any architectures that use SHT_REL yet. */
if (shdr->sh_type != SHT_RELA)
if (reloc_shdr->sh_type != SHT_RELA)
continue;
const char *scnname = elf_strptr(elf, shstrndx, shdr->sh_name);
const char *scnname = elf_strptr(elf, shstrndx,
reloc_shdr->sh_name);
if (!scnname) {
err = drgn_error_libelf();
goto out;
@ -1646,17 +1647,36 @@ static struct drgn_error *relocate_elf_file(Elf *elf)
if (strstartswith(scnname, ".rela.debug_") ||
strstartswith(scnname, ".rela.orc_")) {
Elf_Scn *scn = elf_getscn(elf, shdr->sh_info);
Elf_Scn *scn = elf_getscn(elf, reloc_shdr->sh_info);
if (!scn) {
err = drgn_error_libelf();
goto out;
}
GElf_Shdr *shdr, shdr_mem;
shdr = gelf_getshdr(scn, &shdr_mem);
if (!shdr) {
err = drgn_error_libelf();
goto out;
}
if (shdr->sh_type == SHT_NOBITS)
continue;
Elf_Scn *symtab_scn = elf_getscn(elf, shdr->sh_link);
Elf_Scn *symtab_scn = elf_getscn(elf,
reloc_shdr->sh_link);
if (!symtab_scn) {
err = drgn_error_libelf();
goto out;
}
shdr = gelf_getshdr(symtab_scn, &shdr_mem);
if (!shdr) {
err = drgn_error_libelf();
goto out;
}
if (shdr->sh_type == SHT_NOBITS) {
err = drgn_error_create(DRGN_ERROR_OTHER,
"relocation symbol table has no data");
goto out;
}
Elf_Data *data, *reloc_data, *symtab_data;
if ((err = read_elf_section(scn, &data)) ||
@ -1681,8 +1701,8 @@ static struct drgn_error *relocate_elf_file(Elf *elf)
* Mark the relocation section as empty so that libdwfl
* doesn't try to apply it again.
*/
shdr->sh_size = 0;
if (!gelf_update_shdr(reloc_scn, shdr)) {
reloc_shdr->sh_size = 0;
if (!gelf_update_shdr(reloc_scn, reloc_shdr)) {
err = drgn_error_libelf();
goto out;
}

View File

@ -332,6 +332,10 @@ struct drgn_error *open_elf_file(const char *path, int *fd_ret, Elf **elf_ret);
struct drgn_error *find_elf_file(char **path_ret, int *fd_ret, Elf **elf_ret,
const char * const *path_formats, ...);
/*
* NB: if the section is SHT_NOBITS, this returns an Elf_Data with d_buf = NULL
* and d_size >= 0.
*/
struct drgn_error *read_elf_section(Elf_Scn *scn, Elf_Data **ret);
struct drgn_error *elf_address_range(Elf *elf, uint64_t bias,

View File

@ -933,6 +933,10 @@ get_kernel_module_name_from_modinfo(Elf_Scn *modinfo_scn, const char **ret)
err = read_elf_section(modinfo_scn, &data);
if (err)
return err;
if (!data->d_buf) {
/* Section is either SHT_NOBITS or empty. */
goto not_found;
}
p = data->d_buf;
end = p + data->d_size;
while (p < end) {
@ -946,6 +950,7 @@ get_kernel_module_name_from_modinfo(Elf_Scn *modinfo_scn, const char **ret)
p = nul + 1;
}
}
not_found:
*ret = NULL;
return NULL;
}
@ -967,7 +972,7 @@ get_kernel_module_name_from_this_module(Elf_Scn *this_module_scn,
err = read_elf_section(this_module_scn, &data);
if (err)
return err;
if (name_offset < data->d_size) {
if (data->d_buf && name_offset < data->d_size) {
p = data->d_buf + name_offset;
nul = memchr(p, 0, data->d_size - name_offset);
if (nul && nul != p) {