From 70cb4aaddabf7befd030e338a24babcdffa47c34 Mon Sep 17 00:00:00 2001 From: John Reiser Date: Wed, 10 Dec 2025 10:21:25 -0800 Subject: [PATCH] Fix "bad slide" on 32-bit ARM shared library https://github.com/upx/upx/issues/944 modified: src/p_elf_enum.h modified: src/p_lx_elf.cpp --- src/p_elf_enum.h | 2 + src/p_lx_elf.cpp | 126 ++++++++++++++++++++++++++--------------------- 2 files changed, 71 insertions(+), 57 deletions(-) diff --git a/src/p_elf_enum.h b/src/p_elf_enum.h index 93d52556..24781462 100644 --- a/src/p_elf_enum.h +++ b/src/p_elf_enum.h @@ -108,6 +108,7 @@ class Dummy { /* Register usage information. */ // // PT_MIPS_RTPROC = 0x70000001, /* Runtime procedure table. */ // PT_MIPS_OPTIONS = 0x70000002, + PT_ARM_EXIDX = (0x70000000 + 1), /* ARM unwind segment. */ PT_MIPS_ABIFLAGS = 0x70000003, /* FP mode requirement. */ }; @@ -148,6 +149,7 @@ class Dummy { SHT_LOOS = 0x60000000, /* LOcal OS; SHT_ANDROID_REL{,A} is +1, +2 */ SHT_LOPROC = 0x70000000, /* Start of processor-specific */ + SHT_ARM_EXIDX = (SHT_LOPROC + 1), /* ARM unwind section. */ SHT_ARM_ATTRIBUTES = (SHT_LOPROC + 3), /* ARM attributes section. */ }; diff --git a/src/p_lx_elf.cpp b/src/p_lx_elf.cpp index 6f3bed95..e8641fa7 100644 --- a/src/p_lx_elf.cpp +++ b/src/p_lx_elf.cpp @@ -487,17 +487,17 @@ PackLinuxElf32::slide_sh_offset(Elf32_Shdr *shdr) { unsigned sh_offset = get_te32(&shdr->sh_offset); unsigned sh_addr = get_te32(&shdr->sh_addr); + char const *sh_name = get_te32(&shdr->sh_name) + shstrtab; + (void)sh_name; // convenience for debug if (Elf32_Shdr::SHF_WRITE & get_te32(&shdr->sh_flags) || (sh_offset && !sh_addr)) { unsigned newoff = so_slide + sh_offset + (is_asl ? asl_delta : 0); - if ((unsigned)this->file_size < newoff) { + if (sh_addr // only check "real memory" + && (unsigned)this->file_size < newoff) { throwInternalError("bad slide %p %#x", shdr, (unsigned)so_slide); } set_te32(&shdr->sh_offset, newoff); - if (sh_addr) // change only if non-zero - set_te32(&shdr->sh_addr, - so_slide + sh_addr + (is_asl ? asl_delta : 0)); return newoff; } return sh_offset; @@ -520,17 +520,17 @@ PackLinuxElf64::slide_sh_offset(Elf64_Shdr *shdr) { unsigned sh_offset = get_te64(&shdr->sh_offset); unsigned sh_addr = get_te64(&shdr->sh_addr); + char const *sh_name = get_te32(&shdr->sh_name) + shstrtab; + (void)sh_name; // convenience for debug if (Elf64_Shdr::SHF_WRITE & get_te64(&shdr->sh_flags) || (sh_offset && !sh_addr)) { unsigned newoff = so_slide + sh_offset + (is_asl ? asl_delta : 0); - if ((unsigned)this->file_size < newoff) { + if (sh_addr // only check "real memory" + && (unsigned)this->file_size < newoff) { throwInternalError("bad slide %p %#x", shdr, (unsigned)so_slide); } set_te64(&shdr->sh_offset, newoff); - if (sh_addr) // change only if non-zero - set_te64(&shdr->sh_addr, - so_slide + sh_addr + (is_asl ? asl_delta : 0)); return newoff; } return sh_offset; @@ -641,25 +641,18 @@ off_t PackLinuxElf32::pack3(OutputFile *fo, Filter &ft) Elf32_Phdr *const phdr0 = (Elf32_Phdr *)lowmem.subref( "bad e_phoff", e_phoff, e_phnum * sizeof(Elf32_Phdr)); - Elf32_Phdr *phdr = phdr0; + Elf32_Phdr *phdr = phdr0, *outp = phdr0; upx_off_t off = fo->st_size(); // 64 bits so_slide = 0; for (unsigned j = 0; j < e_phnum; ++j, ++phdr) { // p_vaddr and p_paddr do not change! - unsigned const len = get_te32(&phdr->p_filesz); unsigned const ioff = get_te32(&phdri[j].p_offset); // without asl_delta unsigned align= get_te32(&phdr->p_align); unsigned const type = get_te32(&phdr->p_type); - if (Elf32_Phdr::PT_INTERP==type) { - // Rotate to highest position, so it can be lopped - // by decrementing e_phnum. - memcpy((unsigned char *)ibuf, phdr, sizeof(*phdr)); // extract - memmove(phdr, 1+phdr, (e_phnum - (1+ j))*sizeof(*phdr)); // overlapping - memcpy(&phdr[e_phnum - (1+ j)], (unsigned char *)ibuf, sizeof(*phdr)); // to top - --phdr; --e_phnum; - set_te16(&ehdri.e_phnum, e_phnum); - set_te16(&((Elf32_Ehdr *)(unsigned char *)lowmem)->e_phnum, e_phnum); - continue; + unsigned const len = get_te32(&phdr->p_filesz); + if (Elf32_Phdr::PT_INTERP==type + || Elf32_Phdr::PT_ARM_EXIDX==type) { +/* OMIT */ continue; // OMIT: reference to compressed data } if (PT_LOAD == type) { if (!ioff) { // first PT_LOAD must contain everything written so far @@ -707,12 +700,21 @@ off_t PackLinuxElf32::pack3(OutputFile *fo, Filter &ft) fo->seek(0, SEEK_END); } } - continue; // all done with this PT_LOAD } - if (xct_off < ioff) { - set_te32(&phdr->p_offset, so_slide + (is_asl ? asl_delta : 0) + ioff); + else if (Elf32_Phdr::PT_DYNAMIC == type + || Elf32_Phdr::PT_GNU_RELRO == type) { + unsigned delta = so_slide + (is_asl ? asl_delta : 0); + set_te32(&phdr->p_offset, delta + ioff); + // unsigned x = get_te32(&phdr->p_vaddr); + // set_te32(&phdr->p_vaddr, delta + x); + // unsigned y = get_te32(&phdr->p_paddr); + // set_te32(&phdr->p_paddr, delta + y); } + *outp++ = *phdr; // propagate } // end each Phdr + e_phnum = outp - phdr0; + set_te16(&ehdri.e_phnum, e_phnum); + set_te16(&((Elf32_Ehdr *)(unsigned char *)lowmem)->e_phnum, e_phnum); if (sec_arm_attr || is_asl) { // must update Shdr.sh_offset for so_slide Elf32_Shdr *shdr = shdri; @@ -769,6 +771,7 @@ off_t PackLinuxElf32::pack3(OutputFile *fo, Filter &ft) return total_out; } +// NYI 2025-12-10: should be updated from the Elf32::pack3 version off_t PackLinuxElf64::pack3(OutputFile *fo, Filter &ft) { if (!overlay_offset) { @@ -835,7 +838,7 @@ off_t PackLinuxElf64::pack3(OutputFile *fo, Filter &ft) Elf64_Phdr *const phdr0 = (Elf64_Phdr *)lowmem.subref( "bad e_phoff", e_phoff, e_phnum * sizeof(Elf64_Phdr)); - Elf64_Phdr *phdr = phdr0; + Elf64_Phdr *phdr = phdr0, *outp = phdr0; upx_off_t off = fo->st_size(); // 64 bits so_slide = 0; for (unsigned j = 0; j < e_phnum; ++j, ++phdr) { @@ -845,16 +848,9 @@ off_t PackLinuxElf64::pack3(OutputFile *fo, Filter &ft) u64_t align= get_te64(&phdr->p_align); unsigned const type = get_te32(&phdr->p_type); if (Elf64_Phdr::PT_INTERP==type) { - // Rotate to highest position, so it can be lopped - // by decrementing e_phnum. - memcpy((unsigned char *)ibuf, phdr, sizeof(*phdr)); // extract - memmove(phdr, 1+phdr, (e_phnum - (1+ j))*sizeof(*phdr)); // overlapping - memcpy(&phdr[e_phnum - (1+ j)], (unsigned char *)ibuf, sizeof(*phdr)); // to top - --phdr; --e_phnum; - set_te16(&ehdri.e_phnum, e_phnum); - set_te16(&((Elf64_Ehdr *)(unsigned char *)lowmem)->e_phnum, e_phnum); - continue; + continue; // OMIT } + *outp++ = *phdr; // propagate if (PT_LOAD == type) { if (!ioff) { // first PT_LOAD must contain everything written so far set_te64(&phdr->p_filesz, sz_pack2 + lsize); // is this correct? @@ -903,6 +899,9 @@ off_t PackLinuxElf64::pack3(OutputFile *fo, Filter &ft) set_te64(&phdr->p_offset, so_slide + (is_asl ? asl_delta : 0) + ioff); } } // end each Phdr + e_phnum = outp - phdr0; + set_te16(&ehdri.e_phnum, e_phnum); + set_te16(&((Elf64_Ehdr *)(unsigned char *)lowmem)->e_phnum, e_phnum); if (sec_arm_attr || is_asl) { // must update Shdr.sh_offset for so_slide // Update {DYNAMIC}.sh_offset by so_slide. @@ -6107,6 +6106,8 @@ unsigned PackLinuxElf32::forward_Shdrs(OutputFile *fo, Elf32_Ehdr *const eho) // Keep _Shdr for SHF_WRITE. // Discard _Shdr with (0==sh_addr), except _Shdr[0] // Keep ARM_ATTRIBUTES + // Remove .ARM.extab(PROGBITS) and .ARM.exidx(SHT_ARM_EXIDX) + // because they refer to compressed .text(PT_LOAD) unsigned const want_types_mask = 0 | 1u< xct_off) { // may slide down: earlier compression - if (sh_offset >= xct_off_hi) { // easy: so_slide down - if (Elf32_Shdr::SHT_ARM_ATTRIBUTES != sh_type) { - slide_sh_offset(sh_out); - } - } - else { // somewhere in compressed; try proportional (aligned) - // But note that PROGBITS without SHF_ALLOC - // will be dropped below. - u32_t const slice = xct_off + (~0xFu & (unsigned)( - (sh_offset - xct_off) * - ((sh_offset - xct_off) / (float)(xct_off_hi - xct_off)))); - //set_te32(&sh_out->sh_addr, slice); - set_te32(&sh_out->sh_offset, slice); - } - u32_t const max_sz = total_out - get_te32(&sh_out->sh_offset); - if (sh_size > max_sz) { // avoid complaint "extends beyond EOF" - set_te32(&sh_out->sh_size, max_sz); - } - } if (j == e_shstrndx) { // changes Elf32_Ehdr itself set_te16(&eho->e_shstrndx, sh_out - (Elf32_Shdr *)mb_shdro.getVoidPtr()); } + if (Elf32_Shdr::SHT_ARM_EXIDX == sh_type + || 0==strcmp(".ARM.extab", name)) { +/* OMIT */ continue; // OMIT: compressed contents + } if (Elf32_Shdr::SHT_ARM_ATTRIBUTES == sh_type || (SHT_NOTE == sh_type && xct_off < sh_offset) ) { // append a copy @@ -6218,7 +6203,7 @@ unsigned PackLinuxElf32::forward_Shdrs(OutputFile *fo, Elf32_Ehdr *const eho) if (!(Elf32_Shdr::SHF_ALLOC & sh_flags)) { // .debug_*, .gnu_debuglink etc. Typically compressed // but not in RAM, and gdb (BFD) gets confused. - continue; // OMIT the commit: do not forward +/* OMIT */ continue; // OMIT the commit: do not forward } else if (sh_offset <= xct_off && 0 == strcmp(".text", name) ) { @@ -6233,9 +6218,31 @@ unsigned PackLinuxElf32::forward_Shdrs(OutputFile *fo, Elf32_Ehdr *const eho) n_shstrsec = sh_out; } else if (strcmp(".dynstr", name)) { - continue; // OMIT the commit of non-global symbol names +/* OMIT */ continue; // OMIT the commit of non-global symbol names } } + if (sh_offset > xct_off) { // may slide down: earlier compression + if (sh_offset >= xct_off_hi) { // easy: so_slide down + if (Elf32_Shdr::SHT_ARM_ATTRIBUTES != sh_type + // && Elf32_Shdr::SHT_ARM_EXIDX != sh_type + ) { + slide_sh_offset(sh_out); + } + } + else { // somewhere in compressed; try proportional (aligned) + // Note that PROGBITS without SHF_ALLOC is already omitted. + u32_t const slice = xct_off + (~0xFu & (unsigned)( + (sh_offset - xct_off) * + ((sh_offset - xct_off) / (float)(xct_off_hi - xct_off)))); + //set_te32(&sh_out->sh_addr, slice); + set_te32(&sh_out->sh_offset, slice); + } + u32_t const max_sz = total_out - get_te32(&sh_out->sh_offset); + if (sh_size > max_sz) { // avoid complaint "extends beyond EOF" + set_te32(&sh_out->sh_size, max_sz); + } + } + // Will be forwarded, but needs a name. set_te32(&sh_out->sh_name, ptr_shstrings - (char *)mb_shstrings.getVoidPtr()); do { // stupid MSVC lacks stpcpy() *ptr_shstrings++ = *name; @@ -6292,6 +6299,7 @@ unsigned PackLinuxElf32::forward_Shdrs(OutputFile *fo, Elf32_Ehdr *const eho) return penalty; } +// NYI 2025-12-10: should be merged from Elf32::forward_Shdrs unsigned PackLinuxElf64::forward_Shdrs(OutputFile *fo, Elf64_Ehdr *const eho) { if (!fo) { @@ -6303,7 +6311,9 @@ unsigned PackLinuxElf64::forward_Shdrs(OutputFile *fo, Elf64_Ehdr *const eho) // Keep _Shdr for rtld data (below xct_off). // Discard _Shdr for compressed regions, except ".text" for gdb. // Keep _Shdr with SHF_WRITE. - // Keep ARM_ATTRIBUTES + // Keep ARM_ATTRIBUTES. + // Remove .ARM.extab(PROGBITS) and .ARM.exidx(ARM_EXIDX) + // because they refer to compressed .text. // Discard _Shdr with (0==sh_addr), except _Shdr[0] unsigned const want_types_mask = 1u<sh_name)]; - (void)sh_name; // debugging unsigned sh_type = get_te32(&sh_in->sh_type); u64_t sh_flags = get_te64(&sh_in->sh_flags); u64_t sh_addr = get_te64(&sh_in->sh_addr); @@ -6396,6 +6405,9 @@ unsigned PackLinuxElf64::forward_Shdrs(OutputFile *fo, Elf64_Ehdr *const eho) set_te16(&eho->e_shstrndx, sh_out - (Elf64_Shdr *)mb_shdro.getVoidPtr()); } + if (0==strcmp(".ARM.extab", sh_name)) { +/* OMIT */ continue; // OMIT: compressed contents + } if (j == e_shstrndx || sec_arm_attr == sh_in || (SHT_NOTE == sh_type && xct_off < sh_offset)