diff --git a/src/p_elf_enum.h b/src/p_elf_enum.h index bddd433f..ab7810f4 100644 --- a/src/p_elf_enum.h +++ b/src/p_elf_enum.h @@ -126,6 +126,7 @@ SHT_GROUP = 17, /* Section group */ SHT_SYMTAB_SHNDX = 18, /* Extended section indeces */ SHT_GNU_LIBLIST = 0x6ffffff7 /* Prelink library list */ + , SHT_GNU_HASH = 0x6ffffff6 /* GNU-style hash table. */ , SHT_LOOS = 0x60000000 /* LOcal OS; SHT_ANDROID_REL{,A} is +1, +2 */ , SHT_LOPROC = 0x70000000/* Start of processor-specific */ @@ -174,12 +175,14 @@ DT_FINI_ARRAYSZ= 28, /* size in bytes */ DT_PREINIT_ARRAY = 32, /* Array with addresses of preinit fct*/ DT_PREINIT_ARRAYSZ= 33, /* size in bytes */ + DT_NUM = 35, /* end of easy range */ + DT_CHECKSUM = 0x6ffffdf8, /* Only for prelink? */ DT_GNU_HASH = 0x6ffffef5, /* GNU-style hash table */ DT_VERSYM = 0x6ffffff0, /* version[] for each symbol */ DT_FLAGS_1 = 0x6ffffffb, /* DF_1_* */ DT_VERDEF = 0x6ffffffc, /* version definitions[] */ - DT_VERNEEDED= 0x6ffffffe /* version[] needed */ + DT_VERNEED = 0x6ffffffe /* version[] needed */ }; enum { // DT_FLAGS_1 DF_1_NOW = 0x00000001, /* Set RTLD_NOW for this object. */ diff --git a/src/p_lx_elf.cpp b/src/p_lx_elf.cpp index b45af844..a3099e9e 100644 --- a/src/p_lx_elf.cpp +++ b/src/p_lx_elf.cpp @@ -713,7 +713,8 @@ PackLinuxElf32::PackLinuxElf32(InputFile *f) : super(f), phdri(nullptr), shdri(nullptr), gnu_stack(nullptr), page_mask(~0u< b) return 1; + return 0; +} + void PackLinuxElf64::invert_pt_dynamic(Elf64_Dyn const *dynp, upx_uint64_t headway) { @@ -5675,9 +5687,48 @@ PackLinuxElf64::invert_pt_dynamic(Elf64_Dyn const *dynp, upx_uint64_t headway) throwCantPack("bad DT_SYMTAB"); } } - // DT_HASH often ends at DT_SYMTAB - // FIXME: sort DT_HASH, DT_GNU_HASH, STRTAB, SYMTAB, REL, RELA, JMPREL - // to partition the space. + // DT_HASH, DT_GNU_HASH have no explicit length (except in ElfXX_Shdr), + // so it is hard to detect when the index of a hash chain is out-of-bounds. + // Workaround: Assume no overlap of DT_* tables. Then any given table + // ends when another table begins. So find the tables, and sort the offsets. + unsigned const dt_names[] = { // *.d_val are often in this order + Elf64_Dyn::DT_SYMTAB, + Elf64_Dyn::DT_VERSYM, + Elf64_Dyn::DT_VERNEED, + Elf64_Dyn::DT_HASH, + Elf64_Dyn::DT_GNU_HASH, + Elf64_Dyn::DT_STRTAB, + Elf64_Dyn::DT_VERDEF, + Elf64_Dyn::DT_REL, + Elf64_Dyn::DT_RELA, + Elf64_Dyn::DT_INIT, + Elf64_Dyn::DT_INIT_ARRAY, + 0, + }; + unsigned dt_offsets[sizeof(dt_names)/sizeof(dt_names[0])]; + unsigned n_off = 0, k; + for (unsigned j=0; (k = dt_names[j]); ++j) { + dt_offsets[n_off] = 0; // default to "not found" + if (k < DT_NUM) { // in range of easy table + if (dt_table[k]) { // present now + dt_offsets[n_off] = get_te64(&dynp0[-1+ dt_table[k]].d_val); + } + } + else { + if (file_image) { // why is this guard necessary? + dt_offsets[n_off] = elf_unsigned_dynamic(k); // zero if not found + } + } + if (file_size <= dt_offsets[n_off]) { + char msg[60]; snprintf(msg, sizeof(msg), "bad DT_{%#x} = %#x (beyond EOF)", + dt_names[k], dt_offsets[n_off]); + throwCantPack(msg); + } + n_off += !!dt_offsets[n_off]; + } + dt_offsets[n_off++] = file_size; // sentinel + qsort(dt_offsets, n_off, sizeof(dt_offsets[0]), qcmp_unsigned); + unsigned const v_hsh = elf_unsigned_dynamic(Elf64_Dyn::DT_HASH); if (v_hsh && file_image) { hashtab = (unsigned const *)elf_find_dynamic(Elf64_Dyn::DT_HASH); @@ -5686,6 +5737,15 @@ PackLinuxElf64::invert_pt_dynamic(Elf64_Dyn const *dynp, upx_uint64_t headway) "bad DT_HASH %#x", v_hsh); throwCantPack(msg); } + for (unsigned j = 0; j < n_off; ++j) { + if (v_hsh == dt_offsets[j]) { + if (dt_offsets[1+ j]) { + hashend = (unsigned const *)((dt_offsets[1+ j] - dt_offsets[j]) + + (char const *)hashtab); + } + break; + } + } unsigned const nbucket = get_te32(&hashtab[0]); unsigned const *const buckets = &hashtab[2]; unsigned const *const chains = &buckets[nbucket]; (void)chains; @@ -5724,6 +5784,15 @@ PackLinuxElf64::invert_pt_dynamic(Elf64_Dyn const *dynp, upx_uint64_t headway) "bad DT_GNU_HASH %#x", v_gsh); throwCantPack(msg); } + for (unsigned j = 0; j < n_off; ++j) { // linear search of short table + if (v_gsh == dt_offsets[j]) { + if (dt_offsets[1+ j]) { + gashend = (unsigned const *)((dt_offsets[1+ j] - dt_offsets[j]) + + (char const *)gashtab); + } + break; + } + } unsigned const n_bucket = get_te32(&gashtab[0]); unsigned const symbias = get_te32(&gashtab[1]); unsigned const n_bitmask = get_te32(&gashtab[2]); @@ -5737,7 +5806,8 @@ PackLinuxElf64::invert_pt_dynamic(Elf64_Dyn const *dynp, upx_uint64_t headway) "bad n_bucket %#x\n", n_bucket); throwCantPack(msg); } - //unsigned const *const gashend = &hasharr[n_bucket]; // minimum, except: + // unsigned const *const gashend = &hasharr[n_bucket]; + // minimum, except: // Rust and Android trim unused zeroes from high end of hasharr[] unsigned bmax = 0; for (unsigned j= 0; j < n_bucket; ++j) { @@ -5767,6 +5837,31 @@ PackLinuxElf64::invert_pt_dynamic(Elf64_Dyn const *dynp, upx_uint64_t headway) } bmax -= symbias; + // preliminary bound on gashend + Elf64_Shdr const *sec_gash = elf_find_section_type(Elf64_Shdr::SHT_GNU_HASH); + unsigned const off_symtab = elf_unsigned_dynamic(Elf64_Dyn::DT_SYMTAB); + unsigned const off_strtab = elf_unsigned_dynamic(Elf64_Dyn::DT_STRTAB); + unsigned const off_gshtab = elf_unsigned_dynamic(Elf64_Dyn::DT_GNU_HASH); + if (off_gshtab < file_size // paranoia + && off_strtab < file_size + && off_symtab < file_size ) { + unsigned sz_gshtab = 0; + if (sec_gash && off_gshtab == get_te32(&sec_gash->sh_offset)) { + sz_gshtab = get_te32(&sec_gash->sh_size); + } + else { // heuristics + if (off_gshtab < off_strtab) { + sz_gshtab = off_strtab - off_gshtab; + } + else if (off_gshtab < off_symtab) { + sz_gshtab = off_symtab - off_gshtab; + } + } + if (sz_gshtab <= (file_size - off_gshtab)) { + gashend = (unsigned const *)(sz_gshtab + (char const *)gashtab); + } + } + upx_uint64_t const v_sym = !x_sym ? 0 : get_te64(&dynp0[-1+ x_sym].d_val); unsigned r = 0; if (!n_bucket || !n_bitmask || !v_sym @@ -5982,25 +6077,27 @@ Elf64_Sym const *PackLinuxElf64::elf_lookup(char const *name) const unsigned const hbit2 = 077& (h>>gnu_shift); upx_uint64_t const w = get_te64(&bitmask[(n_bitmask -1) & (h>>6)]); if (1& (w>>hbit1) & (w>>hbit2)) { - unsigned bucket = get_te32(&buckets[h % n_bucket]); - if (n_bucket <= bucket) { - char msg[90]; snprintf(msg, sizeof(msg), - "bad DT_GNU_HASH n_bucket{%#x} <= buckets[%d]{%#x}\n", - n_bucket, h % n_bucket, bucket); - throwCantPack(msg); - } - if (0!=bucket) { - Elf64_Sym const *dsp = &dynsym[bucket]; - unsigned const *hp = &hasharr[bucket - symbias]; - do if (0==((h ^ get_te32(hp))>>1)) { - unsigned st_name = get_te32(&dsp->st_name); - char const *const p = get_str_name(st_name, (unsigned)-1); - if (0==strcmp(name, p)) { - return dsp; + unsigned hhead = get_te32(&buckets[h % n_bucket]); + if (hhead) { + Elf64_Sym const *dsp = &dynsym[hhead]; + unsigned const *hp = &hasharr[hhead - symbias]; + unsigned k; + do { + if (gashend <= hp) { + char msg[120]; snprintf(msg, sizeof(msg), + "bad gnu_hash[%#zx] head=%u", + hp - hasharr, hhead); + throwCantPack(msg); } - } while (++dsp, - (char const *)hp < (char const *)&file_image[file_size] - && 0==(1u& get_te32(hp++))); + k = get_te32(hp); + if (0==((h ^ k)>>1)) { + unsigned const st_name = get_te32(&dsp->st_name); + char const *const p = get_str_name(st_name, (unsigned)-1); + if (0==strcmp(name, p)) { + return dsp; + } + } + } while (++dsp, ++hp, 0==(1u& k)); } } } diff --git a/src/p_lx_elf.h b/src/p_lx_elf.h index 4bbcc114..87d6e068 100644 --- a/src/p_lx_elf.h +++ b/src/p_lx_elf.h @@ -190,8 +190,8 @@ protected: unsigned page_mask; // AND clears the offset-within-page Elf32_Dyn const *dynseg; // from PT_DYNAMIC - unsigned int const *hashtab; // from DT_HASH - unsigned int const *gashtab; // from DT_GNU_HASH + unsigned int const *hashtab, *hashend; // from DT_HASH + unsigned int const *gashtab, *gashend; // from DT_GNU_HASH Elf32_Sym const *dynsym; // from DT_SYMTAB Elf32_Sym const *jni_onload_sym; @@ -342,8 +342,8 @@ protected: upx_uint64_t page_mask; // AND clears the offset-within-page Elf64_Dyn const *dynseg; // from PT_DYNAMIC - unsigned int const *hashtab; // from DT_HASH - unsigned int const *gashtab; // from DT_GNU_HASH + unsigned int const *hashtab, *hashend; // from DT_HASH + unsigned int const *gashtab, *gashend; // from DT_GNU_HASH Elf64_Sym const *dynsym; // from DT_SYMTAB Elf64_Sym const *jni_onload_sym;