Unify 64-bit upx_main() stub for shlib with 32-bit i386

modified:   stub/src/amd64-linux.elf-so_main.c
This commit is contained in:
John Reiser 2024-11-26 16:43:02 -08:00
parent 93f420939e
commit a2fa417af9
1 changed files with 134 additions and 111 deletions

View File

@ -40,7 +40,7 @@ extern size_t Pwrite(unsigned, void const *, size_t);
extern void f_int3(int arg);
#define DEBUG 0
#define DEBUG 1
#ifndef DEBUG //{
#define DEBUG 0
@ -242,33 +242,51 @@ ERR_LAB
extern int memfd_create(const char *name, unsigned int flags);
#define ElfW(sym) Elf64_ ## sym
#define nullptr (void *)0
extern char *upx_mmap_and_fd( // x86_64 Android emulator of i386 is not faithful
void *ptr // desired address
, unsigned len // also pre-allocate space in file
, char *pathname // 0 ==> call get_upxfn_path, which stores if 1st time
);
#if defined(__x86_64__) //{
static char *
make_hatch_x86_64(
Elf64_Phdr const *const phdr,
char *
make_hatch(
ElfW(Phdr) const *const phdr,
char *next_unc,
unsigned frag_mask
)
{
char *hatch = 0;
unsigned *hatch = 0;
unsigned code[3] = {
0x5e5f050f, // syscall; pop %arg1{%rdi}; pop %arg2{%rsi}
0xff3e585a, // pop %arg3{%rdx}; pop %rax; notrack jmp
0x909090e0 // *%rax; nop; nop; nop
};
DPRINTF("make_hatch %%p %%p %%x\\n", phdr, next_unc, frag_mask);
if (phdr->p_type==PT_LOAD && phdr->p_flags & PF_X) {
next_unc += phdr->p_memsz - phdr->p_filesz; // Skip over local .bss
frag_mask &= -(long)next_unc; // bytes left on page
if (6 <= frag_mask) {
hatch = next_unc;
(( long *)hatch)[0] = 0x5e5f050f; // syscall; pop %arg1{%rdi}; pop %arg2{%rsi}
((short *)hatch)[2] = 0xc35a; // pop %arg3{%rdx}; ret
if (sizeof(code) <= frag_mask) {
hatch = (unsigned *)next_unc;
hatch[0] = code[0];
hatch[1] = code[1];
hatch[2] = code[2];
}
else { // Does not fit at hi end of .text, so must use a new page "permanently"
int mfd = memfd_create(addr_string("upx"), 0); // the directory entry
Pwrite(mfd, addr_string("\x0f\x05\x5f\x5e\x5a\xc3"), 6);
hatch = Pmap(0, 6, PROT_READ|PROT_EXEC, MAP_PRIVATE, mfd, 0);
unsigned long fdmap = (long)upx_mmap_and_fd((void *)0, sizeof(code), nullptr);
unsigned mfd = -1+ (0xfff& fdmap);
write(mfd, &code, sizeof(code));
hatch = mmap((void *)(fdmap & ~0xffful), sizeof(code),
PROT_READ|PROT_EXEC, MAP_PRIVATE, mfd, 0);
close(mfd);
}
}
DPRINTF("hatch=%%p\\n", hatch);
return hatch;
return (char *)hatch;
}
#elif defined(__powerpc64__) //}{
static unsigned
@ -278,8 +296,8 @@ ORRX(unsigned ra, unsigned rs, unsigned rb) // or ra,rs,rb
}
static char *
make_hatch_ppc64(
Elf64_Phdr const *const phdr,
make_hatch(
ElfW(Phdr) const *const phdr,
char *next_unc,
unsigned frag_mask
)
@ -314,8 +332,8 @@ make_hatch_ppc64(
}
#elif defined(__aarch64__) //{
static char *
make_hatch_arm64(
Elf64_Phdr const *const phdr,
make_hatch(
ElfW(Phdr) const *const phdr,
char *next_unc,
unsigned frag_mask
)
@ -365,26 +383,24 @@ make_hatch_arm64(
|(REP8(PROT_WRITE) & EXP8(PF_W)) \
) >> ((pf & (PF_R|PF_W|PF_X))<<2) ))
#define nullptr (void *)0
#undef PAGE_MASK
static unsigned long
get_PAGE_MASK(void) // the mask which KEEPS the page, discards the offset
get_page_mask(void) // the mask which KEEPS the page, discards the offset
{
unsigned long rv = ~0xffful; // default to (PAGE_SIZE == 4KiB)
int fd = openat(0, addr_string("/proc/self/auxv"), O_RDONLY, 0);
if (0 <= fd) {
Elf64_auxv_t data[40];
Elf64_auxv_t *end = &data[read(fd, data, sizeof(data)) / sizeof(data[0])];
ElfW(auxv_t) data[40];
ElfW(auxv_t) *end = &data[read(fd, data, sizeof(data)) / sizeof(data[0])];
close(fd);
Elf64_auxv_t *ptr; for (ptr = &data[0]; ptr < end ; ++ptr) {
ElfW(auxv_t) *ptr; for (ptr = &data[0]; ptr < end ; ++ptr) {
if (AT_PAGESZ == ptr->a_type) {
rv = (0u - ptr->a_un.a_val);
break;
}
}
}
DPRINTF("get_PAGE_MASK= %%p\\n", rv);
DPRINTF("get_page_mask= %%p\\n", rv);
return rv;
}
@ -410,12 +426,47 @@ underlay(unsigned size, char *ptr, unsigned len);
// Exchange the bits with values 4 (PF_R, PROT_EXEC) and 1 (PF_X, PROT_READ)
// Use table lookup into a PIC-string that pre-computes the result.
unsigned PF_to_PROT(Elf64_Phdr const *phdr)
unsigned PF_to_PROT(ElfW(Phdr) const *phdr)
{
return 7& addr_string("@\x04\x02\x06\x01\x05\x03\x07")
[phdr->p_flags & (PF_R|PF_W|PF_X)];
}
unsigned
fini_SELinux(
unsigned size,
char *ptr,
ElfW(Phdr) const *phdr,
unsigned mfd,
ElfW(Addr) base
)
{
if (phdr->p_flags & PF_X) {
// Map the contents of mfd as per *phdr.
Punmap(ptr, size);
Pmap(ptr, size, PF_to_PROT(phdr), MAP_FIXED|MAP_PRIVATE, mfd, 0);
close(mfd);
}
else { // easy
Pprotect( (char *)(phdr->p_vaddr + base), phdr->p_memsz, PF_to_PROT(phdr));
}
return 0;
}
unsigned
prep_SELinux(unsigned size, char *ptr, unsigned len) // returns mfd
{
// Cannot set PROT_EXEC except via mmap() into a region (Linux "vma")
// that has never had PROT_WRITE. So use a Linux-only "memory file"
// to hold the contents.
char *val = upx_mmap_and_fd(ptr, size, nullptr);
unsigned mfd = 0xfff & (unsigned)(long)val;
val -= mfd; --mfd;
if (len)
Pwrite(mfd, ptr, len); // Save lo fragment of contents on first page.
return mfd;
}
typedef struct {
long argc;
char **argv;
@ -423,7 +474,7 @@ typedef struct {
} So_args;
typedef struct {
unsigned off_reloc; // distance back to &Elf64_Ehdr
unsigned off_reloc; // distance back to &ElfW(Ehdr)
unsigned off_user_DT_INIT;
unsigned off_xct_off; // where un-compressed bytes end
unsigned off_info; // xct_off: {l_info; p_info; b_info; compressed data)
@ -437,10 +488,10 @@ void *
upx_so_main( // returns &escape_hatch
So_info *so_info,
So_args *so_args,
Elf64_Ehdr *elf_tmp // scratch for Elf64_Ehdr and Elf64_Phdrs
ElfW(Ehdr) *elf_tmp // scratch for ElfW(Ehdr) and ElfW(Phdrs)
)
{
unsigned long const PAGE_MASK = get_PAGE_MASK();
unsigned long const page_mask = get_page_mask();
char *const va_load = (char *)&so_info->off_reloc - so_info->off_reloc;
So_info so_infc; // So_info Copy
memcpy(&so_infc, so_info, sizeof(so_infc)); // before de-compression overwrites
@ -457,7 +508,7 @@ upx_so_main( // returns &escape_hatch
// Copy compressed data before de-compression overwrites it.
char *const sideaddr = mmap(nullptr, cpr_len, PROT_WRITE|PROT_READ,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
DPRINTF("sideaddr=%%p\\n", sideaddr);
DPRINTF("&sideaddr=%%p\\n", &sideaddr);
memcpy(sideaddr, cpr_ptr, cpr_len);
// Transition to copied data
@ -479,110 +530,82 @@ upx_so_main( // returns &escape_hatch
// De-compress from remaining [sideaddr, +sidelen).
// Pprotect(,, PF_TO_PROT(.p_flags));
// Get the uncompressed Elf64_Ehdr and Elf64_Phdr
// Get the uncompressed ElfW(Ehdr) and ElfW(Phdr)
// The first b_info is aligned, so direct access to fields is OK.
Extent x1 = {binfo->sz_unc, (char *)elf_tmp}; // destination
Extent x0 = {binfo->sz_cpr + sizeof(*binfo), (char *)binfo}; // source
unpackExtent(&x0, &x1); // de-compress _Ehdr and _Phdrs; x0.buf is updated
Elf64_Phdr const *phdr = (Elf64_Phdr *)(1+ elf_tmp);
Elf64_Phdr const *const phdrN = &phdr[elf_tmp->e_phnum];
while (phdr->p_type != PT_LOAD) ++phdr; // skip PT_PHDR if any
Elf64_Addr const base = (Elf64_Addr)va_load - phdr->p_vaddr;
DPRINTF("base=%%p\\n", base);
if (phdr->p_flags & PF_X) {
int mfd = memfd_create(addr_string("upx"), 0);
unsigned mfd_len = 0ul - PAGE_MASK;
Pwrite(mfd, elf_tmp, binfo->sz_unc); // de-compressed Elf_Ehdr and Elf_Phdrs
Pwrite(mfd, binfo->sz_unc + va_load, mfd_len - binfo->sz_unc); // rest of 1st page
Punmap(va_load, mfd_len); // make SELinux forget any previous protection
Elf64_Addr va_mfd = (Elf64_Addr)Pmap(va_load, mfd_len, PF_to_PROT(phdr),
MAP_FIXED|MAP_PRIVATE, mfd, 0); (void)va_mfd;
close(mfd);
}
ElfW(Phdr) const *phdr = (ElfW(Phdr) *)(1+ elf_tmp);
ElfW(Phdr) const *const phdrN = &phdr[elf_tmp->e_phnum];
// Process each read-only PT_LOAD.
// A read+write PT_LOAD might be relocated by rtld before de-compression,
// so it cannot be compressed.
struct b_info al_bi; // for aligned data from binfo
void *hatch = nullptr;
ElfW(Addr) base = 0;
int n_load = 0;
for (; phdr < phdrN; ++phdr)
if ( phdr->p_type == PT_LOAD && !(phdr->p_flags & PF_W)) {
DPRINTF("phdr@%%p p_offset=%%p p_vaddr=%%p p_filesz=%%p p_memsz=%%p binfo=%%p\\n",
phdr, phdr->p_offset, phdr->p_vaddr, phdr->p_filesz, phdr->p_memsz, x0.buf);
if ((phdr->p_filesz + phdr->p_offset) <= so_infc.off_xct_off) {
continue; // below compressed region
}
Elf64_Addr const pfx = (so_infc.off_xct_off < phdr->p_offset)
? 0 // entire PT_LOAD is compressed
: so_infc.off_xct_off - phdr->p_offset ; // below xct_off is not
if (phdr->p_type == PT_LOAD && !(phdr->p_flags & PF_W)) {
unsigned hi_offset = phdr->p_filesz + phdr->p_offset;
struct b_info al_bi; // for aligned data from binfo
// Need un-aligned read of b_info to determine compression sizes.
x0.size = sizeof(struct b_info);
xread(&x0, (char *)&al_bi, x0.size); // aligned binfo
x0.buf -= sizeof(al_bi); // back up (the xread() was a peek)
DPRINTF("next1 pfx=%%x binfo@%%p (%%p %%p %%p)\\n", pfx, x0.buf,
al_bi.sz_unc, al_bi.sz_cpr, *(unsigned *)(void *)&al_bi.b_method);
x1.size = hi_offset - xct_off;
x1.buf = (void *)(hi_offset + base - al_bi.sz_unc);
x0.size = al_bi.sz_cpr;
// Using .p_memsz implicitly handles .bss via MAP_ANONYMOUS.
// Omit any non-tcompressed prefix (below xct_off)
x1.buf = (char *)(pfx + phdr->p_vaddr + base);
x1.size = phdr->p_memsz - pfx;
unsigned const frag = (phdr->p_vaddr + pfx) & ~PAGE_MASK; // lo fragment on page
x1.buf -= frag;
x1.size += frag;
DPRINTF("phdr(%%p %%p) xct_off=%%x frag=%%x\\n", x1.buf, x1.size, xct_off, frag);
int mfd = 0;
char *mfd_addr = 0;
if (phdr->p_flags & PF_X) { // SELinux
// Cannot set PROT_EXEC except via mmap() into a region (Linux "vma")
// that has never had PROT_WRITE. So use a Linux-only "memory file"
// to hold the contents.
mfd = memfd_create(addr_string("upx"), 0); // the directory entry
ftruncate(mfd, x1.size); // Allocate the pages in the file.
Pwrite(mfd, x1.buf, frag); // Save lo fragment of contents on first page.
Punmap(x1.buf, x1.size);
mfd_addr = Pmap(x1.buf, x1.size, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_SHARED, mfd, 0);
DPRINTF("mfd_addr= %%p\\n", mfd_addr); // Re-use the address space
}
else {
underlay(x1.size, x1.buf, frag); // also makes PROT_WRITE
if (!base) {
base = (ElfW(Addr))va_load - phdr->p_vaddr;
DPRINTF("base=%%p\\n", base);
}
DPRINTF("phdr@%%p p_offset=%%p p_vaddr=%%p p_filesz=%%p p_memsz=%%p\\n",
phdr, phdr->p_offset, phdr->p_vaddr, phdr->p_filesz, phdr->p_memsz);
DPRINTF("x0=%%p x1=%%p\\n", &x0, &x1);
//my_bkpt((void const *)0x1230, phdr, &x0, &x1);
x1.buf += frag;
x1.size = al_bi.sz_unc;
x0.size = al_bi.sz_cpr + sizeof(struct b_info);
DPRINTF("before unpack x0=(%%p %%p x1=(%%p %%p)\\n", x0.size, x0.buf, x1.size, x1.buf);
unpackExtent(&x0, &x1); // updates x0 and x1
DPRINTF(" after unpack x0=(%%p %%p x1=(%%p %%p)\\n", x0.size, x0.buf, x1.size, x1.buf);
if (!hatch && phdr->p_flags & PF_X) {
#if defined(__x86_64) //{
hatch = make_hatch_x86_64(phdr, x1.buf, ~PAGE_MASK);
#elif defined(__powerpc64__) //}{
hatch = make_hatch_ppc64(phdr, x1.buf, ~PAGE_MASK);
#elif defined(__aarch64__) //}{
hatch = make_hatch_arm64(phdr, x1.buf, ~PAGE_MASK);
#endif //}
unsigned frag = ~page_mask & (unsigned)(long)x1.buf;
unsigned mfd = 0;
if (!n_load) { // 1st PT_LOAD is special.
// Already ELF headers are in place, perhaps already followed
// by non-compressed loader tables below xct_off.
if (xct_off < hi_offset) { // 1st PT_LOAD also has compressed code, too
if (phdr->p_flags & PF_X) {
mfd = prep_SELinux(x1.size, x1.buf, frag);
}
else {
underlay(x1.size, x1.buf, page_mask); // also makes PROT_WRITE
}
unpackExtent(&x0, &x1);
if (!hatch && phdr->p_flags & PF_X) {
hatch = make_hatch(phdr, x1.buf, ~page_mask);
}
my_bkpt((void const *)0x1235, &x1);
fini_SELinux(x1.size, x1.buf, phdr, mfd, base); // FIXME: x1 changed!
}
}
if (phdr->p_flags & PF_X) { // SELinux
// Map the contents of mfd as per *phdr.
DPRINTF("mfd mmap addr=%%p len=%%p\\n", (phdr->p_vaddr + base + pfx), al_bi.sz_unc);
Punmap(mfd_addr, frag + al_bi.sz_unc); // Discard RW mapping; mfd has the bytes
Pmap((char *)(phdr->p_vaddr + base + pfx), al_bi.sz_unc, PF_to_PROT(phdr),
MAP_FIXED|MAP_PRIVATE, mfd, 0);
close(mfd);
}
else { // easy
Pprotect( (char *)(phdr->p_vaddr + base), phdr->p_memsz, PF_to_PROT(phdr));
else { // 2nd and later PT_LOADs
x1.buf = (void *)(phdr->p_vaddr + base);
x1.size = phdr->p_filesz;
if (phdr->p_flags & PF_X) {
mfd = prep_SELinux(x1.size, x1.buf, frag);
}
else {
underlay(x1.size, x1.buf, page_mask); // also makes PROT_WRITE
}
unpackExtent(&x0, &x1);
if (!hatch && phdr->p_flags &PF_X) {
hatch = make_hatch(phdr, x1.buf, ~page_mask);
}
fini_SELinux(al_bi.sz_unc, (void *)(phdr->p_vaddr + base), phdr, mfd, base);
}
++n_load;
}
DPRINTF("Punmap sideaddr=%%p cpr_len=%%p\\n", sideaddr, cpr_len);
Punmap(sideaddr, cpr_len);
DPRINTF("calling user DT_INIT %%p\\n", dt_init);
dt_init(so_args->argc, so_args->argv, so_args->envp);