diff options
Diffstat (limited to 'exec/exec.c')
-rw-r--r-- | exec/exec.c | 2065 |
1 files changed, 2065 insertions, 0 deletions
diff --git a/exec/exec.c b/exec/exec.c new file mode 100644 index 00000000..02ca43f8 --- /dev/null +++ b/exec/exec.c @@ -0,0 +1,2065 @@ +/* GNU Hurd standard exec server. + Copyright (C) 1992,93,94,95,96,98,99 Free Software Foundation, Inc. + Written by Roland McGrath. + + Can exec ELF format directly. + #ifdef GZIP + Can gunzip executables into core on the fly. + #endif + #ifdef BFD + Can exec any executable format the BFD library understands + to be for this flavor of machine. + #endif + #ifdef BZIP2 + Can bunzip2 executables into core on the fly. + #endif + +This file is part of the GNU Hurd. + +The GNU Hurd is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +The GNU Hurd 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 the GNU Hurd; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ + + + +#include "priv.h" +#include <hurd.h> +#include <hurd/exec.h> +#include <sys/stat.h> +#include <unistd.h> + +mach_port_t procserver; /* Our proc port. */ + +/* Standard exec data for secure execs. */ +mach_port_t *std_ports; +int *std_ints; +size_t std_nports, std_nints; +struct rwlock std_lock = RWLOCK_INITIALIZER; + + +#ifdef BFD +/* Return a Hurd error code corresponding to the most recent BFD error. */ +static error_t +b2he (error_t deflt) +{ + switch (bfd_get_error ()) + { + case bfd_error_system_call: + return errno; + + case bfd_error_no_memory: + return ENOMEM; + + default: + return deflt; + } +} +#else +#define b2he() a2he (errno) +#endif + +#ifdef GZIP +static void check_gzip (struct execdata *); +#endif + +#ifdef BZIP2 +static void check_bzip2 (struct execdata *); +#endif + +#ifdef BFD + +/* Check a section, updating the `locations' vector [BFD]. */ +static void +check_section (bfd *bfd, asection *sec, void *userdata) +{ + struct execdata *u = userdata; + vm_address_t addr; + static const union + { + char string[8]; + unsigned int quadword __attribute__ ((mode (DI))); + } interp = { string: ".interp" }; + + if (u->error) + return; + + /* Fast strcmp for this 8-byte constant string. */ + if (*(const __typeof (interp.quadword) *) sec->name == interp.quadword) + u->interp.section = sec; + + if (!(sec->flags & (SEC_ALLOC|SEC_LOAD)) || + (sec->flags & SEC_NEVER_LOAD)) + /* Nothing to do for this section. */ + return; + + addr = (vm_address_t) sec->vma; + + if (sec->flags & SEC_LOAD) + { + u->info.bfd_locations[sec->index] = sec->filepos; + if ((off_t) sec->filepos < 0 || (off_t) sec->filepos > u->file_size) + u->error = ENOEXEC; + } +} +#endif + + +/* Zero the specified region but don't crash the server if it faults. */ + +#include <hurd/sigpreempt.h> + +static error_t +safe_bzero (void *ptr, size_t size) +{ + return hurd_safe_memset (ptr, 0, size); +} + + +/* Load or allocate a section. */ +static void +load_section (void *section, struct execdata *u) +{ + vm_address_t addr = 0; + vm_offset_t filepos = 0; + vm_size_t filesz = 0, memsz = 0; + vm_prot_t vm_prot; + int anywhere; + vm_address_t mask = 0; +#ifdef BFD + asection *const sec = section; +#endif + const Elf32_Phdr *const ph = section; + + if (u->error) + return; + +#ifdef BFD + if (u->bfd && sec->flags & SEC_NEVER_LOAD) + /* Nothing to do for this section. */ + return; +#endif + + vm_prot = VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE; + +#ifdef BFD + if (u->bfd) + { + addr = (vm_address_t) sec->vma; + filepos = u->info.bfd_locations[sec->index]; + memsz = sec->_raw_size; + filesz = (sec->flags & SEC_LOAD) ? memsz : 0; + if (sec->flags & (SEC_READONLY|SEC_ROM)) + vm_prot &= ~VM_PROT_WRITE; + anywhere = 0; + } + else +#endif + { + addr = ph->p_vaddr & ~(ph->p_align - 1); + memsz = ph->p_vaddr + ph->p_memsz - addr; + filepos = ph->p_offset & ~(ph->p_align - 1); + filesz = ph->p_offset + ph->p_filesz - filepos; + if ((ph->p_flags & PF_R) == 0) + vm_prot &= ~VM_PROT_READ; + if ((ph->p_flags & PF_W) == 0) + vm_prot &= ~VM_PROT_WRITE; + if ((ph->p_flags & PF_X) == 0) + vm_prot &= ~VM_PROT_EXECUTE; + anywhere = u->info.elf.anywhere; + if (! anywhere) + addr += u->info.elf.loadbase; + else +#if 0 + switch (elf_machine) + { + case EM_386: + case EM_486: + /* On the i386, programs normally load at 0x08000000, and + expect their data segment to be able to grow dynamically + upward from its start near that address. We need to make + sure that the dynamic linker is not mapped in a conflicting + address. */ + /* mask = 0xf8000000UL; */ /* XXX */ + break; + default: + break; + } +#endif + if (anywhere && addr < vm_page_size) + addr = vm_page_size; + } + + if (memsz == 0) + /* This section is empty; ignore it. */ + return; + + if (filesz != 0) + { + vm_address_t mapstart = round_page (addr); + + /* Allocate space in the task and write CONTENTS into it. */ + void write_to_task (vm_address_t mapstart, vm_size_t size, + vm_prot_t vm_prot, vm_address_t contents) + { + vm_size_t off = size % vm_page_size; + /* Allocate with vm_map to set max protections. */ + u->error = vm_map (u->task, + &mapstart, size, mask, anywhere, + MACH_PORT_NULL, 0, 1, + vm_prot|VM_PROT_WRITE, + VM_PROT_READ|VM_PROT_WRITE|VM_PROT_EXECUTE, + VM_INHERIT_COPY); + if (! u->error && size >= vm_page_size) + u->error = vm_write (u->task, mapstart, contents, size - off); + if (! u->error && off != 0) + { + vm_address_t page = 0; + page = (vm_address_t) mmap (0, vm_page_size, + PROT_READ|PROT_WRITE, MAP_ANON, + 0, 0); + u->error = (page == -1) ? errno : 0; + if (! u->error) + { + memcpy ((void *) page, + (void *) (contents + (size - off)), + off); + u->error = vm_write (u->task, mapstart + (size - off), + page, vm_page_size); + munmap ((caddr_t) page, vm_page_size); + } + } + /* Reset the current protections to the desired state. */ + if (! u->error && (vm_prot & VM_PROT_WRITE) == 0) + u->error = vm_protect (u->task, mapstart, size, 0, vm_prot); + } + + if (mapstart - addr < filesz) + { + /* MAPSTART is the first page that starts inside the section. + Map all the pages that start inside the section. */ + +#define SECTION_IN_MEMORY_P (u->file_data != NULL) +#define SECTION_CONTENTS (u->file_data + filepos) + if (SECTION_IN_MEMORY_P) + /* Data is already in memory; write it into the task. */ + write_to_task (mapstart, filesz - (mapstart - addr), vm_prot, + (vm_address_t) SECTION_CONTENTS + + (mapstart - addr)); + else if (u->filemap != MACH_PORT_NULL) + /* Map the data into the task directly from the file. */ + u->error = vm_map (u->task, + &mapstart, filesz - (mapstart - addr), + mask, anywhere, + u->filemap, filepos + (mapstart - addr), 1, + vm_prot, + VM_PROT_READ|VM_PROT_WRITE|VM_PROT_EXECUTE, + VM_INHERIT_COPY); + else + { + /* Cannot map the data. Read it into a buffer and vm_write + it into the task. */ + void *buf; + const vm_size_t size = filesz - (mapstart - addr); + buf = mmap (0, size, PROT_READ|PROT_WRITE, MAP_ANON, 0, 0); + u->error = (buf == (caddr_t) -1) ? errno : 0; + if (! u->error) + { + if (fseek (&u->stream, + filepos + (mapstart - addr), SEEK_SET) || + fread (buf, size, 1, &u->stream) != 1) + u->error = errno; + else + write_to_task (mapstart, size, vm_prot, + (vm_address_t) buf); + munmap (buf, size); + } + } + if (u->error) + return; + + if (anywhere) + { + /* We let the kernel choose the location of the mapping. + Now record where it ended up. Later sections cannot + be mapped anywhere, they must come after this one. */ + u->info.elf.loadbase = mapstart; + addr = mapstart + (addr % vm_page_size); + anywhere = u->info.elf.anywhere = 0; + mask = 0; + } + } + + if (mapstart > addr) + { + /* We must read and copy in the space in the section before the + first page boundary. */ + vm_address_t overlap_page = trunc_page (addr); + vm_address_t ourpage = 0; + vm_size_t size = 0; + void *readaddr; + size_t readsize; + + u->error = vm_read (u->task, overlap_page, vm_page_size, + &ourpage, &size); + if (u->error) + { + if (u->error == KERN_INVALID_ADDRESS) + { + /* The space is unallocated. */ + u->error = vm_allocate (u->task, + &overlap_page, vm_page_size, 0); + size = vm_page_size; + if (!u->error) + { + ourpage = (vm_address_t) mmap (0, vm_page_size, + PROT_READ|PROT_WRITE, + MAP_ANON, 0, 0); + u->error = (ourpage == -1) ? errno : 0; + } + } + if (u->error) + { + maplose: + vm_deallocate (u->task, mapstart, filesz); + return; + } + } + + readaddr = (void *) (ourpage + (addr - overlap_page)); + readsize = size - (addr - overlap_page); + if (readsize > filesz) + readsize = filesz; + + if (SECTION_IN_MEMORY_P) + bcopy (SECTION_CONTENTS, readaddr, readsize); + else + if (fseek (&u->stream, filepos, SEEK_SET) || + fread (readaddr, readsize, 1, &u->stream) != 1) + { + u->error = errno; + goto maplose; + } + u->error = vm_write (u->task, overlap_page, ourpage, size); + if (u->error == KERN_PROTECTION_FAILURE) + { + /* The overlap page is not writable; the section + that appears in preceding memory is read-only. + Change the page's protection so we can write it. */ + u->error = vm_protect (u->task, overlap_page, size, + 0, vm_prot | VM_PROT_WRITE); + if (!u->error) + u->error = vm_write (u->task, overlap_page, ourpage, size); + /* If this section is not supposed to be writable either, + restore the page's protection to read-only. */ + if (!u->error && !(vm_prot & VM_PROT_WRITE)) + u->error = vm_protect (u->task, overlap_page, size, + 0, vm_prot); + } + munmap ((caddr_t) ourpage, size); + if (u->error) + goto maplose; + } + + if (u->cntl) + u->cntl->accessed = 1; + + /* Tell the code below to zero-fill the remaining area. */ + addr += filesz; + memsz -= filesz; + } + + if (memsz != 0) + { + /* SEC_ALLOC: Allocate zero-filled memory for the section. */ + + vm_address_t mapstart = round_page (addr); + + if (mapstart - addr < memsz) + { + /* MAPSTART is the first page that starts inside the section. + Allocate all the pages that start inside the section. */ + u->error = vm_map (u->task, &mapstart, memsz - (mapstart - addr), + mask, anywhere, MACH_PORT_NULL, 0, 1, + vm_prot, VM_PROT_ALL, VM_INHERIT_COPY); + if (u->error) + return; + } + + if (anywhere) + { + /* We let the kernel choose the location of the zero space. + Now record where it ended up. Later sections cannot + be mapped anywhere, they must come after this one. */ + u->info.elf.loadbase = mapstart; + addr = mapstart + (addr % vm_page_size); + anywhere = u->info.elf.anywhere = 0; + mask = 0; + } + + if (mapstart > addr) + { + /* Zero space in the section before the first page boundary. */ + vm_address_t overlap_page = trunc_page (addr); + vm_address_t ourpage = 0; + vm_size_t size = 0; + u->error = vm_read (u->task, overlap_page, vm_page_size, + &ourpage, &size); + if (u->error) + { + vm_deallocate (u->task, mapstart, memsz); + return; + } + u->error = safe_bzero ((void *) (ourpage + (addr - overlap_page)), + size - (addr - overlap_page)); + if (! u->error && !(vm_prot & VM_PROT_WRITE)) + u->error = vm_protect (u->task, overlap_page, size, + 0, VM_PROT_WRITE); + if (! u->error) + u->error = vm_write (u->task, overlap_page, ourpage, size); + if (! u->error && !(vm_prot & VM_PROT_WRITE)) + u->error = vm_protect (u->task, overlap_page, size, 0, vm_prot); + munmap ((caddr_t) ourpage, size); + } + } +} + +/* Make sure our mapping window (or read buffer) covers + LEN bytes of the file starting at POSN. */ + +static void * +map (struct execdata *e, off_t posn, size_t len) +{ + FILE *f = &e->stream; + const size_t size = e->file_size; + size_t offset; + + if ((f->__offset & ~(f->__bufsize - 1)) == (posn & ~(f->__bufsize - 1)) && + f->__buffer + (posn + len - f->__offset) < f->__get_limit) + /* The current mapping window covers it. */ + offset = posn & (f->__bufsize - 1); + else if (e->filemap == MACH_PORT_NULL) + { + /* No mapping for the file. Read the data by RPC. */ + char *buffer = f->__buffer; + mach_msg_type_number_t nread = f->__bufsize; + /* Read as much as we can get into the buffer right now. */ + e->error = io_read (e->file, &buffer, &nread, posn, round_page (len)); + if (e->error) + { + errno = e->error; + f->__error = 1; + return NULL; + } + if (buffer != f->__buffer) + { + /* The data was returned out of line. Discard the old buffer. */ + if (f->__bufsize != 0) + munmap (f->__buffer, f->__bufsize); + f->__buffer = buffer; + f->__bufsize = round_page (nread); + } + + f->__offset = posn; + f->__get_limit = f->__buffer + nread; + offset = 0; + } + else + { + /* Deallocate the old mapping area. */ + if (f->__buffer != NULL) + munmap (f->__buffer, f->__bufsize); + f->__buffer = NULL; + + /* Make sure our mapping is page-aligned in the file. */ + offset = posn & (vm_page_size - 1); + f->__offset = trunc_page (posn); + f->__bufsize = round_page (posn + len) - f->__offset; + + /* Map the data from the file. */ + if (vm_map (mach_task_self (), + (vm_address_t *) &f->__buffer, f->__bufsize, 0, 1, + e->filemap, f->__offset, 1, VM_PROT_READ, VM_PROT_READ, + VM_INHERIT_NONE)) + { + errno = e->error = EIO; + f->__error = 1; + return NULL; + } + + if (e->cntl) + e->cntl->accessed = 1; + + if (f->__offset + f->__bufsize > size) + f->__get_limit = f->__buffer + (size - f->__offset); + else + f->__get_limit = f->__buffer + f->__bufsize; + } + + f->__target = f->__offset; + f->__bufp = f->__buffer + offset; + + if (f->__bufp + len > f->__get_limit) + { + f->__eof = 1; + return NULL; + } + + return f->__bufp; +} + +/* stdio input-room function. */ +static int +input_room (FILE *f) +{ + return (map (f->__cookie, f->__target, 1) == NULL ? EOF : + (unsigned char) *f->__bufp++); +} + +static int +close_exec_stream (void *cookie) +{ + struct execdata *e = cookie; + + if (e->stream.__buffer != NULL) + munmap (e->stream.__buffer, e->stream.__bufsize); + + return 0; +} + +/* stdio seek function. */ +static int +fake_seek (void *cookie, fpos_t *pos, int whence) +{ + struct execdata *e = cookie; + + /* Set __target to match the specifed seek location */ + switch (whence) + { + case SEEK_END: + e->stream.__target = e->file_size + *pos; + break; + + case SEEK_CUR: + e->stream.__target += *pos; + break; + + case SEEK_SET: + e->stream.__target = *pos; + break; + } + *pos = e->stream.__target; + return 0; +} + + +/* Prepare to check and load FILE. */ +static void +prepare (file_t file, struct execdata *e) +{ + memory_object_t rd, wr; + + e->file = file; + +#ifdef BFD + e->bfd = NULL; +#endif + e->file_data = NULL; + e->cntl = NULL; + e->filemap = MACH_PORT_NULL; + e->cntlmap = MACH_PORT_NULL; + + e->interp.section = NULL; + + /* Initialize E's stdio stream. */ + memset (&e->stream, 0, sizeof (e->stream)); + e->stream.__magic = _IOMAGIC; + e->stream.__mode.__read = 1; + e->stream.__userbuf = 1; + e->stream.__room_funcs.__input = input_room; + e->stream.__io_funcs.seek = fake_seek; + e->stream.__io_funcs.close = close_exec_stream; + e->stream.__cookie = e; + e->stream.__seen = 1; + + /* Try to mmap FILE. */ + e->error = io_map (file, &rd, &wr); + if (! e->error) + /* Mapping is O.K. */ + { + if (wr != MACH_PORT_NULL) + mach_port_deallocate (mach_task_self (), wr); + if (rd == MACH_PORT_NULL) + { + e->error = EBADF; /* ? XXX */ + return; + } + e->filemap = rd; + + e->error = /* io_map_cntl (file, &e->cntlmap) */ EOPNOTSUPP; /* XXX */ + if (e->error) + { + /* No shared page. Do a stat to find the file size. */ + struct stat st; + e->error = io_stat (file, &st); + if (e->error) + return; + e->file_size = st.st_size; + e->optimal_block = st.st_blksize; + } + else + e->error = vm_map (mach_task_self (), (vm_address_t *) &e->cntl, + vm_page_size, 0, 1, e->cntlmap, 0, 0, + VM_PROT_READ|VM_PROT_WRITE, + VM_PROT_READ|VM_PROT_WRITE, VM_INHERIT_NONE); + + if (e->cntl) + while (1) + { + spin_lock (&e->cntl->lock); + switch (e->cntl->conch_status) + { + case USER_COULD_HAVE_CONCH: + e->cntl->conch_status = USER_HAS_CONCH; + case USER_HAS_CONCH: + spin_unlock (&e->cntl->lock); + /* Break out of the loop. */ + break; + case USER_RELEASE_CONCH: + case USER_HAS_NOT_CONCH: + default: /* Oops. */ + spin_unlock (&e->cntl->lock); + e->error = io_get_conch (e->file); + if (e->error) + return; + /* Continue the loop. */ + continue; + } + + /* Get here if we are now IT. */ + e->file_size = 0; + if (e->cntl->use_file_size) + e->file_size = e->cntl->file_size; + if (e->cntl->use_read_size && e->cntl->read_size > e->file_size) + e->file_size = e->cntl->read_size; + break; + } + } + else if (e->error == EOPNOTSUPP) + /* We can't mmap FILE, but perhaps we can do normal I/O to it. */ + e->error = 0; +} + +/* Check the magic number, etc. of the file. + On successful return, the caller must allocate the + E->locations vector, and map check_section over the BFD. */ + +#ifdef BFD +static void +check_bfd (struct execdata *e) +{ + bfd_set_error (bfd_error_no_error); + + e->bfd = bfd_openstreamr (NULL, NULL, &e->stream); + if (e->bfd == NULL) + { + e->error = b2he (ENOEXEC); + return; + } + + if (!bfd_check_format (e->bfd, bfd_object)) + { + e->error = b2he (ENOEXEC); + return; + } + else if (/* !(e->bfd->flags & EXEC_P) || XXX */ + (host_bfd.arch_info->compatible = e->bfd->arch_info->compatible, + bfd_arch_get_compatible (&host_bfd, e->bfd)) != host_bfd.arch_info) + { + /* This file is of a recognized binary file format, but it is not + executable on this machine. */ + e->error = b2he (ENOEXEC); + return; + } + + e->entry = e->bfd->start_address; +} +#endif + +#include <endian.h> +#if BYTE_ORDER == BIG_ENDIAN +#define host_ELFDATA ELFDATA2MSB +#endif +#if BYTE_ORDER == LITTLE_ENDIAN +#define host_ELFDATA ELFDATA2LSB +#endif + +static void +check_elf (struct execdata *e) +{ + Elf32_Ehdr *ehdr = map (e, 0, sizeof (Elf32_Ehdr)); + Elf32_Phdr *phdr; + + if (! ehdr) + { + if (! ferror (&e->stream)) + e->error = ENOEXEC; + return; + } + + if (*(Elf32_Word *) ehdr != ((union { Elf32_Word word; + unsigned char string[SELFMAG]; }) + { string: ELFMAG }).word) + { + e->error = ENOEXEC; + return; + } + + if (ehdr->e_ident[EI_CLASS] != ELFCLASS32 || + ehdr->e_ident[EI_DATA] != host_ELFDATA || + ehdr->e_ident[EI_VERSION] != EV_CURRENT || + ehdr->e_version != EV_CURRENT || + ehdr->e_ehsize < sizeof *ehdr || + ehdr->e_phentsize != sizeof (Elf32_Phdr)) + { + e->error = ENOEXEC; + return; + } + e->error = elf_machine_matches_host (ehdr->e_machine); + if (e->error) + return; + + /* Extract all this information now, while EHDR is mapped. + The `map' call below for the phdrs may reuse the mapping window. */ + e->entry = ehdr->e_entry; + e->info.elf.anywhere = (ehdr->e_type == ET_DYN || + ehdr->e_type == ET_REL); + e->info.elf.loadbase = 0; + e->info.elf.phnum = ehdr->e_phnum; + + phdr = map (e, ehdr->e_phoff, ehdr->e_phnum * sizeof (Elf32_Phdr)); + if (! phdr) + { + if (! ferror (&e->stream)) + e->error = ENOEXEC; + return; + } + e->info.elf.phdr = phdr; +} + +static void +check_elf_phdr (struct execdata *e, const Elf32_Phdr *mapped_phdr, + vm_address_t *phdr_addr, vm_size_t *phdr_size) +{ + const Elf32_Phdr *phdr; + + memcpy (e->info.elf.phdr, mapped_phdr, + e->info.elf.phnum * sizeof (Elf32_Phdr)); + + for (phdr = e->info.elf.phdr; + phdr < &e->info.elf.phdr[e->info.elf.phnum]; + ++phdr) + switch (phdr->p_type) + { + case PT_INTERP: + e->interp.phdr = phdr; + break; + case PT_PHDR: + if (phdr_addr) + *phdr_addr = phdr->p_vaddr & ~(phdr->p_align - 1); + if (phdr_size) + *phdr_size = phdr->p_memsz; + break; + case PT_LOAD: + /* Sanity check. */ + if (e->file_size <= (off_t) (phdr->p_offset + + phdr->p_filesz)) + e->error = ENOEXEC; + break; + } +} + + +static void +check (struct execdata *e) +{ + check_elf (e); +#ifdef BFD + if (e->error == ENOEXEC) + { + e->error = 0; + check_bfd (e); + } +#endif +} + + +/* Release the conch and clean up mapping the file and control page. */ +static void +finish_mapping (struct execdata *e) +{ + if (e->cntl != NULL) + { + spin_lock (&e->cntl->lock); + if (e->cntl->conch_status == USER_RELEASE_CONCH) + { + spin_unlock (&e->cntl->lock); + io_release_conch (e->file); + } + else + { + e->cntl->conch_status = USER_HAS_NOT_CONCH; + spin_unlock (&e->cntl->lock); + } + munmap (e->cntl, vm_page_size); + e->cntl = NULL; + } + if (e->filemap != MACH_PORT_NULL) + { + mach_port_deallocate (mach_task_self (), e->filemap); + e->filemap = MACH_PORT_NULL; + } + if (e->cntlmap != MACH_PORT_NULL) + { + mach_port_deallocate (mach_task_self (), e->cntlmap); + e->cntlmap = MACH_PORT_NULL; + } +} + +/* Clean up after reading the file (need not be completed). */ +void +finish (struct execdata *e, int dealloc_file) +{ + finish_mapping (e); +#ifdef BFD + if (e->bfd != NULL) + { + bfd_close (e->bfd); + e->bfd = NULL; + } + else +#endif + fclose (&e->stream); + if (dealloc_file && e->file != MACH_PORT_NULL) + { + mach_port_deallocate (mach_task_self (), e->file); + e->file = MACH_PORT_NULL; + } +} + + +/* Load the file. */ +static void +load (task_t usertask, struct execdata *e) +{ + e->task = usertask; + + if (! e->error) + { +#ifdef BFD + if (e->bfd) + { + void load_bfd_section (bfd *bfd, asection *sec, void *userdata) + { + load_section (sec, userdata); + } + bfd_map_over_sections (e->bfd, &load_bfd_section, e); + } + else +#endif + { + Elf32_Word i; + for (i = 0; i < e->info.elf.phnum; ++i) + if (e->info.elf.phdr[i].p_type == PT_LOAD) + load_section (&e->info.elf.phdr[i], e); + + /* The entry point address is relative to whereever we loaded the + program text. */ + e->entry += e->info.elf.loadbase; + } + } + + /* Release the conch for the file. */ + finish_mapping (e); + + if (! e->error) + { + /* Do post-loading processing on the task. */ + +#ifdef BFD + if (e->bfd) + { + /* Do post-loading processing for a section. This consists of + peeking the pages of non-demand-paged executables. */ + + void postload_section (bfd *bfd, asection *sec, void *userdata) + { + struct execdata *u = userdata; + vm_address_t addr = 0; + vm_size_t secsize = 0; + + addr = (vm_address_t) sec->vma; + secsize = sec->_raw_size; + + if ((sec->flags & SEC_LOAD) && !(bfd->flags & D_PAGED)) + { + /* Pre-load the section by peeking every mapped page. */ + vm_address_t myaddr, a; + vm_size_t mysize; + myaddr = 0; + + /* We have already mapped the file into the task in + load_section. Now read from the task's memory into our + own address space so we can peek each page and cause it to + be paged in. */ + u->error = vm_read (u->task, trunc_page (addr), + round_page (secsize), &myaddr, &mysize); + if (u->error) + return; + + /* Peek at the first word of each page. */ + for (a = ((myaddr + mysize) & ~(vm_page_size - 1)); + a >= myaddr; a -= vm_page_size) + /* Force it to be paged in. */ + (void) *(volatile int *) a; + + munmap ((caddr_t) myaddr, mysize); + } + } + + bfd_map_over_sections (e->bfd, postload_section, e); + } +#endif + } +} + +#ifdef GZIP +/* Check the file for being a gzip'd image. Return with ENOEXEC means not + a valid gzip file; return with another error means lossage in decoding; + return with zero means the file was uncompressed into memory which E now + points to, and `check' can be run again. */ + +static void +check_gzip (struct execdata *earg) +{ + struct execdata *e = earg; + /* Entry points to unzip engine. */ + int get_method (int); + void unzip (int, int); + extern long int bytes_out; + /* Callbacks from unzip for I/O and error interface. */ + extern int (*unzip_read) (char *buf, size_t maxread); + extern void (*unzip_write) (const char *buf, size_t nwrite); + extern void (*unzip_read_error) (void); + extern void (*unzip_error) (const char *msg); + + char *zipdata = NULL; + size_t zipdatasz = 0; + FILE *zipout = NULL; + jmp_buf ziperr; + int zipread (char *buf, size_t maxread) + { + return fread (buf, 1, maxread, &e->stream); + } + void zipwrite (const char *buf, size_t nwrite) + { + if (fwrite (buf, nwrite, 1, zipout) != 1) + longjmp (ziperr, 1); + } + void ziprderr (void) + { + errno = ENOEXEC; + longjmp (ziperr, 2); + } + void ziperror (const char *msg) + { + errno = ENOEXEC; + longjmp (ziperr, 2); + } + + unzip_read = zipread; + unzip_write = zipwrite; + unzip_read_error = ziprderr; + unzip_error = ziperror; + + if (setjmp (ziperr)) + { + /* Error in unzipping jumped out. */ + if (zipout) + { + fclose (zipout); + free (zipdata); + } + e->error = errno; + return; + } + + rewind (&e->stream); + if (get_method (0) != 0) + { + /* Not a happy gzip file. */ + e->error = ENOEXEC; + return; + } + + /* Matched gzip magic number. Ready to unzip. + Set up the output stream and let 'er rip. */ + + zipout = open_memstream (&zipdata, &zipdatasz); + if (! zipout) + { + e->error = errno; + return; + } + + /* Call the gunzip engine. */ + bytes_out = 0; + unzip (17, 23); /* Arguments ignored. */ + + /* The output is complete. Clean up the stream and store its resultant + buffer and size in the execdata as the file contents. */ + fclose (zipout); + e->file_data = zipdata; + e->file_size = zipdatasz; + + /* Clean up the old exec file stream's state. */ + finish (e, 0); + + /* Point the stream at the buffer of file data. */ + memset (&e->stream, 0, sizeof (e->stream)); + e->stream.__magic = _IOMAGIC; + e->stream.__mode.__read = 1; + e->stream.__buffer = e->file_data; + e->stream.__bufsize = e->file_size; + e->stream.__get_limit = e->stream.__buffer + e->stream.__bufsize; + e->stream.__bufp = e->stream.__buffer; + e->stream.__seen = 1; +} +#endif + +#ifdef BZIP2 +/* Check the file for being a bzip2'd image. Return with ENOEXEC means not + a valid bzip2 file; return with another error means lossage in decoding; + return with zero means the file was uncompressed into memory which E now + points to, and `check' can be run again. */ + +static void +check_bzip2 (struct execdata *earg) +{ + struct execdata *e = earg; + /* Entry points to bunzip2 engine. */ + void do_bunzip2 (void); + /* Callbacks from unzip for I/O and error interface. */ + extern int (*unzip_read) (char *buf, size_t maxread); + extern void (*unzip_write) (const char *buf, size_t nwrite); + extern void (*unzip_read_error) (void); + extern void (*unzip_error) (const char *msg); + + char *zipdata = NULL; + size_t zipdatasz = 0; + FILE *zipout = NULL; + jmp_buf ziperr; + int zipread (char *buf, size_t maxread) + { + return fread (buf, 1, maxread, &e->stream); + } + void zipwrite (const char *buf, size_t nwrite) + { + if (fwrite (buf, nwrite, 1, zipout) != 1) + longjmp (ziperr, 1); + } + void ziprderr (void) + { + errno = ENOEXEC; + longjmp (ziperr, 2); + } + void ziperror (const char *msg) + { + errno = ENOEXEC; + longjmp (ziperr, 2); + } + + unzip_read = zipread; + unzip_write = zipwrite; + unzip_read_error = ziprderr; + unzip_error = ziperror; + + if (setjmp (ziperr)) + { + /* Error in unzipping jumped out. */ + if (zipout) + { + fclose (zipout); + free (zipdata); + } + e->error = errno; + return; + } + + rewind (&e->stream); + + zipout = open_memstream (&zipdata, &zipdatasz); + if (! zipout) + { + e->error = errno; + return; + } + + /* Call the bunzip2 engine. */ + do_bunzip2 (); + + /* The output is complete. Clean up the stream and store its resultant + buffer and size in the execdata as the file contents. */ + fclose (zipout); + e->file_data = zipdata; + e->file_size = zipdatasz; + + /* Clean up the old exec file stream's state. */ + finish (e, 0); + + /* Point the stream at the buffer of file data. */ + memset (&e->stream, 0, sizeof (e->stream)); + e->stream.__magic = _IOMAGIC; + e->stream.__mode.__read = 1; + e->stream.__buffer = e->file_data; + e->stream.__bufsize = e->file_size; + e->stream.__get_limit = e->stream.__buffer + e->stream.__bufsize; + e->stream.__bufp = e->stream.__buffer; + e->stream.__seen = 1; +} +#endif + + +static inline error_t +servercopy (void **arg, mach_msg_type_number_t argsize, boolean_t argcopy) +{ + if (argcopy) + { + /* ARG came in-line, so we must copy it. */ + void *copy; + copy = mmap (0, argsize, PROT_READ|PROT_WRITE, MAP_ANON, 0, 0); + if (copy == (void *) -1) + return errno; + bcopy (*arg, copy, argsize); + *arg = copy; + } + return 0; +} + + +static error_t +do_exec (file_t file, + task_t oldtask, + int flags, + char *argv, mach_msg_type_number_t argvlen, boolean_t argv_copy, + char *envp, mach_msg_type_number_t envplen, boolean_t envp_copy, + mach_port_t *dtable, mach_msg_type_number_t dtablesize, + boolean_t dtable_copy, + mach_port_t *portarray, mach_msg_type_number_t nports, + boolean_t portarray_copy, + int *intarray, mach_msg_type_number_t nints, boolean_t intarray_copy, + mach_port_t *deallocnames, mach_msg_type_number_t ndeallocnames, + mach_port_t *destroynames, mach_msg_type_number_t ndestroynames) +{ + struct execdata e, interp; + task_t newtask = MACH_PORT_NULL; + thread_t thread = MACH_PORT_NULL; + struct bootinfo *boot = 0; + int *ports_replaced; + int secure, defaults; + vm_address_t phdr_addr = 0; + vm_size_t phdr_size = 0; + mach_msg_type_number_t i; + int intarray_dealloc = 0; /* Dealloc INTARRAY before returning? */ + int oldtask_trashed = 0; /* Have we trashed the old task? */ + + /* Prime E for executing FILE and check its validity. This must be an + inline function because it stores pointers into alloca'd storage in E + for later use in `load'. */ + void prepare_and_check (file_t file, struct execdata *e) + { + /* Prepare E to read the file. */ + prepare (file, e); + if (e->error) + return; + + /* Check the file for validity first. */ + check (e); + +#ifdef GZIP + if (e->error == ENOEXEC) + { + /* See if it is a compressed image. */ + static struct mutex lock = MUTEX_INITIALIZER; + /* The gzip code is really cheesy, not even close to thread-safe. + So we serialize all uses of it. */ + mutex_lock (&lock); + e->error = 0; + check_gzip (e); + mutex_unlock (&lock); + if (e->error == 0) + /* The file was uncompressed into memory, and now E describes the + uncompressed image rather than the actual file. Check it again + for a valid magic number. */ + check (e); + } +#endif +#ifdef BZIP2 + if (e->error == ENOEXEC) + { + /* See if it is a compressed image. */ + static struct mutex lock = MUTEX_INITIALIZER; + /* The bzip2 code is really cheesy, not even close to thread-safe. + So we serialize all uses of it. */ + mutex_lock (&lock); + e->error = 0; + check_bzip2 (e); + mutex_unlock (&lock); + if (e->error == 0) + /* The file was uncompressed into memory, and now E describes the + uncompressed image rather than the actual file. Check it again + for a valid magic number. */ + check (e); + } +#endif + } + + + /* Here is the main body of the function. */ + + + /* Catch this error now, rather than later. */ + /* XXX For EXEC_DEFAULTS, this is only an error if one of the user's + ports is null; if they are all provided, then EXEC_DEFAULTS would + have no effect, and the lack of installed standard ports should + not cause an error. -mib */ + if ((!std_ports || !std_ints) && (flags & (EXEC_SECURE|EXEC_DEFAULTS))) + return EIEIO; + + /* Suspend the existing task before frobnicating it. */ + if (oldtask != MACH_PORT_NULL && (e.error = task_suspend (oldtask))) + return e.error; + + /* Prime E for executing FILE and check its validity. */ + prepare_and_check (file, &e); + + if (e.error == ENOEXEC) + { + /* Check for a #! executable file. */ + check_hashbang (&e, + file, oldtask, flags, + argv, argvlen, argv_copy, + envp, envplen, envp_copy, + dtable, dtablesize, dtable_copy, + portarray, nports, portarray_copy, + intarray, nints, intarray_copy, + deallocnames, ndeallocnames, + destroynames, ndestroynames); + if (! e.error) + /* The #! exec succeeded; nothing more to do. */ + return 0; + } + + if (e.error) + /* The file is not a valid executable. */ + goto out; + +#ifdef BFD + if (e.bfd) + { + e.info.bfd_locations = alloca (e.bfd->section_count * + sizeof (vm_offset_t)); + bfd_map_over_sections (e.bfd, check_section, &e); + } + else +#endif + { + const Elf32_Phdr *phdr = e.info.elf.phdr; + e.info.elf.phdr = alloca (e.info.elf.phnum * sizeof (Elf32_Phdr)); + check_elf_phdr (&e, phdr, &phdr_addr, &phdr_size); + } + + interp.file = MACH_PORT_NULL; + + if (oldtask == MACH_PORT_NULL) + flags |= EXEC_NEWTASK; + + if (flags & (EXEC_NEWTASK|EXEC_SECURE)) + { + /* Create the new task. If we are not being secure, then use OLDTASK + for the task_create RPC, in case it is something magical. */ + e.error = task_create (((flags & EXEC_SECURE) || + oldtask == MACH_PORT_NULL) ? + mach_task_self () : oldtask, + 0, &newtask); + if (e.error) + goto out; + } + else + newtask = oldtask; + + + rwlock_reader_lock (&std_lock); + { + /* Store the data that we will give in response + to the RPC on the new task's bootstrap port. */ + + /* Set boot->portarray[IDX] to NEW. If REAUTH is nonzero, + io_reauthenticate NEW and set it to the authenticated port. + If CONSUME is nonzero, a reference on NEW is consumed; + it is invalid to give nonzero values to both REAUTH and CONSUME. */ +#define use(idx, new, reauth, consume) \ + do { use1 (idx, new, reauth, consume); \ + if (e.error) goto stdout; } while (0) + void use1 (unsigned int idx, mach_port_t new, + int reauth, int consume) + { + if (new != MACH_PORT_NULL && reauth) + { + mach_port_t ref = mach_reply_port (), authed; + e.error = io_reauthenticate (new, ref, MACH_MSG_TYPE_MAKE_SEND); + if (! e.error) + e.error = auth_user_authenticate + (boot->portarray[INIT_PORT_AUTH], + ref, MACH_MSG_TYPE_MAKE_SEND, &authed); + mach_port_destroy (mach_task_self (), ref); + if (e.error) + return; + new = authed; + } + else + { + if (!consume && new != MACH_PORT_NULL) + mach_port_mod_refs (mach_task_self (), + new, MACH_PORT_RIGHT_SEND, 1); + } + + boot->portarray[idx] = new; + ports_replaced[idx] = 1; + } + + e.error = ports_create_port (execboot_portclass, port_bucket, + sizeof *boot, &boot); + if (boot == NULL) + { + stdout: + rwlock_reader_unlock (&std_lock); + goto out; + } + bzero (&boot->pi + 1, (char *) &boot[1] - (char *) (&boot->pi + 1)); + + /* These flags say the information we pass through to the new program + may need to be modified. */ + secure = (flags & EXEC_SECURE); + defaults = (flags & EXEC_DEFAULTS); + + /* Now record the big blocks of data we shuffle around unchanged. + Whatever arrived inline, we must allocate space for so it can + survive after this RPC returns. */ + + boot->flags = flags; + + e.error = servercopy ((void **) &argv, argvlen, argv_copy); + if (e.error) + goto stdout; + boot->argv = argv; + boot->argvlen = argvlen; + e.error = servercopy ((void **) &envp, envplen, envp_copy); + if (e.error) + goto stdout; + boot->envp = envp; + boot->envplen = envplen; + e.error = servercopy ((void **) &dtable, dtablesize * sizeof (mach_port_t), + dtable_copy); + if (e.error) + goto stdout; + boot->dtable = dtable; + boot->dtablesize = dtablesize; + + if ((secure || defaults) && nints < INIT_INT_MAX) + { + /* Make sure the intarray is at least big enough. */ + if (intarray_copy || (round_page (nints * sizeof (int)) < + round_page (INIT_INT_MAX * sizeof (int)))) + { + /* Allocate a new vector that is big enough. */ + boot->intarray = mmap (0, INIT_INT_MAX * sizeof (int), + PROT_READ|PROT_WRITE, MAP_ANON, 0, 0); + memcpy (boot->intarray, intarray, nints * sizeof (int)); + intarray_dealloc = !intarray_copy; + } + else + boot->intarray = intarray; + boot->nints = INIT_INT_MAX; + } + else + { + e.error = servercopy ((void **) &intarray, nints * sizeof (int), + intarray_copy); + if (e.error) + goto stdout; + boot->intarray = intarray; + boot->nints = nints; + } + + if (secure) + boot->intarray[INIT_UMASK] = std_ints ? std_ints[INIT_UMASK] : CMASK; + + /* Now choose the ports to give the new program. */ + + boot->nports = nports < INIT_PORT_MAX ? INIT_PORT_MAX : nports; + boot->portarray = mmap (0, boot->nports * sizeof (mach_port_t), + PROT_READ|PROT_WRITE, MAP_ANON, 0, 0); + /* Start by copying the array as passed. */ + for (i = 0; i < nports; ++i) + boot->portarray[i] = portarray[i]; + if (MACH_PORT_NULL != 0) + for (; i < boot->nports; ++i) + boot->portarray[i] = MACH_PORT_NULL; + /* Keep track of which ports in BOOT->portarray come from the original + PORTARRAY, and which we replace. */ + ports_replaced = alloca (boot->nports * sizeof *ports_replaced); + bzero (ports_replaced, boot->nports * sizeof *ports_replaced); + + if (portarray[INIT_PORT_BOOTSTRAP] == MACH_PORT_NULL && + oldtask != MACH_PORT_NULL) + { + if (! task_get_bootstrap_port (oldtask, + &boot->portarray[INIT_PORT_BOOTSTRAP])) + ports_replaced[INIT_PORT_BOOTSTRAP] = 1; + } + + /* Note that the parentheses on this first test are different from the + others below it. */ + if ((secure || defaults) + && boot->portarray[INIT_PORT_AUTH] == MACH_PORT_NULL) + /* Q: Doesn't this let anyone run a program and make it + get a root auth port? + A: No; the standard port for INIT_PORT_AUTH has no UID's at all. + See init.trim/init.c (init_stdarrays). */ + use (INIT_PORT_AUTH, std_ports[INIT_PORT_AUTH], 0, 0); + if (secure || (defaults + && boot->portarray[INIT_PORT_PROC] == MACH_PORT_NULL)) + { + /* Ask the proc server for the proc port for this task. */ + mach_port_t new; + + e.error = proc_task2proc (procserver, newtask, &new); + if (e.error) + goto stdout; + use (INIT_PORT_PROC, new, 0, 1); + } + else if (oldtask != newtask && oldtask != MACH_PORT_NULL + && boot->portarray[INIT_PORT_PROC] != MACH_PORT_NULL) + { + mach_port_t new; + /* This task port refers to the old task; use it to fetch a new + one for the new task. */ + e.error = proc_task2proc (boot->portarray[INIT_PORT_PROC], + newtask, &new); + if (e.error) + goto stdout; + use (INIT_PORT_PROC, new, 0, 1); + } + if (secure || (defaults + && boot->portarray[INIT_PORT_CRDIR] == MACH_PORT_NULL)) + use (INIT_PORT_CRDIR, std_ports[INIT_PORT_CRDIR], 1, 0); + if ((secure || defaults) + && boot->portarray[INIT_PORT_CWDIR] == MACH_PORT_NULL) + use (INIT_PORT_CWDIR, std_ports[INIT_PORT_CWDIR], 1, 0); + } + rwlock_reader_unlock (&std_lock); + + + /* We have now concocted in BOOT the complete Hurd context (ports and + ints) that the new program image will run under. We will use these + ports for looking up the interpreter file if there is one. */ + + if (! e.error && e.interp.section) + { + /* There is an interpreter section specifying another file to load + along with this executable. Find the name of the file and open + it. */ + +#ifdef BFD + char namebuf[e.bfd ? e.interp.section->_raw_size : 0]; +#endif + char *name; + +#ifdef BFD + if (e.bfd) + { + if (! bfd_get_section_contents (e.bfd, e.interp.section, + namebuf, 0, + e.interp.section->_raw_size)) + { + e.error = b2he (errno); + name = NULL; + } + else + name = namebuf; + } + else +#endif + { + name = map (&e, (e.interp.phdr->p_offset + & ~(e.interp.phdr->p_align - 1)), + e.interp.phdr->p_filesz); + if (! name && ! ferror (&e.stream)) + e.error = ENOEXEC; + } + + if (! name) + e.interp.section = NULL; + else + { + /* Open the named file using the appropriate directory ports for + the user. */ + error_t user_port (int which, error_t (*operate) (mach_port_t)) + { + return (*operate) (boot->nports > which ? + boot->portarray[which] : + MACH_PORT_NULL); + } + file_t user_fd (int fd) + { + if (fd < 0 || fd >= boot->dtablesize || + boot->dtable[fd] == MACH_PORT_NULL) + { + errno = EBADF; + return MACH_PORT_NULL; + } + return boot->dtable[fd]; + } + e.error = hurd_file_name_lookup (&user_port, &user_fd, 0, + name, O_READ, 0, &interp.file); + } + } + + if (interp.file != MACH_PORT_NULL) + { + /* We opened an interpreter file. Prepare it for loading too. */ + prepare_and_check (interp.file, &interp); + if (! interp.error) + { +#ifdef BFD + if (interp.bfd) + { + interp.info.bfd_locations = alloca (interp.bfd->section_count * + sizeof (vm_offset_t)); + bfd_map_over_sections (interp.bfd, check_section, &e); + } + else +#endif + { + const Elf32_Phdr *phdr = interp.info.elf.phdr; + interp.info.elf.phdr = alloca (interp.info.elf.phnum * + sizeof (Elf32_Phdr)); + check_elf_phdr (&interp, phdr, NULL, NULL); + } + } + e.error = interp.error; + } + + if (e.error) + goto out; + + + /* We are now committed to the exec. It "should not fail". + If it does fail now, the task will be hopelessly munged. */ + + if (newtask == oldtask) + { + thread_array_t threads; + mach_msg_type_number_t nthreads, i; + + /* Terminate all the threads of the old task. */ + + e.error = task_threads (oldtask, &threads, &nthreads); + if (e.error) + goto out; + for (i = 0; i < nthreads; ++i) + { + thread_terminate (threads[i]); + mach_port_deallocate (mach_task_self (), threads[i]); + } + munmap ((caddr_t) threads, nthreads * sizeof (thread_t)); + + /* Deallocate the entire virtual address space of the task. */ + + vm_deallocate (oldtask, + VM_MIN_ADDRESS, VM_MAX_ADDRESS - VM_MIN_ADDRESS); + + /* Nothing is supposed to go wrong any more. If anything does, the + old task is now in a hopeless state and must be killed. */ + oldtask_trashed = 1; + + /* Deallocate and destroy the ports requested by the caller. + These are ports the task wants not to lose if the exec call + fails, but wants removed from the new program task. */ + + for (i = 0; i < ndeallocnames; ++i) + mach_port_deallocate (oldtask, deallocnames[i]); + + for (i = 0; i < ndestroynames; ++i) + mach_port_destroy (oldtask, destroynames[i]); + } + +/* XXX this should be below + it is here to work around a vm_map kernel bug. */ + if (interp.file != MACH_PORT_NULL) + { + /* Load the interpreter file. */ + load (newtask, &interp); + if (interp.error) + { + e.error = interp.error; + goto out; + } + finish (&interp, 1); + } + + + /* Load the file into the task. */ + load (newtask, &e); + if (e.error) + goto out; + + /* XXX loading of interp belongs here */ + + /* Clean up. */ + finish (&e, 0); + + /* Now record some essential addresses from the image itself that the + program's startup code will need to know. We do this after loading + the image so that a load-anywhere image gets the adjusted addresses. */ +#ifdef BFD + if (!e.bfd) + phdr_addr += e.info.elf.loadbase; +#endif + boot->phdr_addr = phdr_addr; + boot->phdr_size = phdr_size; + boot->user_entry = e.entry; /* already adjusted in `load' */ + + /* Create the initial thread. */ + e.error = thread_create (newtask, &thread); + if (e.error) + goto out; + + /* Start up the initial thread at the entry point. */ + boot->stack_base = 0, boot->stack_size = 0; /* Don't care about values. */ + e.error = mach_setup_thread (newtask, thread, + (void *) (e.interp.section ? interp.entry : + e.entry), + &boot->stack_base, &boot->stack_size); + if (e.error) + goto out; + + if (oldtask != newtask && oldtask != MACH_PORT_NULL) + { + /* The program is on its way. The old task can be nuked. */ + process_t proc; + process_t psrv; + + /* Use the canonical proc server if secure, or there is none other. + When not secure, it is nice to let processes associate with + whatever proc server turns them on, regardless of which exec + itself is using. */ + if (secure + || boot->nports <= INIT_PORT_PROC + || boot->portarray[INIT_PORT_PROC] == MACH_PORT_NULL) + psrv = procserver; + else + psrv = boot->portarray[INIT_PORT_PROC]; + + /* XXX there is a race here for SIGKILLing the process. -roland + I don't think it matters. -mib */ + if (! proc_task2proc (psrv, oldtask, &proc)) + { + proc_reassign (proc, newtask); + mach_port_deallocate (mach_task_self (), proc); + } + + mach_port_deallocate (mach_task_self (), oldtask); + } + + /* Make sure the proc server has the right idea of our identity. */ + if (secure) + { + uid_t euidbuf[10], egidbuf[10], auidbuf[10], agidbuf[10]; + uid_t *euids, *egids, *auids, *agids; + size_t neuids, negids, nauids, nagids; + error_t err; + + /* Find out what our UID is from the auth server. */ + neuids = negids = nauids = nagids = 10; + euids = euidbuf, egids = egidbuf; + auids = auidbuf, agids = agidbuf; + err = auth_getids (boot->portarray[INIT_PORT_AUTH], + &euids, &neuids, &auids, &nauids, + &egids, &negids, &agids, &nagids); + + if (!err) + { + /* Set the owner with the proc server */ + /* Not much we can do about errors here; caller is responsible + for making sure that the provided proc port is correctly + authenticated anyhow. */ + proc_setowner (boot->portarray[INIT_PORT_PROC], + neuids ? euids[0] : 0, !neuids); + + /* Clean up */ + if (euids != euidbuf) + munmap (euids, neuids * sizeof (uid_t)); + if (egids != egidbuf) + munmap (egids, negids * sizeof (uid_t)); + if (auids != auidbuf) + munmap (auids, nauids * sizeof (uid_t)); + if (agids != agidbuf) + munmap (agids, nagids * sizeof (uid_t)); + } + } + + { + mach_port_t btport = ports_get_right (boot); + mach_port_insert_right (mach_task_self (), btport, btport, + MACH_MSG_TYPE_MAKE_SEND); + e.error = task_set_bootstrap_port (newtask, btport); + mach_port_deallocate (mach_task_self (), btport); + } + + out: + if (interp.file != MACH_PORT_NULL) + finish (&interp, 1); + finish (&e, !e.error); + + if (!e.error && (flags & EXEC_SIGTRAP)) /* XXX && !secure ? */ + { + /* This is a "traced" exec, i.e. the new task is to be debugged. The + caller has requested that the new process stop with SIGTRAP before + it starts. Since the process has no signal thread yet to do its + own POSIX signal mechanics, we simulate it by notifying the proc + server of the signal and leaving the initial thread with a suspend + count of one, as it would be if the process were stopped by a + POSIX signal. */ + mach_port_t proc; + if (boot->nports > INIT_PORT_PROC) + proc = boot->portarray[INIT_PORT_PROC]; + else + /* Ask the proc server for the proc port for this task. */ + e.error = proc_task2proc (procserver, newtask, &proc); + if (!e.error) + /* Tell the proc server that the process has stopped with the + SIGTRAP signal. Don't bother to check for errors from the RPC + here; for non-secure execs PROC may be the user's own proc + server its confusion shouldn't make the exec fail. */ + proc_mark_stop (proc, SIGTRAP, 0); + } + + if (boot) + { + /* Release the original reference. Now there is only one + reference, which will be released on no-senders notification. + If we are bailing out due to error before setting the task's + bootstrap port, this will be the last reference and BOOT + will get cleaned up here. */ + + if (e.error) + /* Kill the pointers to the argument information so the cleanup + of BOOT doesn't deallocate it. It will be deallocated my MiG + when we return the error. */ + bzero (&boot->pi + 1, (char *) &boot[1] - (char *) (&boot->pi + 1)); + else + /* Do this before we release the last reference. */ + if (boot->nports > INIT_PORT_PROC) + proc_mark_exec (boot->portarray[INIT_PORT_PROC]); + + ports_port_deref (boot); + } + + if (thread != MACH_PORT_NULL) + { + if (!e.error && !(flags & EXEC_SIGTRAP)) + thread_resume (thread); + mach_port_deallocate (mach_task_self (), thread); + } + + if (e.error) + { + if (oldtask != newtask) + { + /* We created a new task but failed to set it up. Kill it. */ + task_terminate (newtask); + mach_port_deallocate (mach_task_self (), newtask); + } + if (oldtask_trashed) + /* The old task is hopelessly trashed; there is no way it + can resume execution. Coup de grace. */ + task_terminate (oldtask); + else + /* Resume the old task, which we suspended earlier. */ + task_resume (oldtask); + } + else + { + if (oldtask != newtask) + { + /* We successfully set the new task up. + Terminate the old task and deallocate our right to it. */ + task_terminate (oldtask); + mach_port_deallocate (mach_task_self (), oldtask); + } + else + /* Resume the task, it is ready to run the new program. */ + task_resume (oldtask); + /* Deallocate the right to the new task we created. */ + mach_port_deallocate (mach_task_self (), newtask); + + for (i = 0; i < nports; ++i) + if (ports_replaced[i] && portarray[i] != MACH_PORT_NULL) + /* This port was replaced, so the reference that arrived in the + original portarray is not being saved in BOOT for transfer to + the user task. Deallocate it; we don't want it, and MiG will + leave it for us on successful return. */ + mach_port_deallocate (mach_task_self (), portarray[i]); + + /* If there is vm_allocate'd space for the original intarray and/or + portarray, and we are not saving those pointers in BOOT for later + transfer, deallocate the original space now. */ + if (intarray_dealloc) + munmap (intarray, nints * sizeof intarray[0]); + if (!portarray_copy) + munmap (portarray, nports * sizeof portarray[0]); + } + + return e.error; +} + +kern_return_t +S_exec_exec (struct trivfs_protid *protid, + file_t file, + task_t oldtask, + int flags, + char *argv, mach_msg_type_number_t argvlen, boolean_t argv_copy, + char *envp, mach_msg_type_number_t envplen, boolean_t envp_copy, + mach_port_t *dtable, mach_msg_type_number_t dtablesize, + boolean_t dtable_copy, + mach_port_t *portarray, mach_msg_type_number_t nports, + boolean_t portarray_copy, + int *intarray, mach_msg_type_number_t nints, + boolean_t intarray_copy, + mach_port_t *deallocnames, mach_msg_type_number_t ndeallocnames, + mach_port_t *destroynames, mach_msg_type_number_t ndestroynames) +{ + if (! protid) + return EOPNOTSUPP; + +#if 0 + if (!(flags & EXEC_SECURE)) + { + char *list = envz_get (envp, envplen, "EXECSERVERS"); + + if (list) + { + int tried = 0; + list = strdupa (list); + while ((p = strsep (&list, ":"))) + { + /* Open the named file using the appropriate directory ports for + the user. */ + error_t user_port (int which, error_t (*operate) (mach_port_t)) + { + return (*operate) (nports > which + ? portarray[which] : MACH_PORT_NULL); + } + file_t user_fd (int fd) + { + if (fd < 0 || fd >= dtablesize || + dtable[fd] == MACH_PORT_NULL) + { + errno = EBADF; + return MACH_PORT_NULL; + } + return dtable[fd]; + } + file_t server; + if (!hurd_file_name_lookup (user_port, user_fd, 0, p, 0,0, &server)) + { + error_t err; + struct trivfs_protid *protid + = ports_lookup_port (port_bucket, server, + trivfs_protid_portclasses[0]); + if (protid) + { + err = do_exec (file, oldtask, 0, + argv, argvlen, argv_copy, + envp, envplen, envp_copy, + dtable, dtablesize, dtable_copy, + portarray, nports, portarray_copy, + intarray, nints, intarray_copy, + deallocnames, ndeallocnames, + destroynames, ndestroynames); + ports_port_deref (protid); + } + else + { + int n; + err = exec_exec (server, + file, MACH_MSG_TYPE_COPY_SEND, + oldtask, 0, + argv, argvlen, + envp, envplen, + dtable, MACH_MSG_TYPE_COPY_SEND, + dtablesize, + portarray, MACH_MSG_TYPE_COPY_SEND, + nports, + intarray, nints, + deallocnames, ndeallocnames, + destroynames, ndestroynames); + mach_port_deallocate (mach_task_self (), file); + for (n = 0; n < dtablesize; n++) + mach_port_deallocate (mach_task_self (), dtable[n]); + for (n = 0; n < nports; n++) + mach_port_deallocate (mach_task_self (), portarray[n]); + } + mach_port_deallocate (mach_task_self (), server); + if (err != ENOEXEC) + return err; + tried = 1; + } + } + if (tried) + /* At least one exec server got a crack at it and gave up. */ + return ENOEXEC; + } + } +#endif + + /* There were no user-specified exec servers, + or none of them could be found. */ + + return do_exec (file, oldtask, flags, + argv, argvlen, argv_copy, + envp, envplen, envp_copy, + dtable, dtablesize, dtable_copy, + portarray, nports, portarray_copy, + intarray, nints, intarray_copy, + deallocnames, ndeallocnames, + destroynames, ndestroynames); +} + +kern_return_t +S_exec_setexecdata (struct trivfs_protid *protid, + mach_port_t *ports, mach_msg_type_number_t nports, int ports_copy, + int *ints, mach_msg_type_number_t nints, int ints_copy) +{ + error_t err; + + if (! protid || (protid->realnode != MACH_PORT_NULL && ! protid->isroot)) + return EPERM; + + if (nports < INIT_PORT_MAX || nints < INIT_INT_MAX) + return EINVAL; /* */ + + err = servercopy ((void **) &ports, nports * sizeof (mach_port_t), + ports_copy); + if (err) + return err; + err = servercopy ((void **) &ints, nints * sizeof (int), ints_copy); + if (err) + return err; + + rwlock_writer_lock (&std_lock); + + if (std_ports) + { + mach_msg_type_number_t i; + for (i = 0; i < std_nports; ++i) + mach_port_deallocate (mach_task_self (), std_ports[i]); + munmap (std_ports, std_nports * sizeof (mach_port_t)); + } + + std_ports = ports; + std_nports = nports; + + if (std_ints) + munmap (std_ints, std_nints * sizeof (int)); + + std_ints = ints; + std_nints = nints; + + rwlock_writer_unlock (&std_lock); + + return 0; +} + + +#include "exec_startup_S.h" + +/* RPC sent on the bootstrap port. */ + +kern_return_t +S_exec_startup_get_info (mach_port_t port, + vm_address_t *user_entry, + vm_address_t *phdr_data, vm_size_t *phdr_size, + vm_address_t *stack_base, vm_size_t *stack_size, + int *flags, + char **argvp, mach_msg_type_number_t *argvlen, + char **envpp, mach_msg_type_number_t *envplen, + mach_port_t **dtable, + mach_msg_type_name_t *dtablepoly, + mach_msg_type_number_t *dtablesize, + mach_port_t **portarray, + mach_msg_type_name_t *portpoly, + mach_msg_type_number_t *nports, + int **intarray, mach_msg_type_number_t *nints) +{ + struct bootinfo *boot = ports_lookup_port (port_bucket, port, + execboot_portclass); + if (! boot) + return EOPNOTSUPP; + ports_port_deref (boot); + + /* Pass back all the information we are storing. */ + + *user_entry = boot->user_entry; + *phdr_data = boot->phdr_addr; + *phdr_size = boot->phdr_size; + *stack_base = boot->stack_base; + *stack_size = boot->stack_size; + + *argvp = boot->argv; + *argvlen = boot->argvlen; + boot->argvlen = 0; + + *envpp = boot->envp; + *envplen = boot->envplen; + boot->envplen = 0; + + *dtable = boot->dtable; + *dtablesize = boot->dtablesize; + *dtablepoly = MACH_MSG_TYPE_MOVE_SEND; + boot->dtablesize = 0; + + *intarray = boot->intarray; + *nints = boot->nints; + boot->nints = 0; + + *portarray = boot->portarray; + *nports = boot->nports; + *portpoly = MACH_MSG_TYPE_MOVE_SEND; + boot->nports = 0; + + *flags = boot->flags; + + return 0; +} |