// ELF program loader #define _XOPEN_SOURCE 500 #include <unistd.h>#include <string.h>#include <fcntl.h>#include <errno.h>#include <stdio.h>#include <stdlib.h>#include <assert.h> #include "vx32.h"#include "vx32impl.h"#include "elf.h"#include "words.h" #define VX32_ARG_MAX 10*1024#define VX32_STACK 64*1024#define VXMEMSIZE (1<<30) int vx_elfbigmem; static int elfloader(vxproc *p, ssize_t (*readcb)(void*, off_t, void*, size_t), void*, const char *const *argv, const char *const *envp); // From a file. struct filedesc{ int fd;}; static ssize_t loadfilecb(void *cbdata, off_t offset, void *buf, size_t size){ ssize_t rc; struct filedesc *desc; desc = cbdata; rc = pread(desc->fd, buf, size, offset); return rc;} int vxproc_loadelffile(vxproc *p, const char *file, const char *const *argv, const char *const *envp){ int fd, rc; struct filedesc desc; if ((fd = open(file, O_RDONLY)) < 0) return -1; desc.fd = fd; rc = elfloader(p, loadfilecb, &desc, argv, envp); close(fd); return rc;} // From memory. struct memdesc{ const void *buf; size_t size;}; static ssize_t loadmemcb(void *cbdata, off_t offset, void *buf, size_t size){ struct memdesc *desc; desc = cbdata; if (offset >= desc->size || offset + size >= desc->size) return 0; memmove(buf, desc->buf + offset, size); return size;} int vxproc_loadelfmem(vxproc *p, const void *exe, size_t size, const char *const *argv, const char *const *envp){ struct memdesc desc; desc.buf = exe; desc.size = size; return elfloader(p, loadmemcb, &desc, argv, envp);} // In general. // Count number of args in array.static int countargs(const char *const *argv){ int i; for(i=0; argv[i]; i++) ; return i;} // Copy the strings from argv onto the stack, recording// their guest address in gargv.static int copystrings(uint8_t *base, uint32_t *espp, int argc, const char *const argv[], uint32_t *gargv){ int i; uint32_t esp = *espp; for (i = argc-1; i >= 0; i--) { int len = strlen(argv[i]); if (len + 4096 > esp) return -1; esp -= len+1; memmove(base+esp, argv[i], len+1); gargv[i] = esp; } *espp = esp; return 0;} // Copy pre-translated pointer array into guest address spacestatic int copyptrs(uint8_t *base, uint32_t *espp, int argc, uint32_t *gargv){ uint32_t esp = *espp; esp &= ~3; // align if (argc * 4 + 4096 > esp) return -1; esp -= argc*4; memmove(base+esp, gargv, argc*4); *espp = esp; return 0;} #define ELF_MAX_PH 32 static int elfloader(vxproc *proc, ssize_t (*readcb)(void*, off_t, void*, size_t), void *cbdata, const char *const *argv, const char *const *envp){ vxmem *mem; int i; size_t size; ssize_t act; struct Proghdr ph[ELF_MAX_PH]; vxmmap *mm; static const char *null; if (argv == NULL) argv = &null; if (envp == NULL) envp = &null; size = 4096; // For "big mem" we need more than 1/2 GB, but 64-bit Linux // has only about 1 GB of address space to give out with MAP_32BIT, // and we've used up some of it for the vxemu structure. // Used to ask for (1<<30) - (1<<24), which should be close enough to 1GB // to run the SPEC programs but leave enough for things like vxemu. // On Ubuntu 8.10, I get intermittent ouf of memory errors from this // mmap, so back off to 1<<29. if (vx_elfbigmem) size = (1<<29); mm = NULL; if ((mem = vxmem_chunk_new(size)) == NULL) return -1; // Read and check the ELF header struct Elf32 h; act = readcb(cbdata, 0, &h, sizeof(h)); if (act < 0) goto error; if (act < sizeof(h)) { noexec: errno = ENOEXEC; goto error; } if (ltoh32(h.e_magic) != ELF_MAGIC) goto noexec; // Read the program header table off_t phoff = ltoh32(h.e_phoff); size_t phnum = ltoh16(h.e_phnum); if (phnum <= 0 || phnum > ELF_MAX_PH) // arbitrary limit for security goto noexec; act = readcb(cbdata, phoff, ph, phnum * sizeof ph[0]); if (act < 0) goto error; if (act < phnum * sizeof ph[0]) goto noexec; // Load each program segment size_t stackhi = VXMEMSIZE; size_t addrhi = 0; for (i = 0; i < phnum; i++) { const struct Proghdr *p = &ph[i]; if (ltoh32(p->p_type) != ELF_PROG_LOAD) continue; // Validate the program segment off_t offset = ltoh32(p->p_offset); size_t filesz = ltoh32(p->p_filesz); size_t va = ltoh32(p->p_va); size_t memsz = ltoh32(p->p_memsz); if (filesz > memsz) goto noexec; // Validate the memory page region the segment loads into size_t memlo = VXPAGETRUNC(va); size_t memhi = VXPAGEROUND(va + memsz); if (memlo > VXMEMSIZE || memhi > VXMEMSIZE || memhi < memlo) goto noexec; // Make sure the VX process is big enough, and mapped if (size < memhi) { if (mm) { vxmem_unmap(mem, mm); mm = NULL; } if (vxmem_resize(mem, memhi) < 0) goto error; size = memhi; } if (mm == NULL) { mm = vxmem_map(mem, 0); if (mm == NULL) goto error; } // Temporarily give ourselves write permissions // on the segment in order to load it. if (vxmem_setperm(mem, memlo, memhi - memlo, VXPERM_READ | VXPERM_WRITE) < 0) return -1; // Load the segment. // Any bss portion is already cleared automatically // by virtue of the vxproc_clear() above. act = readcb(cbdata, offset, mm->base + va, filesz); if (act < 0) return -1; if (act < filesz) goto noexec; // Set permissions appropriately on the segment's pages int flags = ltoh32(p->p_flags); int perm = 0; switch (flags & (ELF_PROG_FLAG_READ | ELF_PROG_FLAG_WRITE | ELF_PROG_FLAG_EXEC)) { case ELF_PROG_FLAG_READ: perm = VXPERM_READ; break; case ELF_PROG_FLAG_READ | ELF_PROG_FLAG_WRITE: perm = VXPERM_READ | VXPERM_WRITE; break; case ELF_PROG_FLAG_READ | ELF_PROG_FLAG_EXEC: perm = VXPERM_READ | VXPERM_EXEC; break; default: goto noexec; // invalid perms } if (vxmem_setperm(mem, memlo, memhi - memlo, perm) < 0) goto error; // Find the lowest-used va, for locating the stack if (va < stackhi) stackhi = va; // Find the highest va too if (memhi > addrhi) addrhi = memhi; } if (size > addrhi) vxmem_setperm(mem, addrhi, size - addrhi, VXPERM_READ|VXPERM_WRITE); if (mm == NULL) { mm = vxmem_map(mem, 0); if (mm == NULL) goto error; } // Set up the process's stack, // growing downward from the executable's base load address. // Initially we enable read/write access on 64K of stack, // but the process is free to change that if it wants a bigger stack. stackhi = VXPAGETRUNC(stackhi); if (stackhi < VX32_STACK) goto noexec; if (vxmem_setperm(mem, stackhi - VX32_STACK, VX32_STACK, VXPERM_READ | VXPERM_WRITE) < 0) goto error; proc->cpu->reg[ESP] = stackhi; // Push the argument and environment arrays on the stack. uint32_t esp = stackhi; uint32_t argc; uint32_t envc; argc = countargs(argv); envc = countargs(envp); uint32_t *argvenv = malloc((argc+1+envc+1)*sizeof argvenv[0]); if (argv == NULL) goto error; if (copystrings(mm->base, &esp, envc, envp, argvenv+argc+1) < 0 || copystrings(mm->base, &esp, argc, argv, argvenv) < 0) { free(argvenv); goto error; } if (copyptrs(mm->base, &esp, argc+1+envc+1, argvenv) < 0) { free(argvenv); goto error; } // Set up stack just like Linux: argc, then argv pointers begin, then env pointers. esp -= 4; *(uint32_t*)(mm->base+esp) = argc; if (proc->mem) vxmem_free(proc->mem); proc->mem = mem; // Set up the process's initial register state for (i = 0; i < 8; i++) proc->cpu->reg[i] = 0; proc->cpu->reg[ESP] = esp; proc->cpu->eflags = 0; proc->cpu->eip = ltoh32(h.e_entry); return 0; error: if (mm) vxmem_unmap(mem, mm); vxmem_free(mem); return -1;}
// ELF program loader
#define _XOPEN_SOURCE 500
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "vx32.h"
#include "vx32impl.h"
#include "elf.h"
#include "words.h"
#define VX32_ARG_MAX 10*1024
#define VX32_STACK 64*1024
#define VXMEMSIZE (1<<30)
int vx_elfbigmem;
static int elfloader(vxproc *p,
ssize_t (*readcb)(void*, off_t, void*, size_t), void*,
const char *const *argv, const char *const *envp);
// From a file.
struct filedesc
{
int fd;
};
static ssize_t loadfilecb(void *cbdata, off_t offset, void *buf, size_t size)
{
ssize_t rc;
struct filedesc *desc;
desc = cbdata;
rc = pread(desc->fd, buf, size, offset);
return rc;
}
int vxproc_loadelffile(vxproc *p, const char *file,
const char *const *argv, const char *const *envp)
{
int fd, rc;
struct filedesc desc;
if ((fd = open(file, O_RDONLY)) < 0)
return -1;
desc.fd = fd;
rc = elfloader(p, loadfilecb, &desc, argv, envp);
close(fd);
return rc;
}
// From memory.
struct memdesc
{
const void *buf;
size_t size;
};
static ssize_t loadmemcb(void *cbdata, off_t offset, void *buf, size_t size)
{
struct memdesc *desc;
desc = cbdata;
if (offset >= desc->size || offset + size >= desc->size)
return 0;
memmove(buf, desc->buf + offset, size);
return size;
}
int vxproc_loadelfmem(vxproc *p, const void *exe, size_t size,
const char *const *argv, const char *const *envp)
{
struct memdesc desc;
desc.buf = exe;
desc.size = size;
return elfloader(p, loadmemcb, &desc, argv, envp);
}
// In general.
// Count number of args in array.
static int countargs(const char *const *argv)
{
int i;
for(i=0; argv[i]; i++)
;
return i;
}
// Copy the strings from argv onto the stack, recording
// their guest address in gargv.
static int copystrings(uint8_t *base, uint32_t *espp, int argc, const char *const argv[], uint32_t *gargv)
{
int i;
uint32_t esp = *espp;
for (i = argc-1; i >= 0; i--) {
int len = strlen(argv[i]);
if (len + 4096 > esp)
return -1;
esp -= len+1;
memmove(base+esp, argv[i], len+1);
gargv[i] = esp;
}
*espp = esp;
return 0;
}
// Copy pre-translated pointer array into guest address space
static int copyptrs(uint8_t *base, uint32_t *espp, int argc, uint32_t *gargv)
{
uint32_t esp = *espp;
esp &= ~3; // align
if (argc * 4 + 4096 > esp)
return -1;
esp -= argc*4;
memmove(base+esp, gargv, argc*4);
*espp = esp;
return 0;
}
#define ELF_MAX_PH 32
static int elfloader(vxproc *proc,
ssize_t (*readcb)(void*, off_t, void*, size_t),
void *cbdata,
const char *const *argv, const char *const *envp)
{
vxmem *mem;
int i;
size_t size;
ssize_t act;
struct Proghdr ph[ELF_MAX_PH];
vxmmap *mm;
static const char *null;
if (argv == NULL)
argv = &null;
if (envp == NULL)
envp = &null;
size = 4096;
// For "big mem" we need more than 1/2 GB, but 64-bit Linux
// has only about 1 GB of address space to give out with MAP_32BIT,
// and we've used up some of it for the vxemu structure.
// Used to ask for (1<<30) - (1<<24), which should be close enough to 1GB
// to run the SPEC programs but leave enough for things like vxemu.
// On Ubuntu 8.10, I get intermittent ouf of memory errors from this
// mmap, so back off to 1<<29.
if (vx_elfbigmem)
size = (1<<29);
mm = NULL;
if ((mem = vxmem_chunk_new(size)) == NULL)
return -1;
// Read and check the ELF header
struct Elf32 h;
act = readcb(cbdata, 0, &h, sizeof(h));
if (act < 0)
goto error;
if (act < sizeof(h)) {
noexec:
errno = ENOEXEC;
goto error;
}
if (ltoh32(h.e_magic) != ELF_MAGIC)
goto noexec;
// Read the program header table
off_t phoff = ltoh32(h.e_phoff);
size_t phnum = ltoh16(h.e_phnum);
if (phnum <= 0 || phnum > ELF_MAX_PH) // arbitrary limit for security
goto noexec;
act = readcb(cbdata, phoff, ph, phnum * sizeof ph[0]);
if (act < 0)
goto error;
if (act < phnum * sizeof ph[0])
goto noexec;
// Load each program segment
size_t stackhi = VXMEMSIZE;
size_t addrhi = 0;
for (i = 0; i < phnum; i++) {
const struct Proghdr *p = &ph[i];
if (ltoh32(p->p_type) != ELF_PROG_LOAD)
continue;
// Validate the program segment
off_t offset = ltoh32(p->p_offset);
size_t filesz = ltoh32(p->p_filesz);
size_t va = ltoh32(p->p_va);
size_t memsz = ltoh32(p->p_memsz);
if (filesz > memsz)
goto noexec;
// Validate the memory page region the segment loads into
size_t memlo = VXPAGETRUNC(va);
size_t memhi = VXPAGEROUND(va + memsz);
if (memlo > VXMEMSIZE || memhi > VXMEMSIZE || memhi < memlo)
goto noexec;
// Make sure the VX process is big enough, and mapped
if (size < memhi) {
if (mm) {
vxmem_unmap(mem, mm);
mm = NULL;
}
if (vxmem_resize(mem, memhi) < 0)
goto error;
size = memhi;
}
if (mm == NULL) {
mm = vxmem_map(mem, 0);
if (mm == NULL)
goto error;
}
// Temporarily give ourselves write permissions
// on the segment in order to load it.
if (vxmem_setperm(mem, memlo, memhi - memlo,
VXPERM_READ | VXPERM_WRITE) < 0)
return -1;
// Load the segment.
// Any bss portion is already cleared automatically
// by virtue of the vxproc_clear() above.
act = readcb(cbdata, offset, mm->base + va, filesz);
if (act < 0)
return -1;
if (act < filesz)
goto noexec;
// Set permissions appropriately on the segment's pages
int flags = ltoh32(p->p_flags);
int perm = 0;
switch (flags & (ELF_PROG_FLAG_READ | ELF_PROG_FLAG_WRITE |
ELF_PROG_FLAG_EXEC)) {
case ELF_PROG_FLAG_READ:
perm = VXPERM_READ;
break;
case ELF_PROG_FLAG_READ | ELF_PROG_FLAG_WRITE:
perm = VXPERM_READ | VXPERM_WRITE;
break;
case ELF_PROG_FLAG_READ | ELF_PROG_FLAG_EXEC:
perm = VXPERM_READ | VXPERM_EXEC;
break;
default:
goto noexec; // invalid perms
}
if (vxmem_setperm(mem, memlo, memhi - memlo, perm) < 0)
goto error;
// Find the lowest-used va, for locating the stack
if (va < stackhi)
stackhi = va;
// Find the highest va too
if (memhi > addrhi)
addrhi = memhi;
}
if (size > addrhi)
vxmem_setperm(mem, addrhi, size - addrhi, VXPERM_READ|VXPERM_WRITE);
if (mm == NULL) {
mm = vxmem_map(mem, 0);
if (mm == NULL)
goto error;
}
// Set up the process's stack,
// growing downward from the executable's base load address.
// Initially we enable read/write access on 64K of stack,
// but the process is free to change that if it wants a bigger stack.
stackhi = VXPAGETRUNC(stackhi);
if (stackhi < VX32_STACK)
goto noexec;
if (vxmem_setperm(mem, stackhi - VX32_STACK, VX32_STACK,
VXPERM_READ | VXPERM_WRITE) < 0)
goto error;
proc->cpu->reg[ESP] = stackhi;
// Push the argument and environment arrays on the stack.
uint32_t esp = stackhi;
uint32_t argc;
uint32_t envc;
argc = countargs(argv);
envc = countargs(envp);
uint32_t *argvenv = malloc((argc+1+envc+1)*sizeof argvenv[0]);
if (argv == NULL)
goto error;
if (copystrings(mm->base, &esp, envc, envp, argvenv+argc+1) < 0 ||
copystrings(mm->base, &esp, argc, argv, argvenv) < 0) {
free(argvenv);
goto error;
}
if (copyptrs(mm->base, &esp, argc+1+envc+1, argvenv) < 0) {
free(argvenv);
goto error;
}
// Set up stack just like Linux: argc, then argv pointers begin, then env pointers.
esp -= 4;
*(uint32_t*)(mm->base+esp) = argc;
if (proc->mem)
vxmem_free(proc->mem);
proc->mem = mem;
// Set up the process's initial register state
for (i = 0; i < 8; i++)
proc->cpu->reg[i] = 0;
proc->cpu->reg[ESP] = esp;
proc->cpu->eflags = 0;
proc->cpu->eip = ltoh32(h.e_entry);
return 0;
error:
if (mm)
vxmem_unmap(mem, mm);
vxmem_free(mem);
return -1;
}