#define ELF_MAX_PH 32static 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. // 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. if (vx_elfbigmem) size = (1<<30) - (1<<24); 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;}