FIX PT_GNU_HASH when "empty"

... such as a main program with no exported globals
https://github.com/upx/upx/issues/834
	modified:   p_lx_elf.cpp
This commit is contained in:
John Reiser
2024-06-28 08:54:30 -07:00
parent 261609f706
commit 64a3de5a7a
+40 -89
View File
@@ -2100,10 +2100,8 @@ PackLinuxElf32::invert_pt_dynamic(Elf32_Dyn const *dynp, u32_t headway)
&& dt_table[d_tag] && dt_table[d_tag]
&& get_te32(&dynp->d_val) && get_te32(&dynp->d_val)
!= get_te32(&dynp0[-1+ dt_table[d_tag]].d_val)) { != get_te32(&dynp0[-1+ dt_table[d_tag]].d_val)) {
char msg[50]; snprintf(msg, sizeof(msg), throwCantPack("duplicate DT_%#x: [%#x] [%#x]",
"duplicate DT_%#x: [%#x] [%#x]",
(unsigned)d_tag, -1+ dt_table[d_tag], ndx); (unsigned)d_tag, -1+ dt_table[d_tag], ndx);
throwCantPack(msg);
} }
dt_table[d_tag] = 1+ ndx; dt_table[d_tag] = 1+ ndx;
} }
@@ -2132,9 +2130,7 @@ PackLinuxElf32::invert_pt_dynamic(Elf32_Dyn const *dynp, u32_t headway)
// last string in table must have terminating NUL // last string in table must have terminating NUL
|| '\0' != ((char *)file_image.getVoidPtr())[-1+ strtab_max + strtab_beg] || '\0' != ((char *)file_image.getVoidPtr())[-1+ strtab_max + strtab_beg]
) { ) {
char msg[50]; snprintf(msg, sizeof(msg), throwCantPack("bad DT_STRSZ %#x", strtab_max);
"bad DT_STRSZ %#x", strtab_max);
throwCantPack(msg);
} }
// Find end of DT_SYMTAB // Find end of DT_SYMTAB
@@ -2153,9 +2149,7 @@ PackLinuxElf32::invert_pt_dynamic(Elf32_Dyn const *dynp, u32_t headway)
if (v_hsh && file_image) { if (v_hsh && file_image) {
hashtab = (unsigned const *)elf_find_dynamic(Elf32_Dyn::DT_HASH); hashtab = (unsigned const *)elf_find_dynamic(Elf32_Dyn::DT_HASH);
if (!hashtab) { if (!hashtab) {
char msg[40]; snprintf(msg, sizeof(msg), throwCantPack("bad DT_HASH %#x", v_hsh);
"bad DT_HASH %#x", v_hsh);
throwCantPack(msg);
} }
// Find end of DT_HASH // Find end of DT_HASH
hashend = (unsigned const *)(void const *)(elf_find_table_size( hashend = (unsigned const *)(void const *)(elf_find_table_size(
@@ -2166,19 +2160,15 @@ PackLinuxElf32::invert_pt_dynamic(Elf32_Dyn const *dynp, u32_t headway)
unsigned const *const chains = &buckets[nbucket]; (void)chains; unsigned const *const chains = &buckets[nbucket]; (void)chains;
if ((unsigned)(file_size - ((char const *)buckets - (char const *)(void const *)file_image)) if ((unsigned)(file_size - ((char const *)buckets - (char const *)(void const *)file_image))
<= sizeof(unsigned)*nbucket ) { <= sizeof(unsigned)*nbucket ) {
char msg[80]; snprintf(msg, sizeof(msg), throwCantPack("bad nbucket %#x\n", nbucket);
"bad nbucket %#x\n", nbucket);
throwCantPack(msg);
} }
if ((unsigned)(hashend - buckets) < nbucket if ((unsigned)(hashend - buckets) < nbucket
|| !v_sym || (unsigned)file_size <= v_sym || !v_sym || (unsigned)file_size <= v_sym
|| ((v_hsh < v_sym) && (v_sym - v_hsh) < sizeof(*buckets)*(2+ nbucket)) || ((v_hsh < v_sym) && (v_sym - v_hsh) < sizeof(*buckets)*(2+ nbucket))
) { ) {
char msg[80]; snprintf(msg, sizeof(msg), throwCantPack("bad DT_HASH nbucket=%#x len=%#x",
"bad DT_HASH nbucket=%#x len=%#x",
nbucket, (v_sym - v_hsh)); nbucket, (v_sym - v_hsh));
throwCantPack(msg);
} }
unsigned chmax = 0; unsigned chmax = 0;
for (unsigned j= 0; j < nbucket; ++j) { for (unsigned j= 0; j < nbucket; ++j) {
@@ -2189,10 +2179,8 @@ PackLinuxElf32::invert_pt_dynamic(Elf32_Dyn const *dynp, u32_t headway)
} }
if ((v_hsh < v_sym) && (v_sym - v_hsh) < if ((v_hsh < v_sym) && (v_sym - v_hsh) <
(sizeof(*buckets)*(2+ nbucket) + sizeof(*chains)*(1+ chmax))) { (sizeof(*buckets)*(2+ nbucket) + sizeof(*chains)*(1+ chmax))) {
char msg[80]; snprintf(msg, sizeof(msg), throwCantPack("bad DT_HASH nbucket=%#x len=%#x",
"bad DT_HASH nbucket=%#x len=%#x",
nbucket, (v_sym - v_hsh)); nbucket, (v_sym - v_hsh));
throwCantPack(msg);
} }
} }
unsigned const v_gsh = elf_unsigned_dynamic(Elf32_Dyn::DT_GNU_HASH); unsigned const v_gsh = elf_unsigned_dynamic(Elf32_Dyn::DT_GNU_HASH);
@@ -2200,9 +2188,7 @@ PackLinuxElf32::invert_pt_dynamic(Elf32_Dyn const *dynp, u32_t headway)
// Not similar to DT_HASH because DT_GNU_HASH is not small (0x6ffffef5). // Not similar to DT_HASH because DT_GNU_HASH is not small (0x6ffffef5).
gashtab = (unsigned const *)elf_find_dynamic(Elf32_Dyn::DT_GNU_HASH); gashtab = (unsigned const *)elf_find_dynamic(Elf32_Dyn::DT_GNU_HASH);
if (!gashtab) { if (!gashtab) {
char msg[40]; snprintf(msg, sizeof(msg), throwCantPack("bad DT_GNU_HASH %#x", v_gsh);
"bad DT_GNU_HASH %#x", v_gsh);
throwCantPack(msg);
} }
gashend = (unsigned const *)(void const *)(elf_find_table_size( gashend = (unsigned const *)(void const *)(elf_find_table_size(
Elf32_Dyn::DT_GNU_HASH, Elf32_Shdr::SHT_GNU_HASH) + (char const *)gashtab); Elf32_Dyn::DT_GNU_HASH, Elf32_Shdr::SHT_GNU_HASH) + (char const *)gashtab);
@@ -2216,9 +2202,7 @@ PackLinuxElf32::invert_pt_dynamic(Elf32_Dyn const *dynp, u32_t headway)
if (!n_bucket || (1u<<31) <= n_bucket /* fie on fuzzers */ if (!n_bucket || (1u<<31) <= n_bucket /* fie on fuzzers */
|| (unsigned)(gashend - buckets) < n_bucket || (unsigned)(gashend - buckets) < n_bucket
|| (file_size + file_image) <= (void const *)hasharr) { || (file_size + file_image) <= (void const *)hasharr) {
char msg[80]; snprintf(msg, sizeof(msg), throwCantPack("bad n_bucket %#x\n", n_bucket);
"bad n_bucket %#x\n", n_bucket);
throwCantPack(msg);
} }
// It would be better to detect zeroes shifted into low 5 bits of: // It would be better to detect zeroes shifted into low 5 bits of:
// (037 & (hash_32 >> gnu_shift)) // (037 & (hash_32 >> gnu_shift))
@@ -2234,10 +2218,8 @@ PackLinuxElf32::invert_pt_dynamic(Elf32_Dyn const *dynp, u32_t headway)
unsigned bj = get_te32(&buckets[j]); unsigned bj = get_te32(&buckets[j]);
if (bj) { if (bj) {
if (bj < symbias) { if (bj < symbias) {
char msg[90]; snprintf(msg, sizeof(msg), throwCantPack("bad DT_GNU_HASH bucket[%d] < symbias{%#x}\n",
"bad DT_GNU_HASH bucket[%d] < symbias{%#x}\n", bj, symbias);
bj, symbias);
throwCantPack(msg);
} }
if (bmax < bj) { if (bmax < bj) {
bmax = bj; bmax = bj;
@@ -2250,12 +2232,13 @@ PackLinuxElf32::invert_pt_dynamic(Elf32_Dyn const *dynp, u32_t headway)
// But then the DT_GNU_HASH symbol lookup algorithm always fails? // But then the DT_GNU_HASH symbol lookup algorithm always fails?
// https://github.com/upx/upx/issues/525 // https://github.com/upx/upx/issues/525
} else } else
if ((1+ bmax) < symbias) { if (bmax) {
char msg[90]; snprintf(msg, sizeof(msg), if ((1+ bmax) < symbias) {
"bad DT_GNU_HASH (1+ max_bucket)=%#x < symbias=%#x", 1+ bmax, symbias); throwCantPack("bad DT_GNU_HASH (1+ max_bucket)=%#x < symbias=%#x",
throwCantPack(msg); 1+ bmax, symbias);
}
bmax -= symbias;
} }
bmax -= symbias;
unsigned r = 0; unsigned r = 0;
if (!n_bucket || !n_bitmask || !v_sym if (!n_bucket || !n_bitmask || !v_sym
@@ -7969,19 +7952,16 @@ PackLinuxElf64::invert_pt_dynamic(Elf64_Dyn const *dynp, upx_uint64_t headway)
} }
upx_uint64_t const d_tag = get_te64(&dynp->d_tag); upx_uint64_t const d_tag = get_te64(&dynp->d_tag);
if (d_tag>>32) { // outrageous if (d_tag>>32) { // outrageous
char msg[50]; snprintf(msg, sizeof(msg), throwCantPack("bad Elf64_Dyn[%d].d_tag %#lx",
"bad Elf64_Dyn[%d].d_tag %#lx", ndx, (long unsigned)d_tag); ndx, (long unsigned)d_tag);
throwCantPack(msg);
} }
if (d_tag < DT_NUM) { if (d_tag < DT_NUM) {
if (Elf64_Dyn::DT_NEEDED != d_tag if (Elf64_Dyn::DT_NEEDED != d_tag
&& dt_table[d_tag] && dt_table[d_tag]
&& get_te64(&dynp->d_val) && get_te64(&dynp->d_val)
!= get_te64(&dynp0[-1+ dt_table[d_tag]].d_val)) { != get_te64(&dynp0[-1+ dt_table[d_tag]].d_val)) {
char msg[50]; snprintf(msg, sizeof(msg), throwCantPack("duplicate DT_%#x: [%#x] [%#x]",
"duplicate DT_%#x: [%#x] [%#x]",
(unsigned)d_tag, -1+ dt_table[d_tag], ndx); (unsigned)d_tag, -1+ dt_table[d_tag], ndx);
throwCantPack(msg);
} }
dt_table[d_tag] = 1+ ndx; dt_table[d_tag] = 1+ ndx;
} }
@@ -8010,9 +7990,7 @@ PackLinuxElf64::invert_pt_dynamic(Elf64_Dyn const *dynp, upx_uint64_t headway)
// last string in table must have terminating NUL // last string in table must have terminating NUL
|| '\0' != ((char *)file_image.getVoidPtr())[-1+ strtab_max + strtab_beg] || '\0' != ((char *)file_image.getVoidPtr())[-1+ strtab_max + strtab_beg]
) { ) {
char msg[50]; snprintf(msg, sizeof(msg), throwCantPack("bad DT_STRSZ %#x", strtab_max);
"bad DT_STRSZ %#x", strtab_max);
throwCantPack(msg);
} }
// Find end of DT_SYMTAB // Find end of DT_SYMTAB
@@ -8032,9 +8010,7 @@ PackLinuxElf64::invert_pt_dynamic(Elf64_Dyn const *dynp, upx_uint64_t headway)
if (v_hsh && file_image) { if (v_hsh && file_image) {
hashtab = (unsigned const *)elf_find_dynamic(Elf64_Dyn::DT_HASH); hashtab = (unsigned const *)elf_find_dynamic(Elf64_Dyn::DT_HASH);
if (!hashtab) { if (!hashtab) {
char msg[40]; snprintf(msg, sizeof(msg), throwCantPack("bad DT_HASH %#x", v_hsh);
"bad DT_HASH %#x", v_hsh);
throwCantPack(msg);
} }
// Find end of DT_HASH // Find end of DT_HASH
hashend = (unsigned const *)(void const *)(elf_find_table_size( hashend = (unsigned const *)(void const *)(elf_find_table_size(
@@ -8045,19 +8021,15 @@ PackLinuxElf64::invert_pt_dynamic(Elf64_Dyn const *dynp, upx_uint64_t headway)
unsigned const *const chains = &buckets[nbucket]; (void)chains; unsigned const *const chains = &buckets[nbucket]; (void)chains;
if ((unsigned)(file_size - ((char const *)buckets - (char const *)(void const *)file_image)) if ((unsigned)(file_size - ((char const *)buckets - (char const *)(void const *)file_image))
<= sizeof(unsigned)*nbucket ) { <= sizeof(unsigned)*nbucket ) {
char msg[80]; snprintf(msg, sizeof(msg), throwCantPack("bad nbucket %#x\n", nbucket);
"bad nbucket %#x\n", nbucket);
throwCantPack(msg);
} }
if ((unsigned)(hashend - buckets) < nbucket if ((unsigned)(hashend - buckets) < nbucket
|| !v_sym || file_size_u <= v_sym || !v_sym || file_size_u <= v_sym
|| ((v_hsh < v_sym) && (v_sym - v_hsh) < sizeof(*buckets)*(2+ nbucket)) || ((v_hsh < v_sym) && (v_sym - v_hsh) < sizeof(*buckets)*(2+ nbucket))
) { ) {
char msg[80]; snprintf(msg, sizeof(msg), throwCantPack("bad DT_HASH nbucket=%#x len=%#x",
"bad DT_HASH nbucket=%#x len=%#x",
nbucket, (v_sym - v_hsh)); nbucket, (v_sym - v_hsh));
throwCantPack(msg);
} }
unsigned chmax = 0; unsigned chmax = 0;
for (unsigned j= 0; j < nbucket; ++j) { for (unsigned j= 0; j < nbucket; ++j) {
@@ -8068,10 +8040,8 @@ PackLinuxElf64::invert_pt_dynamic(Elf64_Dyn const *dynp, upx_uint64_t headway)
} }
if ((v_hsh < v_sym) && (v_sym - v_hsh) < if ((v_hsh < v_sym) && (v_sym - v_hsh) <
(sizeof(*buckets)*(2+ nbucket) + sizeof(*chains)*(1+ chmax))) { (sizeof(*buckets)*(2+ nbucket) + sizeof(*chains)*(1+ chmax))) {
char msg[80]; snprintf(msg, sizeof(msg), throwCantPack("bad DT_HASH nbucket=%#x len=%#x",
"bad DT_HASH nbucket=%#x len=%#x",
nbucket, (v_sym - v_hsh)); nbucket, (v_sym - v_hsh));
throwCantPack(msg);
} }
} }
unsigned const v_gsh = elf_unsigned_dynamic(Elf64_Dyn::DT_GNU_HASH); unsigned const v_gsh = elf_unsigned_dynamic(Elf64_Dyn::DT_GNU_HASH);
@@ -8079,9 +8049,7 @@ PackLinuxElf64::invert_pt_dynamic(Elf64_Dyn const *dynp, upx_uint64_t headway)
// Not similar to DT_HASH because DT_GNU_HASH is not small (0x6ffffef5). // Not similar to DT_HASH because DT_GNU_HASH is not small (0x6ffffef5).
gashtab = (unsigned const *)elf_find_dynamic(Elf64_Dyn::DT_GNU_HASH); gashtab = (unsigned const *)elf_find_dynamic(Elf64_Dyn::DT_GNU_HASH);
if (!gashtab) { if (!gashtab) {
char msg[40]; snprintf(msg, sizeof(msg), throwCantPack("bad DT_GNU_HASH %#x", v_gsh);
"bad DT_GNU_HASH %#x", v_gsh);
throwCantPack(msg);
} }
gashend = (unsigned const *)(void const *)(elf_find_table_size( gashend = (unsigned const *)(void const *)(elf_find_table_size(
Elf64_Dyn::DT_GNU_HASH, Elf64_Shdr::SHT_GNU_HASH) + (char const *)gashtab); Elf64_Dyn::DT_GNU_HASH, Elf64_Shdr::SHT_GNU_HASH) + (char const *)gashtab);
@@ -8095,9 +8063,7 @@ PackLinuxElf64::invert_pt_dynamic(Elf64_Dyn const *dynp, upx_uint64_t headway)
if (!n_bucket || (1u<<31) <= n_bucket /* fie on fuzzers */ if (!n_bucket || (1u<<31) <= n_bucket /* fie on fuzzers */
|| (unsigned)(gashend - buckets) < n_bucket || (unsigned)(gashend - buckets) < n_bucket
|| (file_size + file_image) <= (void const *)hasharr) { || (file_size + file_image) <= (void const *)hasharr) {
char msg[80]; snprintf(msg, sizeof(msg), throwCantPack("bad n_bucket %#x\n", n_bucket);
"bad n_bucket %#x\n", n_bucket);
throwCantPack(msg);
} }
// It would be better to detect zeroes shifted into low 6 bits of: // It would be better to detect zeroes shifted into low 6 bits of:
// (077 & (hash_32 >> gnu_shift)) // (077 & (hash_32 >> gnu_shift))
@@ -8113,10 +8079,8 @@ PackLinuxElf64::invert_pt_dynamic(Elf64_Dyn const *dynp, upx_uint64_t headway)
unsigned bj = get_te32(&buckets[j]); unsigned bj = get_te32(&buckets[j]);
if (bj) { if (bj) {
if (bj < symbias) { if (bj < symbias) {
char msg[90]; snprintf(msg, sizeof(msg), throwCantPack("bad DT_GNU_HASH bucket[%d] < symbias{%#x}\n",
"bad DT_GNU_HASH bucket[%d] < symbias{%#x}\n",
bj, symbias); bj, symbias);
throwCantPack(msg);
} }
if (bmax < bj) { if (bmax < bj) {
bmax = bj; bmax = bj;
@@ -8129,12 +8093,13 @@ PackLinuxElf64::invert_pt_dynamic(Elf64_Dyn const *dynp, upx_uint64_t headway)
// But then the DT_GNU_HASH symbol lookup algorithm always fails? // But then the DT_GNU_HASH symbol lookup algorithm always fails?
// https://github.com/upx/upx/issues/525 // https://github.com/upx/upx/issues/525
} else } else
if ((1+ bmax) < symbias) { if (bmax) {
char msg[90]; snprintf(msg, sizeof(msg), if ((1+ bmax) < symbias) {
"bad DT_GNU_HASH (1+ max_bucket)=%#x < symbias=%#x", 1+ bmax, symbias); throwCantPack("bad DT_GNU_HASH (1+ max_bucket)=%#x < symbias=%#x",
throwCantPack(msg); 1+ bmax, symbias);
}
bmax -= symbias;
} }
bmax -= symbias;
unsigned r = 0; unsigned r = 0;
if (!n_bucket || !n_bitmask || !v_sym if (!n_bucket || !n_bitmask || !v_sym
@@ -8150,18 +8115,14 @@ PackLinuxElf64::invert_pt_dynamic(Elf64_Dyn const *dynp, upx_uint64_t headway)
+ sizeof(*hasharr)*(1+ bmax) // hasharr + sizeof(*hasharr)*(1+ bmax) // hasharr
)) ) )) )
) { ) {
char msg[90]; snprintf(msg, sizeof(msg), throwCantPack("bad DT_GNU_HASH n_bucket=%#x n_bitmask=%#x len=%#lx r=%d",
"bad DT_GNU_HASH n_bucket=%#x n_bitmask=%#x len=%#lx r=%d",
n_bucket, n_bitmask, (long unsigned)(v_sym - v_gsh), r); n_bucket, n_bitmask, (long unsigned)(v_sym - v_gsh), r);
throwCantPack(msg);
} }
} }
e_shstrndx = get_te16(&ehdri.e_shstrndx); // who omitted this? e_shstrndx = get_te16(&ehdri.e_shstrndx); // who omitted this?
if (e_shnum <= e_shstrndx if (e_shnum <= e_shstrndx
&& !(0==e_shnum && 0==e_shstrndx) ) { && !(0==e_shnum && 0==e_shstrndx) ) {
char msg[40]; snprintf(msg, sizeof(msg), throwCantPack("bad .e_shstrndx %d >= .e_shnum %d", e_shstrndx, e_shnum);
"bad .e_shstrndx %d >= .e_shnum %d", e_shstrndx, e_shnum);
throwCantPack(msg);
} }
} }
@@ -8234,9 +8195,7 @@ Elf32_Sym const *PackLinuxElf32::elf_lookup(char const *name) const
if (!n_bitmask if (!n_bitmask
|| (unsigned)(file_size - ((char const *)bitmask - (char const *)(void const *)file_image)) || (unsigned)(file_size - ((char const *)bitmask - (char const *)(void const *)file_image))
<= sizeof(unsigned)*n_bitmask ) { <= sizeof(unsigned)*n_bitmask ) {
char msg[80]; snprintf(msg, sizeof(msg), throwCantPack("bad n_bitmask %#x\n", n_bitmask);
"bad n_bitmask %#x\n", n_bitmask);
throwCantPack(msg);
} }
if (n_bucket) { if (n_bucket) {
unsigned const h = gnu_hash(name); unsigned const h = gnu_hash(name);
@@ -8247,10 +8206,8 @@ Elf32_Sym const *PackLinuxElf32::elf_lookup(char const *name) const
if (1& (w>>hbit1) & (w>>hbit2)) { if (1& (w>>hbit1) & (w>>hbit2)) {
unsigned bucket = get_te32(&buckets[h % n_bucket]); unsigned bucket = get_te32(&buckets[h % n_bucket]);
if (n_bucket <= bucket) { if (n_bucket <= bucket) {
char msg[90]; snprintf(msg, sizeof(msg), throwCantPack("bad DT_GNU_HASH n_bucket{%#x} <= buckets[%d]{%#x}\n",
"bad DT_GNU_HASH n_bucket{%#x} <= buckets[%d]{%#x}\n",
n_bucket, h % n_bucket, bucket); n_bucket, h % n_bucket, bucket);
throwCantPack(msg);
} }
if (0!=bucket) { if (0!=bucket) {
Elf32_Sym const *dsp = &dynsym[bucket]; Elf32_Sym const *dsp = &dynsym[bucket];
@@ -8315,16 +8272,12 @@ Elf64_Sym const *PackLinuxElf64::elf_lookup(char const *name) const
throwCantPack("bad gnu_shift %#x", gnu_shift); throwCantPack("bad gnu_shift %#x", gnu_shift);
} }
if ((file_size + file_image) <= (void const *)hasharr) { if ((file_size + file_image) <= (void const *)hasharr) {
char msg[80]; snprintf(msg, sizeof(msg), throwCantPack("bad n_bucket %#x\n", n_bucket);
"bad n_bucket %#x\n", n_bucket);
throwCantPack(msg);
} }
if (!n_bitmask if (!n_bitmask
|| (unsigned)(file_size - ((char const *)bitmask - (char const *)(void const *)file_image)) || (unsigned)(file_size - ((char const *)bitmask - (char const *)(void const *)file_image))
<= sizeof(unsigned)*n_bitmask ) { <= sizeof(unsigned)*n_bitmask ) {
char msg[80]; snprintf(msg, sizeof(msg), throwCantPack("bad n_bitmask %#x\n", n_bitmask);
"bad n_bitmask %#x\n", n_bitmask);
throwCantPack(msg);
} }
if (n_bucket) { // -rust-musl can have "empty" gashtab if (n_bucket) { // -rust-musl can have "empty" gashtab
unsigned const h = gnu_hash(name); unsigned const h = gnu_hash(name);
@@ -8339,10 +8292,8 @@ Elf64_Sym const *PackLinuxElf64::elf_lookup(char const *name) const
unsigned k; unsigned k;
do { do {
if (gashend <= hp) { if (gashend <= hp) {
char msg[120]; snprintf(msg, sizeof(msg), throwCantPack("bad gnu_hash[%#tx] head=%u",
"bad gnu_hash[%#tx] head=%u",
hp - hasharr, hhead); hp - hasharr, hhead);
throwCantPack(msg);
} }
k = get_te32(hp); k = get_te32(hp);
if (0==((h ^ k)>>1)) { if (0==((h ^ k)>>1)) {