aboutsummaryrefslogtreecommitdiff
path: root/toys/pending
diff options
context:
space:
mode:
authorElliott Hughes <enh@google.com>2020-11-12 15:58:40 -0800
committerRob Landley <rob@landley.net>2020-11-12 19:02:19 -0600
commit2ee4b7207e9dbfd385740d5f142dfb95b0a621ed (patch)
tree68569662f6790ce0bd17d5b7d5142ce436f8ffee /toys/pending
parent0c567294d476ae6cab863e774844cd94e51b88e2 (diff)
downloadtoybox-2ee4b7207e9dbfd385740d5f142dfb95b0a621ed.tar.gz
readelf: harden against invalid input.
I also promised to fix readelf. Where in file(1) I made no attempt to say what was bad (or even to change `goto bad` to explicitly say that *anything* was bad), I believe that readelf is much more likely to be shown invalid ELF files, and that it would be useful to have some clue as to what's wrong. Relatedly, this patch removes all existing error_exit() calls in case it's being used on multiple files. Again, this survived ~24hrs of AFL++ trying to blow its house down. Test: ~/AFLplusplus/afl-fuzz -i tests/files/elf -o fuzz-out -- ./readelf -a @@
Diffstat (limited to 'toys/pending')
-rw-r--r--toys/pending/readelf.c203
1 files changed, 119 insertions, 84 deletions
diff --git a/toys/pending/readelf.c b/toys/pending/readelf.c
index 5cd148c1..a8564a4d 100644
--- a/toys/pending/readelf.c
+++ b/toys/pending/readelf.c
@@ -35,7 +35,7 @@ GLOBALS(
char *x, *p;
char *elf, *shstrtab, *f;
- unsigned long long shoff, phoff, size;
+ unsigned long long shoff, phoff, size, shstrtabsz;
int bits, endian, shnum, shentsize, phentsize;
)
@@ -75,13 +75,14 @@ static unsigned short elf_short(char **p)
return elf_get(p, 2);
}
-static void get_sh(int i, struct sh *s)
+static int get_sh(unsigned i, struct sh *s)
{
char *shdr = TT.elf+TT.shoff+i*TT.shentsize;
- int name_offset;
+ unsigned name_offset;
if (i >= TT.shnum || shdr > TT.elf+TT.size-TT.shentsize) {
- error_exit("%s: no shdr %d",TT.f,i);
+ printf("No shdr %d\n", i);
+ return 0;
}
name_offset = elf_int(&shdr);
@@ -95,14 +96,21 @@ static void get_sh(int i, struct sh *s)
s->addralign = elf_long(&shdr);
s->entsize = elf_long(&shdr);
+ if (s->offset>TT.size || s->size>TT.size || s->offset>TT.size-s->size) {
+ printf("Bad offset/size %llu/%llu for sh %d\n", s->offset, s->size, i);
+ return 0;
+ }
+
if (!TT.shstrtab) s->name = "?";
else {
s->name = TT.shstrtab + name_offset;
- if (s->name >= TT.elf+TT.size) error_exit("%s: shdr %d bad name", TT.f, i);
- if (s->offset >= TT.size-s->size && s->type != 8 /*SHT_NOBITS*/)
- error_exit("%s: shdr %d has bad offset/size %llu/%llu", TT.f, i,
- s->offset, s->size);
+ if (name_offset > TT.shstrtabsz || s->name >= TT.elf+TT.size) {
+ printf("Bad name for sh %d\n", i);
+ return 0;
+ }
}
+
+ return 1;
}
static int find_section(char *spec, struct sh *s)
@@ -113,26 +121,25 @@ static int find_section(char *spec, struct sh *s)
// Valid section number?
errno = 0;
i = strtoul(spec, &end, 0);
- if (!errno && !*end && i < TT.shnum) {
- get_sh(i, s);
- return 1;
- }
+ if (!errno && !*end && i < TT.shnum) return get_sh(i, s);
// Search the section names.
for (i=0; i<TT.shnum; i++) {
- get_sh(i, s);
- if (!strcmp(s->name, spec)) return 1;
+ if (get_sh(i, s) && !strcmp(s->name, spec)) return 1;
}
error_msg("%s: no section '%s", TT.f, spec);
return 0;
}
-static void get_ph(int i, struct ph *ph)
+static int get_ph(int i, struct ph *ph)
{
char *phdr = TT.elf+TT.phoff+i*TT.phentsize;
- if (phdr > TT.elf+TT.size-TT.phentsize) error_exit("%s: no phdr %d",TT.f,i);
+ if (phdr > TT.elf+TT.size-TT.phentsize) {
+ printf("Bad phdr %d\n", i);
+ return 0;
+ }
// Elf64_Phdr reordered fields.
ph->type = elf_int(&phdr);
@@ -154,9 +161,12 @@ static void get_ph(int i, struct ph *ph)
ph->align = elf_int(&phdr);
}
- if (ph->offset >= TT.size-ph->filesz)
- error_exit("%s: phdr %d has bad offset/size %llu/%llu", TT.f, i,
- ph->offset, ph->filesz);
+ if (ph->offset >= TT.size-ph->filesz) {
+ printf("phdr %d has bad offset/size %llu/%llu", i, ph->offset, ph->filesz);
+ return 0;
+ }
+
+ return 1;
}
#define MAP(...) __VA_ARGS__
@@ -238,9 +248,9 @@ static void show_symbols(struct sh *table, struct sh *strtab)
" Num: %*s Size Type Bind Vis Ndx Name\n",
table->name, numsym, 5+8*TT.bits, "Value");
for (i=0; i<numsym; i++) {
- int st_name = elf_int(&symtab), st_value, st_shndx;
+ unsigned st_name = elf_int(&symtab), st_value, st_shndx;
unsigned char st_info, st_other;
- long st_size;
+ unsigned long st_size;
char *name;
// The various fields were moved around for 64-bit.
@@ -259,7 +269,7 @@ static void show_symbols(struct sh *table, struct sh *strtab)
}
name = TT.elf + strtab->offset + st_name;
- if (name >= TT.elf+TT.size) error_exit("%s: bad symbol name", TT.f);
+ if (name >= TT.elf+TT.size) name = "???";
if (!st_shndx) ndx = "UND";
else if (st_shndx==0xfff1) ndx = "ABS";
@@ -267,7 +277,7 @@ static void show_symbols(struct sh *table, struct sh *strtab)
// TODO: look up and show any symbol versions with @ or @@.
- printf("%6d: %0*x %5ld %-7s %-6s %-9s%3s %s\n", i, 8*(TT.bits+1),
+ printf("%6d: %0*x %5lu %-7s %-6s %-9s%3s %s\n", i, 8*(TT.bits+1),
st_value, st_size, stt_type(st_info & 0xf), stb_type(st_info >> 4),
stv_type(st_other & 3), ndx, name);
}
@@ -280,15 +290,24 @@ static int notematch(int namesz, char **p, char *expected, int len)
return 1;
}
-static void show_notes(long offset, long size)
+static void show_notes(unsigned long offset, unsigned long size)
{
char *note = TT.elf + offset;
+ if (size > TT.size || offset > TT.size-size) {
+ printf("Bad note bounds %lu/%lu\n", offset, size);
+ return;
+ }
+
printf(" %-20s %10s\tDescription\n", "Owner", "Data size");
while (note < TT.elf+offset+size) {
char *p = note, *desc;
- int namesz = elf_int(&p), descsz = elf_int(&p), type = elf_int(&p), j = 0;
+ unsigned namesz=elf_int(&p), descsz=elf_int(&p), type=elf_int(&p), j=0;
+ if (namesz > size || descsz > size) {
+ error_msg("%s: bad note @%lu", TT.f, offset);
+ return;
+ }
printf(" %-20.*s 0x%08x\t", namesz, p, descsz);
if (notematch(namesz, &p, "GNU", 4)) {
if (type == 1) {
@@ -356,18 +375,6 @@ static void scan_elf()
TT.shnum = elf_short(&hdr);
shstrndx = elf_short(&hdr);
- // Set up the section header string table so we can use section header names.
- // Core files have shstrndx == 0.
- TT.shstrtab = 0;
- if (shstrndx != 0) {
- get_sh(shstrndx, &shstr);
- if (shstr.type != 3 /*SHT_STRTAB*/) {
- error_msg("%s: bad shstrndx", TT.f);
- return;
- }
- TT.shstrtab = TT.elf+shstr.offset;
- }
-
if (toys.optc > 1) printf("\nFile: %s\n", TT.f);
if (FLAG(h)) {
@@ -384,9 +391,9 @@ static void scan_elf()
printf(" Machine: %s\n", elf_arch_name(machine));
printf(" Version: 0x%x\n", version);
printf(" Entry point address: 0x%x\n", entry);
- printf(" Start of program headers: %lld (bytes into file)\n",
+ printf(" Start of program headers: %llu (bytes into file)\n",
TT.phoff);
- printf(" Start of section headers: %lld (bytes into file)\n",
+ printf(" Start of section headers: %llu (bytes into file)\n",
TT.shoff);
printf(" Flags: 0x%x\n", flags);
printf(" Size of this header: %d (bytes)\n", ehsize);
@@ -396,6 +403,27 @@ static void scan_elf()
printf(" Number of section headers: %d\n", TT.shnum);
printf(" Section header string table index: %d\n", shstrndx);
}
+ if (TT.phoff > TT.size) {
+ error_msg("%s: bad phoff", TT.f);
+ return;
+ }
+ if (TT.shoff > TT.size) {
+ error_msg("%s: bad shoff", TT.f);
+ return;
+ }
+
+ // Set up the section header string table so we can use section header names.
+ // Core files have shstrndx == 0.
+ TT.shstrtab = 0;
+ TT.shstrtabsz = 0;
+ if (shstrndx != 0) {
+ if (!get_sh(shstrndx, &shstr) || shstr.type != 3 /*SHT_STRTAB*/) {
+ error_msg("%s: bad shstrndx", TT.f);
+ return;
+ }
+ TT.shstrtab = TT.elf+shstr.offset;
+ TT.shstrtabsz = shstr.size;
+ }
w = 8*(TT.bits+1);
if (FLAG(S)) {
@@ -414,7 +442,7 @@ static void scan_elf()
// We need to iterate through the section headers even if we're not
// dumping them, to find specific sections.
for (i=0; i<TT.shnum; i++) {
- get_sh(i, &s);
+ if (!get_sh(i, &s)) continue;
if (s.type == 2 /*SHT_SYMTAB*/) symtab = s;
else if (s.type == 6 /*SHT_DYNAMIC*/) dynamic = s;
else if (s.type == 11 /*SHT_DYNSYM*/) dynsym = s;
@@ -453,12 +481,13 @@ static void scan_elf()
" %-14s %-8s %-*s %-*s %-7s %-7s Flg Align\n", "Type",
"Offset", w, "VirtAddr", w, "PhysAddr", "FileSiz", "MemSiz");
for (i=0; i<phnum; i++) {
- get_ph(i, &ph);
+ if (!get_ph(i, &ph)) continue;
printf(" %-14s 0x%06llx 0x%0*llx 0x%0*llx 0x%05llx 0x%05llx %c%c%c %#llx\n",
ph_type(ph.type), ph.offset, w, ph.vaddr, w, ph.paddr,
ph.filesz, ph.memsz, ph.flags&4?'R':' ', ph.flags&2?'W':' ',
ph.flags&1?'E':' ', ph.align);
- if (ph.type == 3 /*PH_INTERP*/ && ph.filesz - 1 < TT.size - ph.offset) {
+ if (ph.type == 3 /*PH_INTERP*/ && ph.filesz<TT.size &&
+ ph.offset<TT.size && ph.filesz - 1 < TT.size - ph.offset) {
printf(" [Requesting program interpreter: %*s]\n",
(int) ph.filesz-1, TT.elf+ph.offset);
}
@@ -468,10 +497,10 @@ static void scan_elf()
" Section to Segment mapping:\n"
" Segment Sections...\n");
for (i=0; i<phnum; i++) {
- get_ph(i, &ph);
+ if (!get_ph(i, &ph)) continue;
printf(" %02d ", i);
for (j=0; j<TT.shnum; j++) {
- get_sh(j, &s);
+ if (!get_sh(j, &s)) continue;
if (!*s.name) continue;
if (s.offset >= ph.offset && s.offset+s.size <= ph.offset+ph.filesz)
printf("%s ", s.name);
@@ -488,45 +517,51 @@ static void scan_elf()
xputc('\n');
if (!dynamic.size) printf("There is no dynamic section in this file.\n");
- else printf("Dynamic section at offset 0x%llx contains %lld entries:\n"
- " %-*s %-20s %s\n",
- dynamic.offset, dynamic.size/dynamic.entsize,
- w+2, "Tag", "Type", "Name/Value");
- while (dyn < end) {
- unsigned long long tag = elf_long(&dyn), val = elf_long(&dyn);
- char *type = dt_type(tag);
-
- printf(" 0x%0*llx %-20s ", w, tag, *type=='0' ? type : type+1);
- if (*type == 'd') printf("%lld\n", val);
- else if (*type == 'b') printf("%lld (bytes)\n", val);
- else if (*type == 's') printf("%s\n", TT.elf+dynstr.offset+val);
- else if (*type == 'f' || *type == 'F') {
- struct bitname { int bit; char *s; }
- df_names[] = {{0, "ORIGIN"},{1,"SYMBOLIC"},{2,"TEXTREL"},
- {3,"BIND_NOW"},{4,"STATIC_TLS"},{}},
- df_1_names[]={{0,"NOW"},{1,"GLOBAL"},{2,"GROUP"},{3,"NODELETE"},
- {5,"INITFIRST"},{27,"PIE"},{}},
- *names = *type == 'f' ? df_names : df_1_names;
- int mask;
-
- if (*type == 'F') printf("Flags: ");
- for (j=0; names[j].s; j++) {
- if (val & (mask=(1<<names[j].bit))) {
- printf("%s%s", names[j].s, (val &= ~mask) ? " " : "");
+ else if (!dynamic.entsize) printf("Bad dynamic entry size 0!\n");
+ else {
+ printf("Dynamic section at offset 0x%llx contains %lld entries:\n"
+ " %-*s %-20s %s\n",
+ dynamic.offset, dynamic.size/dynamic.entsize,
+ w+2, "Tag", "Type", "Name/Value");
+ while (dyn < end) {
+ unsigned long long tag = elf_long(&dyn), val = elf_long(&dyn);
+ char *type = dt_type(tag);
+
+ printf(" 0x%0*llx %-20s ", w, tag, *type=='0' ? type : type+1);
+ if (*type == 'd') printf("%lld\n", val);
+ else if (*type == 'b') printf("%lld (bytes)\n", val);
+ else if (*type == 's') printf("%s\n", TT.elf+dynstr.offset+val);
+ else if (*type == 'f' || *type == 'F') {
+ struct bitname { int bit; char *s; }
+ df_names[] = {{0, "ORIGIN"},{1,"SYMBOLIC"},{2,"TEXTREL"},
+ {3,"BIND_NOW"},{4,"STATIC_TLS"},{}},
+ df_1_names[]={{0,"NOW"},{1,"GLOBAL"},{2,"GROUP"},{3,"NODELETE"},
+ {5,"INITFIRST"},{27,"PIE"},{}},
+ *names = *type == 'f' ? df_names : df_1_names;
+ int mask;
+
+ if (*type == 'F') printf("Flags: ");
+ for (j=0; names[j].s; j++) {
+ if (val & (mask=(1<<names[j].bit))) {
+ printf("%s%s", names[j].s, (val &= ~mask) ? " " : "");
+ }
}
- }
- if (val) printf("0x%llx", val);
- xputc('\n');
- } else if (*type == 'N' || *type == 'R' || *type == 'S') {
- printf("%s: [%s]\n", *type=='N' ? "Shared library" :
- (*type=='R' ? "Library runpath" : "Library soname"),
- TT.elf+dynstr.offset+val);
- } else if (*type == 'P') {
- type = dt_type(val);
- j = strlen(type);
- if (*type != '0') type += 2, j -= 3;
- printf("%*.*s\n", j, j, type);
- } else printf("0x%llx\n", val);
+ if (val) printf("0x%llx", val);
+ xputc('\n');
+ } else if (*type == 'N' || *type == 'R' || *type == 'S') {
+ char *s = TT.elf+dynstr.offset+val;
+
+ if (dynstr.offset>TT.size || val>TT.size || dynstr.offset>TT.size-val)
+ s = "???";
+ printf("%s: [%s]\n", *type=='N' ? "Shared library" :
+ (*type=='R' ? "Library runpath" : "Library soname"), s);
+ } else if (*type == 'P') {
+ type = dt_type(val);
+ j = strlen(type);
+ if (*type != '0') type += 2, j -= 3;
+ printf("%*.*s\n", j, j, type);
+ } else printf("0x%llx\n", val);
+ }
}
}
@@ -537,7 +572,7 @@ static void scan_elf()
int found = 0;
for (i=0; i<TT.shnum; i++) {
- get_sh(i, &s);
+ if (!get_sh(i, &s)) continue;
if (s.type == 7 /*SHT_NOTE*/) {
printf("\nDisplaying notes found in: %s\n", s.name);
show_notes(s.offset, s.size);
@@ -545,7 +580,7 @@ static void scan_elf()
}
}
for (i=0; !found && i<phnum; i++) {
- get_ph(i, &ph);
+ if (!get_ph(i, &ph)) continue;
if (ph.type == 4 /*PT_NOTE*/) {
printf("\n"
"Displaying notes found at file offset 0x%llx with length 0x%llx:\n",