/* p_lx_interp.cpp --

   This file is part of the UPX executable compressor.

   Copyright (C) Markus Franz Xaver Johannes Oberhumer
   Copyright (C) Laszlo Molnar
   Copyright (C) John F. Reiser
   All Rights Reserved.

   UPX and the UCL library are free software; you can redistribute them
   and/or modify them under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of
   the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; see the file COPYING.
   If not, write to the Free Software Foundation, Inc.,
   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

   Markus F.X.J. Oberhumer              Laszlo Molnar
   <markus@oberhumer.com>               <ezerotven+github@gmail.com>

   John F. Reiser
   <jreiser@users.sourceforge.net>
 */


#include "conf.h"

#include "file.h"
#include "filter.h"
#include "linker.h"
#include "packer.h"
#include "p_elf.h"
#include "p_unix.h"
#include "p_lx_exc.h"
#include "p_lx_elf.h"
#include "p_lx_interp.h"

#define PT_LOAD     Elf32_Phdr::PT_LOAD
#define PT_INTERP   Elf32_Phdr::PT_INTERP


/*************************************************************************
//
**************************************************************************/

static const CLANG_FORMAT_DUMMY_STATEMENT
#include "stub/i386-linux.elf.interp-entry.h"
static const CLANG_FORMAT_DUMMY_STATEMENT
#include "stub/i386-linux.elf.interp-fold.h"

PackLinuxElf32x86interp::PackLinuxElf32x86interp(InputFile *f) :
    super(f)
{
}

PackLinuxElf32x86interp::~PackLinuxElf32x86interp()
{
}

tribool PackLinuxElf32x86interp::canPack()
{
    if (opt->o_unix.make_ptinterp) {
        return true;
    }
    if (!opt->o_unix.use_ptinterp) {
        return false;
    }
    return super::canPack();
}

void PackLinuxElf32x86interp::pack1(OutputFile *fo, Filter &)
{
    fi->seek(0, SEEK_SET);
    fi->readx(&ehdri, sizeof(ehdri));
    assert(ehdri.e_phoff == sizeof(Elf32_Ehdr));  // checked by canPack()
    sz_phdrs = ehdri.e_phnum * ehdri.e_phentsize;

    phdri = New(Elf32_Phdr, ehdri.e_phnum);
    fi->seek(ehdri.e_phoff, SEEK_SET);
    fi->readx(phdri, sz_phdrs);

#define E Elf32_Ehdr
    cprElfHdr3 h3;
    memset(&h3, 0, sizeof(h3));
    memcpy(h3.ehdr.e_ident, "\177ELF", 4);
    h3.ehdr.e_ident[E::EI_CLASS] = E::ELFCLASS32;
    h3.ehdr.e_ident[E::EI_DATA] = E::ELFDATA2LSB;
    h3.ehdr.e_ident[E::EI_VERSION] = E::EV_CURRENT;
    h3.ehdr.e_ident[E::EI_OSABI] = E::ELFOSABI_LINUX;
    h3.ehdr.e_ident[E::EI_ABIVERSION] = E::EV_CURRENT;
    h3.ehdr.e_type = E::ET_EXEC;
    h3.ehdr.e_machine = E::EM_386;
    h3.ehdr.e_version = 1;
    h3.ehdr.e_phoff = sizeof(Elf32_Ehdr);
    h3.ehdr.e_ehsize = sizeof(Elf32_Ehdr);
    h3.ehdr.e_phentsize = sizeof(Elf32_Phdr);
    h3.ehdr.e_phnum = 3;
    h3.phdr[0].p_type = PT_LOAD;
    h3.phdr[0].p_flags = Elf32_Phdr::PF_X | Elf32_Phdr::PF_R;
    h3.phdr[0].p_align = 0x1000;
    h3.phdr[1].p_type = PT_LOAD;
    h3.phdr[1].p_flags = Elf32_Phdr::PF_W | Elf32_Phdr::PF_R;
    h3.phdr[1].p_align = 1;
    h3.phdr[2].p_type = PT_INTERP;
    // h3.phdr[2].p_offset = (char *)&h3.phdr[2].p_vaddr - (char *)&h3;
    h3.phdr[2].p_offset = offsetof(cprElfHdr3, phdr[2].p_vaddr);
    memcpy(&h3.phdr[2].p_vaddr, "/upxrun", h3.phdr[2].p_filesz = 8);
    h3.phdr[2].p_align = 1;

    if (opt->o_unix.make_ptinterp) { // unusual "once per release"
        *(cprElfHdr3 *)(void *)&elfout = h3;
        elfout.ehdr.e_phnum = 1;
        fo->write(&elfout, elfout.ehdr.e_ehsize + elfout.ehdr.e_phentsize);
    }
    else { // usual case
        generateElfHdr(fo, &h3, getbrk(phdri, ehdri.e_phnum));
    }
#undef E
}

int PackLinuxElf32x86interp::pack2(OutputFile *fo, Filter &ft)
{
    if (opt->o_unix.make_ptinterp) {
        return 1;  // ignore current input file!
    }
    return super::pack2(fo, ft);
}

#undef PAGE_MASK
#define PAGE_MASK (~0u<<12)

off_t PackLinuxElf32x86interp::pack3(OutputFile *fo, Filter &/*ft*/)
{
    unsigned base = getbase(phdri, ehdri.e_phnum);
    unsigned sz = PAGE_MASK & (~PAGE_MASK + elfout.phdr[0].p_filesz);
    if (base < (0x11000 + sz)) {
        base =  0x11000 + sz;
    }
    if (opt->o_unix.make_ptinterp) {
        base = 0x10000;
    }
    elfout.phdr[0].p_paddr = elfout.phdr[0].p_vaddr = base - sz;
    if (opt->o_unix.make_ptinterp) {
        initLoader(0, stub_i386_linux_elf_interp_entry, sizeof(stub_i386_linux_elf_interp_entry));
        linker->addSection("FOLDEXEC", stub_i386_linux_elf_interp_fold, sizeof(stub_i386_linux_elf_interp_fold), 0);

        addLoader("LXPTI000", nullptr);

        addLoader("LXPTI040", nullptr);
        ph.method = M_NRV2B_LE32; addLoader(getDecompressorSections(), nullptr);
        addLoader("LXPTI090", nullptr);

        addLoader("LXPTI041", nullptr);
        ph.method = M_NRV2D_LE32; addLoader(getDecompressorSections(), nullptr);
        addLoader("LXPTI090", nullptr);

        addLoader("LXPTI042", nullptr);
        ph.method = M_NRV2E_LE32; addLoader(getDecompressorSections(), nullptr);
        addLoader("LXPTI090", nullptr);

        //addLoader("LXPTI043", nullptr);
        //ph.method = M_CL1B_LE32;  addLoader(getDecompressorSections(), nullptr);
        //addLoader("LXPTI090", nullptr);

        addLoader("LXPTI091", nullptr);

        addLoader("LXPTI140", nullptr);

        addLoader("LXUNF002,LXUNF008,LXUNF010", nullptr);
        addFilter32(0x46);
        addLoader("LXUNF042,LXUNF035", nullptr);

        addLoader("LXUNF002,LXUNF008,LXUNF010", nullptr);
        addFilter32(0x49);
        addLoader("LXUNF042,LXUNF035", nullptr);

        addLoader("LXPTI200", nullptr);
        addLoader("FOLDEXEC", nullptr);
        upx_byte const *p = getLoader();
        lsize = getLoaderSize();
        updateLoader(fo);
        fo->write(p, lsize);
        elfout.phdr[0].p_filesz = fo->getBytesWritten();
    }
    else {
        updateLoader(fo);
    }
    return fo->getBytesWritten();
}


void PackLinuxElf32x86interp::unpack(OutputFile *fo)
{
#define MAX_INTERP_HDR 512
    union {
        unsigned char buf[MAX_INTERP_HDR];
        //struct { Elf32_Ehdr ehdr; Elf32_Phdr phdr; } e;
    } u;
    Elf32_Ehdr *const ehdr = (Elf32_Ehdr *) u.buf;
    Elf32_Phdr const *phdr = (Elf32_Phdr *) (u.buf + sizeof(*ehdr));

    {
        fi->seek(0, SEEK_SET);
        fi->readx(u.buf, MAX_INTERP_HDR);
        unsigned const e_entry = get_te32(&ehdr->e_entry);
        if (e_entry < 0x401180) { /* old style, 8-byte b_info */
            szb_info = 2*sizeof(unsigned);
        }
    }

    fi->seek(overlay_offset, SEEK_SET);
    p_info hbuf;
    fi->readx(&hbuf, sizeof(hbuf));
    unsigned orig_file_size = get_te32(&hbuf.p_filesize);
    blocksize = get_te32(&hbuf.p_blocksize);
    if (file_size > (off_t)orig_file_size || blocksize > orig_file_size)
        throwCantUnpack("file header corrupted");

    ibuf.alloc(blocksize + OVERHEAD + (blocksize >> ELF_NRV_FUDGE));
    b_info bhdr; memset(&bhdr, 0, sizeof(bhdr));
    fi->readx(&bhdr, szb_info);
    ph.u_len = get_te32(&bhdr.sz_unc);
    ph.c_len = get_te32(&bhdr.sz_cpr);
    ph.filter_cto = bhdr.b_cto8;
    // sz_unc/sz_cpr come straight from the packed file's b_info; the Ehdr+Phdrs
    // decompress into the fixed-size u.buf[MAX_INTERP_HDR] on the stack, so the
    // sibling ElfXX/Mach unpackers that size their output buffer to u_len don't
    // overflow, but this one would. Bound u_len to the buffer.
    if (ph.c_len == 0 || ph.u_len < sizeof(*ehdr) || ph.u_len > MAX_INTERP_HDR)
        throwCantUnpack("b_info corrupted");

    // Uncompress Ehdr and Phdrs.
    fi->readx(ibuf, ph.c_len);
    decompress(ibuf, (upx_byte *)ehdr, false);
    if ((ph.u_len - sizeof(*ehdr)) / sizeof(*phdr) < ehdr->e_phnum)
        throwCantUnpack("bad compressed e_phnum");

    total_in = 0;
    total_out = 0;
    unsigned c_adler = upx_adler32(nullptr, 0);
    unsigned u_adler = upx_adler32(nullptr, 0);
    off_t ptload0hi=0, ptload1lo=0, ptload1sz=0;

    // decompress PT_LOAD
    bool first_PF_X = true;
    fi->seek(- (off_t) (szb_info + ph.c_len), SEEK_CUR);
    for (unsigned j=0; j < ehdr->e_phnum; ++phdr, ++j) {
        if (PT_LOAD==phdr->p_type) {
            if (0==ptload0hi) {
                ptload0hi = phdr->p_filesz + phdr->p_offset;
            }
            else if (0==ptload1lo) {
                ptload1lo = phdr->p_offset;
                ptload1sz = phdr->p_filesz;
            }
            if (fo)
                fo->seek(phdr->p_offset, SEEK_SET);
            if (Elf32_Phdr::PF_X & phdr->p_flags) {
                unpackExtent(phdr->p_filesz, fo,
                    c_adler, u_adler, first_PF_X, szb_info);
                first_PF_X = false;
            }
            else {
                unpackExtent(phdr->p_filesz, fo,
                    c_adler, u_adler, false, szb_info);
            }
        }
    }

    if (0!=ptload1sz && ptload0hi < ptload1lo) {  // alignment hole?
        if (fo)
            fo->seek(ptload0hi, SEEK_SET);
        unpackExtent(ptload1lo - ptload0hi, fo,
            c_adler, u_adler, false, szb_info);
    }
    if (total_out != orig_file_size) {  // non-PT_LOAD stuff
        if (fo)
            fo->seek(0, SEEK_END);
        unpackExtent(orig_file_size - total_out, fo,
            c_adler, u_adler, false, szb_info);
    }

    // check for end-of-file
    fi->readx(&bhdr, szb_info);
    unsigned const sz_unc = ph.u_len = get_te32(&bhdr.sz_unc);

    if (sz_unc == 0) { // uncompressed size 0 -> EOF
        // note: magic is always stored le32
        unsigned const sz_cpr = get_le32(&bhdr.sz_cpr);
        if (sz_cpr != UPX_MAGIC_LE32)  // sz_cpr must be h->magic
            throwCompressedDataViolation();
    }
    else { // extra bytes after end?
        throwCompressedDataViolation();
    }

    // update header with totals
    ph.c_len = total_in;
    ph.u_len = total_out;

    // all bytes must be written
    if (total_out != orig_file_size)
        throwEOFException();

    // finally test the checksums
    if (ph.c_adler != c_adler || ph.u_adler != u_adler)
        throwChecksumError();
#undef MAX_INTERP_HDR
}

/* vim:set ts=4 sw=4 et: */
