/* i386-bsd.elf.execve-main.c -- generic stub loader for *BSD using execve()

   This file is part of the UPX executable compressor.

   Copyright (C) 1996-2025 Markus Franz Xaver Johannes Oberhumer
   Copyright (C) 1996-2025 Laszlo Molnar
   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 "include/bsd.h"


/*************************************************************************
// configuration section
**************************************************************************/

// mmap() the temporary output file
#define USE_MMAP_FO 1


/*************************************************************************
// file util
**************************************************************************/

struct Extent {
    int  size;  // must be first to match size[0] uncompressed size
    char *buf;
};


#if !(USE_MMAP_FO)
#if 1
static __inline__ int xwrite(int fd, const void *buf, int count)
{
    // note: we can assert(count > 0);
    do {
        int n = write(fd, buf, count);
        if (n == -EINTR)
            continue;
        if (n <= 0)
            break;
        buf += n;               // gcc extension: add to void *
        count -= n;
    } while (count > 0);
    return count;
}
#else
#define xwrite(fd,buf,count)    ((count) - write(fd,buf,count))
#endif
#endif /* !USE_MMAP_FO */


/*************************************************************************
// util
**************************************************************************/

#if 1

extern char *
__attribute__((regparm(2), stdcall))  // be ruthless
upx_itoa(unsigned long v, char *buf);

#else

// Some versions of gcc optimize the division and/or remainder using
// a multiplication by (2**32)/10, and use a relocatable 32-bit address
// to reference the constant.  We require no relocations because we move
// the code at runtime.  See upx_itoa.S for replacement [also smaller.]
static char *upx_itoa(unsigned long v, char *buf)
{
//    const unsigned TEN = 10;
    volatile unsigned TEN = 10;  // an ugly way to achieve no relocation
    char *p = buf;
    {
        unsigned long k = v;
        do {
            p++;
            k /= TEN;
        } while (k > 0);
    }
    buf = p;
    *p = 0;
    {
        unsigned long k = v;
        do {
            *--p = '0' + k % TEN;
            k /= TEN;
        } while (k > 0);
    }
    return buf;
}

#endif


static uint32_t ascii5(char *p, uint32_t v, unsigned n)
{
    do {
        unsigned char d = v % 32;
        if (d >= 26) d -= 43;       // 43 == 'Z' - '0' + 1
        *--p = (d += 'A');
        v /= 32;
    } while (--n > 0);
    return v;
}


#if defined(__i386__)
#  define SET2(p, c0, c1) \
        * (unsigned short *) (p) = ((c1)<<8 | (c0))
#  define SET4(p, c0, c1, c2, c3) \
        * (uint32_t *) (p) = ((c3)<<24 | (c2)<<16 | (c1)<<8 | (c0))
#  define SET3(p, c0, c1, c2) \
        SET4(p, c0, c1, c2, 0)
#else
#  define SET2(p, c0, c1) \
        (p)[0] = c0, (p)[1] = c1
#  define SET3(p, c0, c1, c2) \
        (p)[0] = c0, (p)[1] = c1, (p)[2] = c2
#  define SET4(p, c0, c1, c2, c3) \
        (p)[0] = c0, (p)[1] = c1, (p)[2] = c2, (p)[3] = c3
#endif


// go_self is a separate subroutine to spread the burden of local arrays.
// Otherwise the size of the stack frame in upx_main exceeds 128 bytes,
// which causes too many offsets to expand from 1 byte to 4.

static int
#if (ACC_CC_GNUC >= 0x030300)
__attribute__((__noinline__, __used__, __stdcall__))
#endif
go_self(char const *tmpname, char *argv[], char *envp[])
{
    // Old FreeBSD does not have /proc/self, so use /proc/<pid> instead.

    // Open the temp file.
    int const fdi = open(tmpname, O_RDONLY, 0);

    if (0 <= fdi) {
        // 17 chars for "/proc/PPPPP/fd/XX" should be enough, but we
        // play safe in case there will be 32-bit pid_t at some time.
        //char procself_buf[17+1];
        char procself_buf[31+1];

        // Compute name of temp fdi.
        SET4(procself_buf + 0, '/', 'p', 'r', 'o');
        SET4(procself_buf + 4, 'c', '/',  0 ,  0 );
        {
            char *const procself = upx_itoa(getpid(), procself_buf + 6);
            SET4(procself, '/', 'f', 'd', '/');
            upx_itoa(fdi, procself + 4);
        }

        // Check for working /proc/self/fd/X by accessing the
        // temp file again, now via temp fdi.
        if (UPX2 == (unsigned) access(procself_buf, R_OK | X_OK)) {
            // Now it's safe to unlink the temp file (as it is still open).
            unlink(tmpname);
            // Set the file close-on-exec.
            fcntl(fdi, F_SETFD, FD_CLOEXEC);
            // Execute the original program via /proc/self/fd/X.
            execve(procself_buf, (char const *const *)argv, (char const *const *)envp);
            // NOTE: if we get here we've lost.
        }

        // The proc filesystem isn't working. No problem.
        close(fdi);
    }
    return fdi;
}


/*************************************************************************
// UPX & NRV stuff
**************************************************************************/

typedef void f_unfilter(
    nrv_byte *,  // also addvalue
    nrv_uint,
    unsigned cto8  // junk in high 24 bits
);
typedef int f_expand(
    const nrv_byte *src, nrv_uint  src_len,
          nrv_byte *dst, nrv_uint *dst_len, int method );


/*************************************************************************
// upx_main - called by our entry code
//
// This function is optimized for size.
**************************************************************************/

void upx_main(
    struct Extent xi,
    f_expand *const f_decompress,
    int junk2,
    f_unfilter *const f_unf,
    char *envp[],
    char *argv[],
    int argc
) __asm__("upx_main");
void upx_main(
    struct Extent xi,
    f_expand *const f_decompress,
    int junk,
    f_unfilter *const f_unf,
    char *envp[],
    char *argv[],
    int argc
)
{
    // file descriptor
    int fdo;

    // decompression buffer
    unsigned char *buf;

    char *tmpname;
    struct p_info header;

    // temporary file name
    char tmpname_buf[20];

    (void)junk;

    //
    // ----- Step 0: set /proc/self using /proc/<pid> -----
    //

    //personality(PER_LINUX);


    //
    // ----- Step 1: prepare input file -----
    //

    // Read header.
    {
        register char *__d0, *__d1;
        __asm__ __volatile__( "movsl; movsl; movsl"
            : "=&D" (__d0), "=&S" (__d1)
            : "0" (&header), "1" (xi.buf)
            : "memory");
        xi.buf   = __d1;
        xi.size -= sizeof(header);
    }

    // Paranoia. Make sure this is actually our expected executable
    // by checking the random program id. (The id is both stored
    // in the header and patched into this stub.)
    if (header.p_progid != UPX3)
        goto error1;


    //
    // ----- Step 2: prepare temporary output file -----
    //

    tmpname = tmpname_buf;
    SET4(tmpname + 0, '/', 't', 'm', 'p');
    SET4(tmpname + 4, '/', 'u', 'p', 'x');

    // Compute name of temporary output file in tmpname[].
    // Protect against Denial-of-Service attacks.
    {
        char *p = tmpname_buf + sizeof(tmpname_buf) - 1;

        // Compute the last 4 characters (20 bits) from getpid().
        uint32_t r = ascii5(p, (uint32_t)getpid(), 4); *p = '\0'; p -= 4;

        // Provide 4 random bytes from our program id.
        r ^= header.p_progid;
        // Mix in 4 runtime random bytes.
        // Don't consume precious bytes from /dev/urandom.
        {
#if 1
            struct timeval tv;
            gettimeofday(&tv, 0);
            r ^= (uint32_t) tv.tv_sec;
            r ^= ((uint32_t) tv.tv_usec) << 12;      // shift into high-bits
#else
            // using adjtimex() may cause portability problems
            struct timex tx;
            adjtimex(&tx);
            r ^= (uint32_t) tx.time.tv_sec;
            r ^= ((uint32_t) tx.time.tv_usec) << 12; // shift into high-bits
            r ^= (uint32_t) tx.errcnt;
#endif
        }
        // Compute 7 more characters from the 32 random bits.
        ascii5(p, r, 7);
    }

    // Just in case, remove the file.
    {
        int err = unlink(tmpname);
        if (err != -ENOENT && err != 0)
            goto error1;
    }

    // Create the temporary output file.
#if (USE_MMAP_FO)
    fdo = open(tmpname, O_RDWR | O_CREAT | O_EXCL, 0700);
#else
    fdo = open(tmpname, O_WRONLY | O_CREAT | O_EXCL, 0700);
#endif
#if 0
    // Save some bytes of code - the ftruncate() below will fail anyway.
    if (fdo < 0)
        goto error;
#endif

    // Set expected uncompressed file size.
    if (ftruncate(fdo, header.p_filesize) != 0)
        goto error;


    //
    // ----- Step 3: setup memory -----
    //

#if (USE_MMAP_FO)
    // FIXME: packer could set length
    buf = (unsigned char *)mmap(0, header.p_filesize,
        PROT_READ | PROT_WRITE, MAP_SHARED, fdo, 0);
    if ((unsigned long) buf >= (unsigned long) -4095)
        goto error;

    // Decompressor can overrun the output by 3 bytes.
    // Defend against SIGSEGV by using a scratch page.
    // FIXME: packer could set address delta
    mmap(buf + (PAGE_MASK & (header.p_filesize + ~PAGE_MASK)),
        -PAGE_MASK, PROT_READ | PROT_WRITE,
        MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0 );
#else
    // Temporary decompression buffer.
    // FIXME: packer could set length
    buf = mmap(0, (header.p_blocksize + OVERHEAD + ~PAGE_MASK) & PAGE_MASK,
        PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0 );
    if ((unsigned long) buf >= (unsigned long) -4095)
        goto error;
#endif

    //
    // ----- Step 4: decompress blocks -----
    //

    for (;;)
    {
        struct b_info h;

        // Read and check block sizes.
        {
            register char *__d0, *__d1;
            __asm__ __volatile__( "movsl; movsl; movsl"
                : "=&D" (__d0), "=&S" (__d1)
                : "0" (&h), "1" (xi.buf)
                : "memory");
            xi.buf   = __d1;
            xi.size -= sizeof(h);
        }
        if (h.sz_unc == 0)                      // uncompressed size 0 -> EOF
        {
            if (h.sz_cpr != UPX_MAGIC_LE32)     // h.sz_cpr must be h->magic
                goto error;
            if (header.p_filesize != 0)         // all bytes must be written
                goto error;
            break;
        }
        //   Note: if sz_unc == sz_cpr then the block was not
        //   compressible and is stored in its uncompressed form.

        if (h.sz_cpr > h.sz_unc || h.sz_cpr > header.p_blocksize)
            goto error;
        // Now we have:
        //   assert(h.sz_cpr <= h.sz_unc);
        //   assert(h.sz_unc > 0 && h.sz_unc <= blocksize);
        //   assert(h.sz_cpr > 0 && h.sz_cpr <= blocksize);

        if (h.sz_cpr < h.sz_unc) { // Decompress block.
            nrv_uint out_len = h.sz_unc;  // EOF for lzma
            int i = (*f_decompress)((unsigned char *)xi.buf, h.sz_cpr, buf,
                &out_len, *(int *)(void *)&h.b_method);
            if (i != 0 || out_len != (nrv_uint)h.sz_unc)
                goto error;
            // Right now, unfilter is combined with decompression.
            // (*f_unfilter)(buf, out_len, cto8);
            (void)f_unf;
        }
        else
        {
            // Incompressible block
#if (USE_MMAP_FO)
            //memcpy(buf, xi.buf, h.sz_unc);
            register unsigned long int __d0, __d1, __d2;
            __asm__ __volatile__( "rep; movsb"
                : "=&c" (__d0), "=&D" (__d1), "=&S" (__d2)
                : "0" (h.sz_unc), "1" (buf), "2" (xi.buf)
                : "memory");
#endif
        }

#if (USE_MMAP_FO)
        // unmap part of the output
        munmap(buf, h.sz_unc);
        buf     += h.sz_unc;
#else
        // write output file
        if (xwrite(fdo, buf, h.sz_unc) != 0)
            goto error;
#endif

        header.p_filesize -= h.sz_unc;

        xi.buf  += h.sz_cpr;
        xi.size -= h.sz_cpr;

        if (xi.size < 0) {
// error exit is here in the middle to keep the jumps short.
        error:
            (void) unlink(tmpname);
        error1:
            // Note: the kernel will close all open files and
            //       unmap any allocated memory.
            for (;;)
                (void) exit(127);
        }
    }

    //
    // ----- Step 5: release resources -----
    //


#if !(USE_MMAP_FO)
    // Free our temporary decompression buffer.
    munmap(buf, malloc_args.ma_length);
#endif

    if (close(fdo) != 0)
        goto error;


    //
    // ----- Step 6: try to start program via /proc/self/fd/X -----
    //

    // Many thanks to Andi Kleen <ak@muc.de> and
    // Jamie Lokier <nospam@cern.ch> for this nice idea.

    if (0 > go_self(tmpname, argv, envp))
        goto error;


    //
    // ----- Step 7: start program in /tmp  -----
    //

    // Fork off a subprocess to clean up.
    // We have to do this double-fork trick to keep a zombie from
    // hanging around if the spawned original program doesn't check for
    // subprocesses (as well as to prevent the real program from getting
    // confused about this subprocess it shouldn't have).
    // Thanks to Adam Ierymenko <api@one.net> for this solution.

    if (fork() == 0)
    {
        if (fork() == 0)
        {
            // Sleep 3 seconds, then remove the temp file.
            struct timespec ts; ts.tv_sec = UPX4; ts.tv_nsec = 0;
            nanosleep(&ts, 0);
            unlink(tmpname);
        }
        exit(0);
    }

    // Wait for the first fork()'d process to die.
    waitpid(-1, (int *)0, 0);

    // Execute the original program.
    (void)argc;
    execve(tmpname, (char const *const *)argv, (char const *const *)envp);


    //
    // ----- Step 8: error exit -----
    //

    // If we return from execve() there was an error. Give up.
    goto error;
}

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