diff options
-rwxr-xr-x | build/install-build-deps.sh | 2 | ||||
-rw-r--r-- | chrome/common/chrome_paths.cc | 9 | ||||
-rw-r--r-- | chrome/common/chrome_paths.h | 1 | ||||
-rw-r--r-- | chrome/nacl.gypi | 116 | ||||
-rw-r--r-- | chrome/nacl/nacl_fork_delegate_linux.cc | 18 | ||||
-rw-r--r-- | chrome/nacl/nacl_helper_bootstrap_linux.c | 433 | ||||
-rw-r--r-- | chrome/nacl/nacl_helper_bootstrap_linux.x | 93 | ||||
-rw-r--r-- | chrome/nacl/nacl_helper_bootstrap_munge_phdr.c | 66 | ||||
-rwxr-xr-x | chrome/nacl/nacl_helper_bootstrap_munge_phdr.py | 36 | ||||
-rw-r--r-- | chrome/nacl/nacl_helper_exports.txt | 10 | ||||
-rw-r--r-- | chrome/nacl/nacl_helper_linux.cc | 26 |
11 files changed, 734 insertions, 76 deletions
diff --git a/build/install-build-deps.sh b/build/install-build-deps.sh index 7908608..f4907bd 100755 --- a/build/install-build-deps.sh +++ b/build/install-build-deps.sh @@ -103,7 +103,7 @@ chromeos_dev_list="libpulse-dev" # Packages need for development dev_list="apache2.2-bin bison fakeroot flex g++ gperf language-pack-fr libapache2-mod-php5 libasound2-dev libbz2-dev libcairo2-dev - libcups2-dev libdbus-glib-1-dev libgconf2-dev + libcups2-dev libdbus-glib-1-dev libelf-dev libgconf2-dev libgl1-mesa-dev libglu1-mesa-dev libglib2.0-dev libgnome-keyring-dev libgtk2.0-dev libjpeg62-dev libkrb5-dev libnspr4-dev libnss3-dev libpam0g-dev libsctp-dev libsqlite3-dev libxslt1-dev libxss-dev diff --git a/chrome/common/chrome_paths.cc b/chrome/common/chrome_paths.cc index 0758f1e..7f223cf 100644 --- a/chrome/common/chrome_paths.cc +++ b/chrome/common/chrome_paths.cc @@ -53,8 +53,10 @@ const FilePath::CharType kInternalNaClPluginFileName[] = #endif #if defined(OS_POSIX) && !defined(OS_MACOSX) -// File name of the nacl_helper, Linux only. +// File name of the nacl_helper and nacl_helper_bootstrap, Linux only. const FilePath::CharType kInternalNaClHelperFileName[] = + FILE_PATH_LITERAL("nacl_helper"); +const FilePath::CharType kInternalNaClHelperBootstrapFileName[] = FILE_PATH_LITERAL("nacl_helper_bootstrap"); #endif @@ -246,6 +248,11 @@ bool PathProvider(int key, FilePath* result) { return false; cur = cur.Append(kInternalNaClHelperFileName); break; + case chrome::FILE_NACL_HELPER_BOOTSTRAP: + if (!PathService::Get(base::DIR_MODULE, &cur)) + return false; + cur = cur.Append(kInternalNaClHelperBootstrapFileName); + break; #endif case chrome::FILE_RESOURCES_PACK: #if defined(OS_MACOSX) diff --git a/chrome/common/chrome_paths.h b/chrome/common/chrome_paths.h index 4998213..4b89a4e 100644 --- a/chrome/common/chrome_paths.h +++ b/chrome/common/chrome_paths.h @@ -71,6 +71,7 @@ enum { #if defined(OS_POSIX) && !defined(OS_MACOSX) FILE_NACL_HELPER, // Full path to Linux nacl_helper executable. + FILE_NACL_HELPER_BOOTSTRAP, // ... and nacl_helper_bootstrap executable. #endif FILE_NACL_PLUGIN, // Full path to the internal NaCl plugin file. FILE_LIBAVCODEC, // Full path to libavcodec media decoding diff --git a/chrome/nacl.gypi b/chrome/nacl.gypi index abfab38..42c0035 100644 --- a/chrome/nacl.gypi +++ b/chrome/nacl.gypi @@ -183,9 +183,7 @@ ['OS=="linux"', { 'targets': [ { - 'target_name': 'nacl_helper.so', - # 'executable' will be overridden below when we add the -shared - # flag; here it prevents gyp from using the --whole-archive flag + 'target_name': 'nacl_helper', 'type': 'executable', 'include_dirs': [ '..', @@ -194,7 +192,7 @@ 'nacl', ], 'sources': [ - '../chrome/nacl/nacl_helper_linux.cc', + 'nacl/nacl_helper_linux.cc', ], 'conditions': [ ['toolkit_uses_gtk == 1', { @@ -203,36 +201,108 @@ ], }], ], + 'cflags': ['-fPIE'], 'link_settings': { - # NOTE: '-shared' overrides 'executable' above - 'ldflags': ['-shared', - '-Wl,--version-script=chrome/nacl/nacl_helper_exports.txt', - ], + 'ldflags': ['-pie'], }, }, { - 'target_name': 'nacl_helper_bootstrap', + 'target_name': 'nacl_helper_bootstrap_munge_phdr', 'type': 'executable', - 'dependencies': [ - 'nacl_helper.so', + 'toolsets': ['host'], + 'sources': [ + 'nacl/nacl_helper_bootstrap_munge_phdr.c', + ], + 'libraries': [ + '-lelf', + ], + # This is an ugly kludge because gyp doesn't actually treat + # host_arch=x64 target_arch=ia32 as proper cross compilation. + # It still wants to compile the "host" program with -m32 et + # al. Though a program built that way can indeed run on the + # x86-64 host, we cannot reliably build this program on such a + # host because Ubuntu does not provide the full suite of + # x86-32 libraries in packages that can be installed on an + # x86-64 host; in particular, libelf is missing. So here we + # use the hack of eliding all the -m* flags from the + # compilation lines, getting the command close to what they + # would be if gyp were to really build properly for the host. + # TODO(bradnelson): Clean up with proper cross support. + 'conditions': [ + ['host_arch=="x64"', { + 'cflags/': [['exclude', '-m.*']], + 'ldflags/': [['exclude', '-m.*']], + }], + ], + }, + { + 'target_name': 'nacl_helper_bootstrap_raw', + 'type': 'executable', + 'include_dirs': [ + '..', ], 'sources': [ - '../chrome/nacl/nacl_helper_bootstrap_linux.c', + 'nacl/nacl_helper_bootstrap_linux.c', + # We list the linker script here for documentation purposes. + # But even this doesn't make gyp treat it as a dependency, + # so incremental builds won't relink when the script changes. + # TODO(bradnelson): Fix the dependency handling. + 'nacl/nacl_helper_bootstrap_linux.x', + ], + 'cflags': [ + # The tiny standalone bootstrap program is incompatible with + # -fstack-protector, which might be on by default. That switch + # requires using the standard libc startup code, which we do not. + '-fno-stack-protector', + # We don't want to compile it PIC (or its cousin PIE), because + # it goes at an absolute address anyway, and because any kind + # of PIC complicates life for the x86-32 assembly code. We + # append -fno-* flags here instead of using a 'cflags!' stanza + # to remove -f* flags, just in case some system's compiler + # defaults to using PIC for everything. + '-fno-pic', '-fno-PIC', + '-fno-pie', '-fno-PIE', ], - # TODO(bradchen): Delete the -B argument when Gold supports - # -Ttext properly. Until then use ld.bfd. 'link_settings': { - 'ldflags': ['-B', 'tools/ld_bfd', - # Force text segment at 0x10000 (64KB) - # The max-page-size option is needed on x86-64 linux - # where 4K pages are not the default in the BFD linker. - '-Wl,-Ttext-segment,10000,-z,max-page-size=0x1000', - # reference nacl_helper as a shared library - '<(PRODUCT_DIR)/nacl_helper.so', - '-Wl,-rpath,<(SHARED_LIB_DIR)', - ], + 'ldflags': [ + # TODO(bradchen): Delete the -B argument when Gold is verified + # to produce good results with our custom linker script. + # Until then use ld.bfd. + '-B', 'tools/ld_bfd', + # This programs is (almost) entirely standalone. It has + # its own startup code, so no crt1.o for it. It is + # statically linked, and on x86 it actually does not use + # libc at all. However, on ARM it needs a few (safe) + # things from libc, so we don't use '-nostdlib' here. + '-static', '-nostartfiles', + # Link with our custom linker script to get out special layout. + # TODO(bradnelson): Use some <(foo) instead of chrome/ here. + '-Wl,--script=chrome/nacl/nacl_helper_bootstrap_linux.x', + # On x86-64, the default page size with some + # linkers is 2M rather than the real Linux page + # size of 4K. A larger page size is incompatible + # with our custom linker script's special layout. + '-Wl,-z,max-page-size=0x1000', + ], }, }, + { + 'target_name': 'nacl_helper_bootstrap', + 'dependencies': [ + 'nacl_helper_bootstrap_raw', + 'nacl_helper_bootstrap_munge_phdr#host', + ], + 'type': 'none', + 'actions': [{ + 'action_name': 'munge_phdr', + 'inputs': ['nacl/nacl_helper_bootstrap_munge_phdr.py', + '<(PRODUCT_DIR)/nacl_helper_bootstrap_munge_phdr', + '<(PRODUCT_DIR)/nacl_helper_bootstrap_raw'], + 'outputs': ['<(PRODUCT_DIR)/nacl_helper_bootstrap'], + 'message': 'Munging ELF program header', + 'action': ['python', '<@(_inputs)', '<@(_outputs)'] + }], + } ], }], ], diff --git a/chrome/nacl/nacl_fork_delegate_linux.cc b/chrome/nacl/nacl_fork_delegate_linux.cc index e5ee01a..26fae53 100644 --- a/chrome/nacl/nacl_fork_delegate_linux.cc +++ b/chrome/nacl/nacl_fork_delegate_linux.cc @@ -26,6 +26,8 @@ NaClForkDelegate::NaClForkDelegate() sandboxed_(false), fd_(-1) {} +const char kNaClHelperAtZero[] = "--at-zero"; + void NaClForkDelegate::Init(const bool sandboxed, const int browserdesc, const int sandboxdesc) { @@ -48,18 +50,18 @@ void NaClForkDelegate::Init(const bool sandboxed, const bool use_helper = CommandLine::ForCurrentProcess()->HasSwitch( switches::kNaClLinuxHelper); FilePath helper_exe; - if (use_helper && PathService::Get(chrome::FILE_NACL_HELPER, &helper_exe)) { + FilePath helper_bootstrap_exe; + if (use_helper && + PathService::Get(chrome::FILE_NACL_HELPER, &helper_exe) && + PathService::Get(chrome::FILE_NACL_HELPER_BOOTSTRAP, + &helper_bootstrap_exe)) { CommandLine::StringVector argv = CommandLine::ForCurrentProcess()->argv(); - argv[0] = helper_exe.value(); + argv[0] = helper_bootstrap_exe.value(); + argv[1] = helper_exe.value(); + argv[2] = kNaClHelperAtZero; base::LaunchOptions options; options.fds_to_remap = &fds_to_map; options.clone_flags = CLONE_FS | SIGCHLD; - // LD_BIND_NOW forces non-lazy binding in the dynamic linker, to - // prevent the linker from trying to look at the text of the nacl_helper - // program after it has been replaced by the nacl module. - base::environment_vector env; - env.push_back(std::make_pair("LD_BIND_NOW", "1")); - options.environ = &env; ready_ = base::LaunchProcess(argv, options, NULL); // parent and error cases are handled below } diff --git a/chrome/nacl/nacl_helper_bootstrap_linux.c b/chrome/nacl/nacl_helper_bootstrap_linux.c index 7a0ace7..c534c39 100644 --- a/chrome/nacl/nacl_helper_bootstrap_linux.c +++ b/chrome/nacl/nacl_helper_bootstrap_linux.c @@ -2,23 +2,428 @@ * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * - * Bootstraping the nacl_helper. This executable reserves the bottom 1G - * of the address space, then invokes nacl_helper_init. Note that, - * as the text of this executable will eventually be overwritten by the - * native_client module, nacl_helper_init must not attempt to return. + * This is a standalone program that loads and runs the dynamic linker. + * This program itself must be linked statically. To keep it small, it's + * written to avoid all dependencies on libc and standard startup code. + * Hence, this should be linked using -nostartfiles. It must be compiled + * with -fno-stack-protector to ensure the compiler won't emit code that + * presumes some special setup has been done. + * + * On ARM, the compiler will emit calls to some libc functions, so we + * cannot link with -nostdlib. The functions it does use (memset and + * __aeabi_* functions for integer division) are sufficiently small and + * self-contained in ARM's libc.a that we don't have any problem using + * the libc definitions though we aren't using the rest of libc or doing + * any of the setup it might expect. + */ + +#include <elf.h> +#include <fcntl.h> +#include <link.h> +#include <stddef.h> +#include <stdint.h> +#include <sys/mman.h> + +#define MAX_PHNUM 12 + +#if defined(__i386__) +# define DYNAMIC_LINKER "/lib/ld-linux.so.2" +#elif defined(__x86_64__) +# define DYNAMIC_LINKER "/lib64/ld-linux-x86-64.so.2" +#elif defined(__ARM_EABI__) +# define DYNAMIC_LINKER "/lib/ld-linux.so.3" +#else +# error "Don't know the dynamic linker file name for this architecture!" +#endif + + +/* + * We're not using <string.h> functions here, to avoid dependencies. + * In the x86 libc, even "simple" functions like memset and strlen can + * depend on complex startup code, because in newer libc + * implementations they are defined using STT_GNU_IFUNC. + */ + +static void my_bzero(void *buf, size_t n) { + char *p = buf; + while (n-- > 0) + *p++ = 0; +} + +static size_t my_strlen(const char *s) { + size_t n = 0; + while (*s++ != '\0') + ++n; + return n; +} + + +/* + * Get inline functions for system calls. + */ +static int my_errno; +#define SYS_ERRNO my_errno +#include "third_party/lss/linux_syscall_support.h" + + +/* + * We're avoiding libc, so no printf. The only nontrivial thing we need + * is rendering numbers, which is, in fact, pretty trivial. + */ +static void iov_int_string(int value, struct kernel_iovec *iov, + char *buf, size_t bufsz) { + char *p = &buf[bufsz]; + do { + --p; + *p = "0123456789"[value % 10]; + value /= 10; + } while (value != 0); + iov->iov_base = p; + iov->iov_len = &buf[bufsz] - p; +} + +#define STRING_IOV(string_constant, cond) \ + { (void *) string_constant, cond ? (sizeof(string_constant) - 1) : 0 } + +__attribute__((noreturn)) static void fail(const char *message, + const char *item1, int value1, + const char *item2, int value2) { + char valbuf1[32]; + char valbuf2[32]; + struct kernel_iovec iov[] = { + STRING_IOV("bootstrap_helper", 1), + STRING_IOV(DYNAMIC_LINKER, 1), + STRING_IOV(": ", 1), + { (void *) message, my_strlen(message) }, + { (void *) item1, item1 == NULL ? 0 : my_strlen(item1) }, + STRING_IOV("=", item1 != NULL), + {}, + STRING_IOV(", ", item1 != NULL && item2 != NULL), + { (void *) item2, item2 == NULL ? 0 : my_strlen(item2) }, + STRING_IOV("=", item2 != NULL), + {}, + { "\n", 1 }, + }; + const int niov = sizeof(iov) / sizeof(iov[0]); + + if (item1 != NULL) + iov_int_string(value1, &iov[6], valbuf1, sizeof(valbuf1)); + if (item2 != NULL) + iov_int_string(value1, &iov[10], valbuf2, sizeof(valbuf2)); + + sys_writev(2, iov, niov); + sys_exit_group(2); + while (1) *(volatile int *) 0 = 0; /* Crash. */ +} + + +static int my_open(const char *file, int oflag) { + int result = sys_open(file, oflag, 0); + if (result < 0) + fail("Cannot open dynamic linker! ", "errno", my_errno, NULL, 0); + return result; +} + +static void my_pread(const char *fail_message, + int fd, void *buf, size_t bufsz, uintptr_t pos) { + ssize_t result = sys_pread64(fd, buf, bufsz, pos); + if (result < 0) + fail(fail_message, "errno", my_errno, NULL, 0); + if ((size_t) result != bufsz) + fail(fail_message, "read count", result, NULL, 0); +} + +static uintptr_t my_mmap(const char *segment_type, unsigned int segnum, + uintptr_t address, size_t size, + int prot, int flags, int fd, uintptr_t pos) { +#if defined(__NR_mmap2) + void *result = sys_mmap2((void *) address, size, prot, flags, fd, pos >> 12); +#else + void *result = sys_mmap((void *) address, size, prot, flags, fd, pos); +#endif + if (result == MAP_FAILED) + fail("Failed to map from dynamic linker! ", + segment_type, segnum, "errno", my_errno); + return (uintptr_t) result; +} + +static void my_mprotect(unsigned int segnum, + uintptr_t address, size_t size, int prot) { + if (sys_mprotect((void *) address, size, prot) < 0) + fail("Failed to mprotect hole in dynamic linker! ", + "segment", segnum, "errno", my_errno); +} + + +static int prot_from_phdr(const ElfW(Phdr) *phdr) { + int prot = 0; + if (phdr->p_flags & PF_R) + prot |= PROT_READ; + if (phdr->p_flags & PF_W) + prot |= PROT_WRITE; + if (phdr->p_flags & PF_X) + prot |= PROT_EXEC; + return prot; +} + +static uintptr_t round_up(uintptr_t value, uintptr_t size) { + return (value + size - 1) & -size; +} + +static uintptr_t round_down(uintptr_t value, uintptr_t size) { + return value & -size; +} + +/* + * Handle the "bss" portion of a segment, where the memory size + * exceeds the file size and we zero-fill the difference. For any + * whole pages in this region, we over-map anonymous pages. For the + * sub-page remainder, we zero-fill bytes directly. */ +static void handle_bss(unsigned int segnum, const ElfW(Phdr) *ph, + ElfW(Addr) load_bias, size_t pagesize) { + if (ph->p_memsz > ph->p_filesz) { + ElfW(Addr) file_end = ph->p_vaddr + load_bias + ph->p_filesz; + ElfW(Addr) file_page_end = round_up(file_end, pagesize); + ElfW(Addr) page_end = round_up(ph->p_vaddr + load_bias + + ph->p_memsz, pagesize); + if (page_end > file_page_end) + my_mmap("bss segment", segnum, + file_page_end, page_end - file_page_end, + prot_from_phdr(ph), MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0); + if (file_page_end > file_end && (ph->p_flags & PF_W)) + my_bzero((void *) file_end, file_page_end - file_end); + } +} + +/* + * This is the main loading code. It's called with the address of the + * auxiliary vector on the stack, which we need to examine and modify. + * It returns the dynamic linker's runtime entry point address, where + * we should jump to. This is called by the machine-dependent _start + * code (below). On return, it restores the original stack pointer + * and jumps to this entry point. + */ +ElfW(Addr) do_load(ElfW(auxv_t) *auxv) { + /* + * Record the auxv entries that are specific to the file loaded. + * The incoming entries point to our own static executable. + */ + ElfW(auxv_t) *av_entry = NULL; + ElfW(auxv_t) *av_phdr = NULL; + ElfW(auxv_t) *av_phnum = NULL; + size_t pagesize = 0; + + ElfW(auxv_t) *av; + for (av = auxv; + av_entry == NULL || av_phdr == NULL || av_phnum == NULL || pagesize == 0; + ++av) { + switch (av->a_type) { + case AT_NULL: + fail("Failed to find AT_ENTRY, AT_PHDR, AT_PHNUM, or AT_PAGESZ!", + NULL, 0, NULL, 0); + /*NOTREACHED*/ + break; + case AT_ENTRY: + av_entry = av; + break; + case AT_PAGESZ: + pagesize = av->a_un.a_val; + break; + case AT_PHDR: + av_phdr = av; + break; + case AT_PHNUM: + av_phnum = av; + break; + } + } + + int fd = my_open(DYNAMIC_LINKER, O_RDONLY); + + ElfW(Ehdr) ehdr; + my_pread("Failed to read ELF header from dynamic linker! ", + fd, &ehdr, sizeof(ehdr), 0); + + if (ehdr.e_ident[EI_MAG0] != ELFMAG0 || + ehdr.e_ident[EI_MAG1] != ELFMAG1 || + ehdr.e_ident[EI_MAG2] != ELFMAG2 || + ehdr.e_ident[EI_MAG3] != ELFMAG3 || + ehdr.e_version != EV_CURRENT || + ehdr.e_ehsize != sizeof(ehdr) || + ehdr.e_phentsize != sizeof(ElfW(Phdr))) + fail("Dynamic linker has no valid ELF header!", NULL, 0, NULL, 0); + + switch (ehdr.e_machine) { +#if defined(__i386__) + case EM_386: +#elif defined(__x86_64__) + case EM_X86_64: +#elif defined(__arm__) + case EM_ARM: +#else +# error "Don't know the e_machine value for this architecture!" +#endif + break; + default: + fail("Dynamic linker has wrong architecture! ", + "e_machine", ehdr.e_machine, NULL, 0); + break; + } + + ElfW(Phdr) phdr[MAX_PHNUM]; + if (ehdr.e_phnum > sizeof(phdr) / sizeof(phdr[0]) || ehdr.e_phnum < 1) + fail("Dynamic linker has unreasonable ", + "e_phnum", ehdr.e_phnum, NULL, 0); -#include <stdlib.h> + if (ehdr.e_type != ET_DYN) + fail("Dynamic linker not ET_DYN! ", + "e_type", ehdr.e_type, NULL, 0); -/* reserve 1GB of space */ -#define ONEGIG (1 << 30) -char nacl_reserved_space[ONEGIG]; + my_pread("Failed to read program headers from dynamic linker! ", + fd, phdr, sizeof(phdr[0]) * ehdr.e_phnum, ehdr.e_phoff); -void nacl_helper_init(int argc, char *argv[], - const char *nacl_reserved_space); + size_t i = 0; + while (i < ehdr.e_phnum && phdr[i].p_type != PT_LOAD) + ++i; + if (i == ehdr.e_phnum) + fail("Dynamic linker has no PT_LOAD header!", + NULL, 0, NULL, 0); -int main(int argc, char *argv[]) { - nacl_helper_init(argc, argv, nacl_reserved_space); - abort(); - return 0; // convince the tools I'm sane. + /* + * ELF requires that PT_LOAD segments be in ascending order of p_vaddr. + * Find the last one to calculate the whole address span of the image. + */ + const ElfW(Phdr) *first_load = &phdr[i]; + const ElfW(Phdr) *last_load = &phdr[ehdr.e_phnum - 1]; + while (last_load > first_load && last_load->p_type != PT_LOAD) + --last_load; + + size_t span = last_load->p_vaddr + last_load->p_memsz - first_load->p_vaddr; + + /* + * Map the first segment and reserve the space used for the rest and + * for holes between segments. + */ + const uintptr_t mapping = my_mmap("segment", first_load - phdr, + round_down(first_load->p_vaddr, pagesize), + span, prot_from_phdr(first_load), + MAP_PRIVATE, fd, + round_down(first_load->p_offset, pagesize)); + + const ElfW(Addr) load_bias = mapping - round_down(first_load->p_vaddr, + pagesize); + + if (first_load->p_offset > ehdr.e_phoff || + first_load->p_filesz < ehdr.e_phoff + (ehdr.e_phnum * sizeof(ElfW(Phdr)))) + fail("First load segment of dynamic linker does not contain phdrs!", + NULL, 0, NULL, 0); + + /* Point the auxv elements at the dynamic linker's phdrs and entry. */ + av_phdr->a_un.a_val = (ehdr.e_phoff - first_load->p_offset + + first_load->p_vaddr + load_bias); + av_phnum->a_un.a_val = ehdr.e_phnum; + av_entry->a_un.a_val = ehdr.e_entry + load_bias; + + handle_bss(first_load - phdr, first_load, load_bias, pagesize); + + ElfW(Addr) last_end = first_load->p_vaddr + load_bias + first_load->p_memsz; + + /* + * Map the remaining segments, and protect any holes between them. + */ + const ElfW(Phdr) *ph; + for (ph = first_load + 1; ph <= last_load; ++ph) { + if (ph->p_type == PT_LOAD) { + ElfW(Addr) last_page_end = round_up(last_end, pagesize); + + last_end = ph->p_vaddr + load_bias + ph->p_memsz; + ElfW(Addr) start = round_down(ph->p_vaddr + load_bias, pagesize); + ElfW(Addr) end = round_up(last_end, pagesize); + + if (start > last_page_end) + my_mprotect(ph - phdr, last_page_end, start - last_page_end, PROT_NONE); + + my_mmap("segment", ph - phdr, + start, end - start, + prot_from_phdr(ph), MAP_PRIVATE | MAP_FIXED, fd, + round_down(ph->p_offset, pagesize)); + + handle_bss(ph - phdr, ph, load_bias, pagesize); + } + } + + sys_close(fd); + + return ehdr.e_entry + load_bias; } + +/* + * We have to define the actual entry point code (_start) in assembly + * for each machine. The kernel startup protocol is not compatible + * with the normal C function calling convention. Here, we calculate + * the address of the auxiliary vector on the stack; call do_load + * (above) using the normal C convention as per the ABI; restore the + * original starting stack; and finally, jump to the dynamic linker's + * entry point address. + */ +#if defined(__i386__) +asm(".globl _start\n" + ".type _start,@function\n" + "_start:\n" + "xorl %ebp, %ebp\n" + "movl %esp, %ebx\n" /* Save starting SP in %ebx. */ + "andl $-16, %esp\n" /* Align the stack as per ABI. */ + "movl (%ebx), %eax\n" /* argc */ + "leal 8(%ebx,%eax,4), %ecx\n" /* envp */ + /* Find the envp element that is NULL, and auxv is past there. */ + "0: addl $4, %ecx\n" + "cmpl $0, -4(%ecx)\n" + "jne 0b\n" + "pushl %ecx\n" /* Argument: auxv. */ + "call do_load\n" + "movl %ebx, %esp\n" /* Restore the saved SP. */ + "jmp *%eax\n" /* Jump to the entry point. */ + ); +#elif defined(__x86_64__) +asm(".globl _start\n" + ".type _start,@function\n" + "_start:\n" + "xorq %rbp, %rbp\n" + "movq %rsp, %rbx\n" /* Save starting SP in %rbx. */ + "andq $-16, %rsp\n" /* Align the stack as per ABI. */ + "movq (%rbx), %rax\n" /* argc */ + "leaq 16(%rbx,%rax,8), %rdi\n" /* envp */ + /* Find the envp element that is NULL, and auxv is past there. */ + "0: addq $8, %rdi\n" + "cmpq $0, -8(%rdi)\n" + "jne 0b\n" + "call do_load\n" /* Argument already in %rdi: auxv */ + "movq %rbx, %rsp\n" /* Restore the saved SP. */ + "jmp *%rax\n" /* Jump to the entry point. */ + ); +#elif defined(__arm__) +asm(".globl _start\n" + ".type _start,#function\n" + "_start:\n" +#if defined(__thumb2__) + ".thumb\n" + ".syntax unified\n" +#endif + "mov fp, #0\n" + "mov lr, #0\n" + "mov r4, sp\n" /* Save starting SP in r4. */ + "ldr r1, [r4]\n" /* argc */ + "add r1, r1, #2\n" + "add r0, r4, r1, asl #2\n" /* envp */ + /* Find the envp element that is NULL, and auxv is past there. */ + "0: ldr r1, [r0], #4\n" + "cmp r1, #0\n" + "bne 0b\n" + "bl do_load\n" + "mov sp, r4\n" /* Restore the saved SP. */ + "blx r0\n" /* Jump to the entry point. */ + ); +#else +# error "Need stack-preserving _start code for this architecture!" +#endif diff --git a/chrome/nacl/nacl_helper_bootstrap_linux.x b/chrome/nacl/nacl_helper_bootstrap_linux.x new file mode 100644 index 0000000..5eae077 --- /dev/null +++ b/chrome/nacl/nacl_helper_bootstrap_linux.x @@ -0,0 +1,93 @@ +/* Copyright (c) 2011 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * This is a custom linker script used to build nacl_helper_bootstrap. + * It has a very special layout. This script will only work with input + * that is kept extremely minimal. If there are unexpected input sections + * not named here, the result will not be correct. + * + * We need to use a standalone loader program rather than just using a + * dynamically-linked program here because its entire address space will be + * taken over for the NaCl untrusted address space. A normal program would + * cause dynamic linker data structures to point to its .dynamic section, + * which is no longer available after startup. + * + * We need this special layout (and the nacl_helper_bootstrap_munge_phdr + * step) because simply having bss space large enough to reserve the + * address space would cause the kernel loader to think we're using that + * much anonymous memory and refuse to execute the program on a machine + * with not much memory available. + */ + +/* + * Set the entry point to the symbol called _start, which we define in assembly. + */ +ENTRY(_start) + +/* + * This is the address where the program text starts. + * We set this as low as we think we can get away with. + * The common settings for sysctl vm.mmap_min_addr range from 4k to 64k. + */ +TEXT_START = 0x10000; + +/* + * This is the top of the range we are trying to reserve, which is 1G + * for x86-32 and ARM. For an x86-64 zero-based sandbox, this really + * needs to be 36G. + */ +RESERVE_TOP = 1 << 30; + +/* + * We specify the program headers we want explicitly, to get the layout + * exactly right and to give the "reserve" segment p_flags of zero, so + * that it gets mapped as PROT_NONE. + */ +PHDRS { + text PT_LOAD FILEHDR PHDRS; + reserve PT_LOAD FLAGS(0); + stack PT_GNU_STACK FLAGS(6); /* RW, no E */ +} + +/* + * Now we lay out the sections across those segments. + */ +SECTIONS { + /* + * Here is the program itself. + */ + .text TEXT_START + SIZEOF_HEADERS : { + *(.note.gnu.build-id) + *(.text*) + *(.rodata*) + *(.eh_frame*) + } :text + etext = .; + + /* + * Now we move up to the next p_align increment, and place the dummy + * segment there. The linker emits this segment with the p_vaddr and + * p_memsz we want, which reserves the address space. But the linker + * gives it a p_filesz of zero. We have to edit the phdr after link + * time to give it a p_filesz matching its p_memsz. That way, the + * kernel doesn't think we are preallocating a huge amount of memory. + * It just maps it from the file, i.e. way off the end of the file, + * which is perfect for reserving the address space. + */ + . = ALIGN(CONSTANT(COMMONPAGESIZE)); + RESERVE_START = .; + .reserve : { + . = RESERVE_TOP - RESERVE_START; + } :reserve + + /* + * These are empty input sections the linker generates. + * If we don't discard them, they pollute the flags in the output segment. + */ + /DISCARD/ : { + *(.iplt) + *(.rel*) + *(.igot.plt) + } +} diff --git a/chrome/nacl/nacl_helper_bootstrap_munge_phdr.c b/chrome/nacl/nacl_helper_bootstrap_munge_phdr.c new file mode 100644 index 0000000..87fe73f --- /dev/null +++ b/chrome/nacl/nacl_helper_bootstrap_munge_phdr.c @@ -0,0 +1,66 @@ +/* Copyright (c) 2011 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * This is a trivial program to edit an ELF file in place, making + * one crucial modification to a program header. It's invoked: + * bootstrap_phdr_hacker FILENAME SEGMENT_NUMBER + * where SEGMENT_NUMBER is the zero-origin index of the program header + * we'll touch. This is a PT_LOAD with p_filesz of zero. We change its + * p_filesz to match its p_memsz value. + */ + +#include <errno.h> +#include <error.h> +#include <fcntl.h> +#include <gelf.h> +#include <libelf.h> +#include <stdlib.h> +#include <unistd.h> + +int main(int argc, char **argv) { + if (argc != 3) + error(1, 0, "Usage: %s FILENAME SEGMENT_NUMBER", argv[0]); + + const char *const file = argv[1]; + const int segment = atoi(argv[2]); + + int fd = open(file, O_RDWR); + if (fd < 0) + error(2, errno, "Cannot open %s for read/write", file); + + if (elf_version(EV_CURRENT) == EV_NONE) + error(2, 0, "elf_version: %s", elf_errmsg(-1)); + + Elf *elf = elf_begin(fd, ELF_C_RDWR, NULL); + if (elf == NULL) + error(2, 0, "elf_begin: %s", elf_errmsg(-1)); + + if (elf_flagelf(elf, ELF_C_SET, ELF_F_LAYOUT) == 0) + error(2, 0, "elf_flagelf: %s", elf_errmsg(-1)); + + GElf_Phdr phdr; + GElf_Phdr *ph = gelf_getphdr(elf, segment, &phdr); + if (ph == NULL) + error(2, 0, "gelf_getphdr: %s", elf_errmsg(-1)); + + if (ph->p_type != PT_LOAD) + error(3, 0, "Program header %d is %u, not PT_LOAD", + segment, (unsigned int) ph->p_type); + if (ph->p_filesz != 0) + error(3, 0, "Program header %d has nonzero p_filesz", segment); + + ph->p_filesz = ph->p_memsz; + if (gelf_update_phdr(elf, segment, ph) == 0) + error(2, 0, "gelf_update_phdr: %s", elf_errmsg(-1)); + + if (elf_flagphdr(elf, ELF_C_SET, ELF_F_DIRTY) == 0) + error(2, 0, "elf_flagphdr: %s", elf_errmsg(-1)); + + if (elf_update(elf, ELF_C_WRITE) < 0) + error(2, 0, "elf_update: %s", elf_errmsg(-1)); + + close(fd); + + return 0; +} diff --git a/chrome/nacl/nacl_helper_bootstrap_munge_phdr.py b/chrome/nacl/nacl_helper_bootstrap_munge_phdr.py new file mode 100755 index 0000000..c3a3931 --- /dev/null +++ b/chrome/nacl/nacl_helper_bootstrap_munge_phdr.py @@ -0,0 +1,36 @@ +#!/usr/bin/python +# Copyright (c) 2011 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# +# This takes three command-line arguments: +# MUNGE-PHDR-PROGRAM file name of program built from +# nacl_helper_bootstrap_munge_phdr.c +# INFILE raw linked ELF file name +# OUTFILE output file name +# +# We just run the MUNGE-PHDR-PROGRAM on a copy of INFILE. +# That modifies the file in place. Then we move it to OUTFILE. +# +# We only have this wrapper script because nacl_helper_bootstrap_munge_phdr.c +# wants to modify a file in place (and it would be a much longer and more +# fragile program if it created a fresh ELF output file instead). + +import shutil +import subprocess +import sys + + +def Main(argv): + if len(argv) != 4: + print 'Usage: %s MUNGE-PHDR-PROGRAM INFILE OUTFILE' % argv[0] + sys.exit(1) + [prog, munger, infile, outfile] = argv + tmpfile = outfile + '.tmp' + shutil.copy(infile, tmpfile) + segment_num = '1' + subprocess.check_call([munger, tmpfile, segment_num]) + shutil.move(tmpfile, outfile) + +if __name__ == '__main__': + Main(sys.argv) diff --git a/chrome/nacl/nacl_helper_exports.txt b/chrome/nacl/nacl_helper_exports.txt deleted file mode 100644 index af930c1..0000000 --- a/chrome/nacl/nacl_helper_exports.txt +++ /dev/null @@ -1,10 +0,0 @@ -# gnu-ld version script for exporting desired symbols from nacl_helper -# - -NACL_HELPER_1_0 { - global: - nacl_helper_init; - nacl_helper_get_1G_address; - local: - *; -}; diff --git a/chrome/nacl/nacl_helper_linux.cc b/chrome/nacl/nacl_helper_linux.cc index b53cf8d..7ffeadb 100644 --- a/chrome/nacl/nacl_helper_linux.cc +++ b/chrome/nacl/nacl_helper_linux.cc @@ -23,6 +23,7 @@ #include "content/common/main_function_params.h" #include "content/common/unix_domain_socket_posix.h" #include "ipc/ipc_switches.h" +#include "native_client/src/trusted/service_runtime/sel_memory.h" namespace { @@ -116,33 +117,20 @@ void HandleForkRequest(const std::vector<int>& child_fds) { } // namespace -static const void* g_nacl_reserved_space = NULL; -extern "C" __attribute__((visibility("default"))) -const void* nacl_helper_get_1G_address() { - return g_nacl_reserved_space; -} +static const char kNaClHelperAtZero[] = "at-zero"; -// nacl_helper_init does the real work of this module. It is invoked as -// a static constructor and never returns, preventing main() from the -// nacl_helper_bootstrap program from being called. -// -// NOTE This routine must not return. -extern "C" __attribute__((visibility("default"))) -void nacl_helper_init(int argc, char *argv[], - const char *nacl_reserved_space) { +int main(int argc, char *argv[]) { CommandLine::Init(argc, argv); base::AtExitManager exit_manager; base::RandUint64(); // acquire /dev/urandom fd before sandbox is raised std::vector<int> empty; // for SendMsg() calls g_suid_sandbox_active = (NULL != getenv("SBX_D")); - g_nacl_reserved_space = nacl_reserved_space; - if (!nacl_reserved_space) { - VLOG(1) << "nacl_reserved_space is NULL"; - } else { - VLOG(1) << "nacl_reserved_space is at " - << (void *)nacl_reserved_space; + + if (CommandLine::ForCurrentProcess()->HasSwitch(kNaClHelperAtZero)) { + g_nacl_prereserved_sandbox_addr = (void *) (uintptr_t) 0x10000; } + // Send the zygote a message to let it know we are ready to help if (!UnixDomainSocket::SendMsg(kNaClZygoteDescriptor, kNaClHelperStartupAck, |