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
This commit is contained in:
John Reiser 2025-12-10 10:21:25 -08:00
parent 75325bf9ef
commit 70cb4aadda
2 changed files with 71 additions and 57 deletions

View File

@ -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. */
};

View File

@ -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<<SHT_PROGBITS // see comment above, and special code below
| 1u<<SHT_HASH
@ -6181,30 +6182,14 @@ unsigned PackLinuxElf32::forward_Shdrs(OutputFile *fo, Elf32_Ehdr *const eho)
|| (Elf32_Shdr::SHT_ARM_ATTRIBUTES == sh_type)
) {
*sh_out = *sh_in; // *sh_in is a candidate for fowarding
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) {
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<<SHT_SYMTAB
@ -6351,7 +6361,6 @@ unsigned PackLinuxElf64::forward_Shdrs(OutputFile *fo, Elf64_Ehdr *const eho)
for (unsigned j = 1; j < e_shnum; ++j, ++sh_in) {
char const *sh_name = &shstrtab[get_te32(&sh_in->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)