vx32

Local 9vx git repository for patches.
git clone git://r-36.net/vx32
Log | Files | Refs

elf.c (7711B)


      1 // ELF program loader
      2 
      3 #define _XOPEN_SOURCE 500
      4 
      5 #include <unistd.h>
      6 #include <string.h>
      7 #include <fcntl.h>
      8 #include <errno.h>
      9 #include <stdio.h>
     10 #include <stdlib.h>
     11 #include <assert.h>
     12 
     13 #include "vx32.h"
     14 #include "vx32impl.h"
     15 #include "elf.h"
     16 #include "words.h"
     17 
     18 #define VX32_ARG_MAX 10*1024
     19 #define VX32_STACK 64*1024
     20 #define VXMEMSIZE (1<<30)
     21 
     22 int vx_elfbigmem;
     23 
     24 static int elfloader(vxproc *p,
     25 	ssize_t (*readcb)(void*, off_t, void*, size_t), void*,
     26 	const char *const *argv, const char *const *envp);
     27 
     28 // From a file.
     29 
     30 struct filedesc
     31 {
     32 	int fd;
     33 };
     34 
     35 static ssize_t loadfilecb(void *cbdata, off_t offset, void *buf, size_t size)
     36 {
     37 	ssize_t rc;
     38 	struct filedesc *desc;
     39 	
     40 	desc = cbdata;
     41 	rc = pread(desc->fd, buf, size, offset);
     42 	return rc;
     43 }
     44 
     45 int vxproc_loadelffile(vxproc *p, const char *file,
     46 	const char *const *argv, const char *const *envp)
     47 {
     48 	int fd, rc;
     49 	struct filedesc desc;
     50 	
     51 	if ((fd = open(file, O_RDONLY)) < 0)
     52 		return -1;
     53 	desc.fd = fd;
     54 	rc = elfloader(p, loadfilecb, &desc, argv, envp);
     55 	close(fd);
     56 	return rc;
     57 }
     58 
     59 // From memory.
     60 
     61 struct memdesc
     62 {
     63 	const void *buf;
     64 	size_t size;
     65 };
     66 
     67 static ssize_t loadmemcb(void *cbdata, off_t offset, void *buf, size_t size)
     68 {
     69 	struct memdesc *desc;
     70 	
     71 	desc = cbdata;
     72 	if (offset >= desc->size || offset + size >= desc->size)
     73 		return 0;
     74 	memmove(buf, desc->buf + offset, size);
     75 	return size;
     76 }
     77 
     78 int vxproc_loadelfmem(vxproc *p, const void *exe, size_t size,
     79 	const char *const *argv, const char *const *envp)
     80 {
     81 	struct memdesc desc;
     82 	
     83 	desc.buf = exe;
     84 	desc.size = size;
     85 	return elfloader(p, loadmemcb, &desc, argv, envp);
     86 }
     87 
     88 
     89 // In general.
     90 
     91 // Count number of args in array.
     92 static int countargs(const char *const *argv)
     93 {
     94 	int i;
     95 	
     96 	for(i=0; argv[i]; i++)
     97 		;
     98 	return i;
     99 }
    100 
    101 // Copy the strings from argv onto the stack, recording
    102 // their guest address in gargv.
    103 static int copystrings(uint8_t *base, uint32_t *espp, int argc, const char *const argv[], uint32_t *gargv)
    104 {
    105 	int i;
    106 	uint32_t esp = *espp;
    107 	for (i = argc-1; i >= 0; i--) {
    108 		int len = strlen(argv[i]);
    109 		if (len + 4096 > esp)
    110 			return -1;
    111 		esp -= len+1;
    112 		memmove(base+esp, argv[i], len+1);
    113 		gargv[i] = esp;
    114 	}
    115 	*espp = esp;
    116 	return 0;
    117 }
    118 
    119 // Copy pre-translated pointer array into guest address space
    120 static int copyptrs(uint8_t *base, uint32_t *espp, int argc, uint32_t *gargv)
    121 {
    122 	uint32_t esp = *espp;
    123 	esp &= ~3;  // align
    124 	if (argc * 4 + 4096 > esp)
    125 		return -1;
    126 	esp -= argc*4;
    127 	memmove(base+esp, gargv, argc*4);
    128 	*espp = esp;
    129 	return 0;
    130 }
    131 
    132 #define ELF_MAX_PH 32
    133 
    134 static int elfloader(vxproc *proc,
    135               ssize_t (*readcb)(void*, off_t, void*, size_t),
    136               void *cbdata,
    137               const char *const *argv, const char *const *envp)
    138 {
    139 	vxmem *mem;
    140 	int i;
    141 	size_t size;
    142 	ssize_t act;
    143 	struct Proghdr ph[ELF_MAX_PH];
    144 	vxmmap *mm;
    145 	static const char *null;
    146 
    147 	if (argv == NULL)
    148 		argv = &null;
    149 	if (envp == NULL)
    150 		envp = &null;
    151 
    152 	size = 4096;
    153 	// For "big mem" we need more than 1/2 GB, but 64-bit Linux
    154 	// has only about 1 GB of address space to give out with MAP_32BIT,
    155 	// and we've used up some of it for the vxemu structure.  
    156 	// Used to ask for (1<<30) - (1<<24), which should be close enough to 1GB
    157 	// to run the SPEC programs but leave enough for things like vxemu.
    158 	// On Ubuntu 8.10, I get intermittent ouf of memory errors from this
    159 	// mmap, so back off to 1<<29.
    160 	if (vx_elfbigmem)
    161 		size = (1<<29);
    162 
    163 	mm = NULL;
    164 
    165 	if ((mem = vxmem_chunk_new(size)) == NULL)
    166 		return -1;
    167 
    168 	// Read and check the ELF header
    169 	struct Elf32 h;
    170 	act = readcb(cbdata, 0, &h, sizeof(h));
    171 	if (act < 0)
    172 		goto error;
    173 	if (act < sizeof(h)) {
    174 	noexec:
    175 		errno = ENOEXEC;
    176 		goto error;
    177 	}
    178 	if (ltoh32(h.e_magic) != ELF_MAGIC)
    179 		goto noexec;
    180 
    181 	// Read the program header table
    182 	off_t phoff = ltoh32(h.e_phoff);
    183 	size_t phnum = ltoh16(h.e_phnum);
    184 	if (phnum <= 0 || phnum > ELF_MAX_PH)	// arbitrary limit for security
    185 		goto noexec;
    186 
    187 	act = readcb(cbdata, phoff, ph, phnum * sizeof ph[0]);
    188 	if (act < 0)
    189 		goto error;
    190 	if (act < phnum * sizeof ph[0])
    191 		goto noexec;
    192 	
    193 	// Load each program segment
    194 	size_t stackhi = VXMEMSIZE;
    195 	size_t addrhi = 0;
    196 	for (i = 0; i < phnum; i++) {
    197 		const struct Proghdr *p = &ph[i];
    198 		if (ltoh32(p->p_type) != ELF_PROG_LOAD)
    199 			continue;
    200 
    201 		// Validate the program segment
    202 		off_t offset = ltoh32(p->p_offset);
    203 		size_t filesz = ltoh32(p->p_filesz);
    204 		size_t va = ltoh32(p->p_va);
    205 		size_t memsz = ltoh32(p->p_memsz);
    206 		if (filesz > memsz)
    207 			goto noexec;
    208 
    209 		// Validate the memory page region the segment loads into
    210 		size_t memlo = VXPAGETRUNC(va);
    211 		size_t memhi = VXPAGEROUND(va + memsz);
    212 		if (memlo > VXMEMSIZE || memhi > VXMEMSIZE || memhi < memlo)
    213 			goto noexec;
    214 
    215 		// Make sure the VX process is big enough, and mapped
    216 		if (size < memhi) {
    217 			if (mm) {
    218 				vxmem_unmap(mem, mm);
    219 				mm = NULL;
    220 			}
    221 			if (vxmem_resize(mem, memhi) < 0)
    222 				goto error;
    223 			size = memhi;
    224 		}
    225 
    226 		if (mm == NULL) {
    227 			mm = vxmem_map(mem, 0);
    228 			if (mm == NULL)
    229 				goto error;
    230 		}
    231 
    232 		// Temporarily give ourselves write permissions
    233 		// on the segment in order to load it.
    234 		if (vxmem_setperm(mem, memlo, memhi - memlo,
    235 				VXPERM_READ | VXPERM_WRITE) < 0)
    236 			return -1;
    237 
    238 		// Load the segment.
    239 		// Any bss portion is already cleared automatically
    240 		// by virtue of the vxproc_clear() above.
    241 		act = readcb(cbdata, offset, mm->base + va, filesz);
    242 		if (act < 0)
    243 			return -1;
    244 		if (act < filesz)
    245 			goto noexec;
    246 
    247 		// Set permissions appropriately on the segment's pages
    248 		int flags = ltoh32(p->p_flags);
    249 		int perm = 0;
    250 		switch (flags & (ELF_PROG_FLAG_READ | ELF_PROG_FLAG_WRITE |
    251 				ELF_PROG_FLAG_EXEC)) {
    252 		case ELF_PROG_FLAG_READ:
    253 			perm = VXPERM_READ;
    254 			break;
    255 		case ELF_PROG_FLAG_READ | ELF_PROG_FLAG_WRITE:
    256 			perm = VXPERM_READ | VXPERM_WRITE;
    257 			break;
    258 		case ELF_PROG_FLAG_READ | ELF_PROG_FLAG_EXEC:
    259 			perm = VXPERM_READ | VXPERM_EXEC;
    260 			break;
    261 		default:
    262 			goto noexec;	// invalid perms
    263 		}
    264 		if (vxmem_setperm(mem, memlo, memhi - memlo, perm) < 0)
    265 			goto error;
    266 
    267 		// Find the lowest-used va, for locating the stack
    268 		if (va < stackhi)
    269 			stackhi = va;
    270 
    271 		// Find the highest va too
    272 		if (memhi > addrhi)
    273 			addrhi = memhi;
    274 	}
    275 
    276 	if (size > addrhi)
    277 		vxmem_setperm(mem, addrhi, size - addrhi, VXPERM_READ|VXPERM_WRITE);
    278 
    279 	if (mm == NULL) {
    280 		mm = vxmem_map(mem, 0);
    281 		if (mm == NULL)
    282 			goto error;
    283 	}
    284 
    285 	// Set up the process's stack,
    286 	// growing downward from the executable's base load address.
    287 	// Initially we enable read/write access on 64K of stack,
    288 	// but the process is free to change that if it wants a bigger stack.
    289 	stackhi = VXPAGETRUNC(stackhi);
    290 	if (stackhi < VX32_STACK)
    291 		goto noexec;
    292 	if (vxmem_setperm(mem, stackhi - VX32_STACK, VX32_STACK,
    293 			VXPERM_READ | VXPERM_WRITE) < 0)
    294 		goto error;
    295 	proc->cpu->reg[ESP] = stackhi;
    296 
    297 	// Push the argument and environment arrays on the stack.
    298 	uint32_t esp = stackhi;
    299 	uint32_t argc;
    300 	uint32_t envc;
    301 	argc = countargs(argv);
    302 	envc = countargs(envp);
    303 	uint32_t *argvenv = malloc((argc+1+envc+1)*sizeof argvenv[0]);
    304 	if (argv == NULL)
    305 		goto error;
    306 	if (copystrings(mm->base, &esp, envc, envp, argvenv+argc+1) < 0 ||
    307 	    copystrings(mm->base, &esp, argc, argv, argvenv) < 0) {
    308 		free(argvenv);
    309 		goto error;
    310 	}
    311 	if (copyptrs(mm->base, &esp, argc+1+envc+1, argvenv) < 0) {
    312 		free(argvenv);
    313 		goto error;
    314 	}
    315 	
    316 	// Set up stack just like Linux: argc, then argv pointers begin, then env pointers.
    317 	esp -= 4;
    318 	*(uint32_t*)(mm->base+esp) = argc;
    319 
    320 	if (proc->mem)
    321 		vxmem_free(proc->mem);
    322 	proc->mem = mem;
    323 	
    324 	// Set up the process's initial register state
    325 	for (i = 0; i < 8; i++)
    326 		proc->cpu->reg[i] = 0;
    327 	proc->cpu->reg[ESP] = esp;
    328 	proc->cpu->eflags = 0;
    329 	proc->cpu->eip = ltoh32(h.e_entry);
    330 
    331 	return 0;
    332 
    333 error:
    334 	if (mm)
    335 		vxmem_unmap(mem, mm);
    336 	vxmem_free(mem);
    337 	return -1;
    338 }
    339