/* -*- mode:c; c-file-style:"k&r"; c-basic-offset: 4; tab-width:4; indent-tabs-mode:nil; mode:auto-fill; fill-column:78; -*- */
/* vim: set ts=4 sw=4 et tw=78 fo=cqt wm=0: */
/* Copyright (C) 2014 OSCAR lab, Stony Brook University
This file is part of Graphene Library OS.
Graphene Library OS 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 3 of the
License, or (at your option) any later version.
Graphene Library OS 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 this program. If not, see . */
/*
* db_main.c
*
* This file contains the main function of the PAL loader, which loads and
* processes environment, arguments and manifest.
*/
#include "pal_defs.h"
#include "pal_linux_defs.h"
#include "pal.h"
#include "pal_internal.h"
#include "pal_linux.h"
#include "pal_debug.h"
#include "pal_error.h"
#include "pal_security.h"
#include "api.h"
#include
#include
#include
#include
#include
#include
#include
/* At the begining of entry point, rsp starts at argc, then argvs,
envps and auxvs. Here we store rsp to rdi, so it will not be
messed up by function calls */
asm (".global pal_start \n"
" .type pal_start,@function \n"
"pal_start: \n"
" movq %rsp, %rdi \n"
" call pal_linux_main \n");
#define RTLD_BOOTSTRAP
/* pal_start is the entry point of libpal.so, which calls pal_main */
#define _ENTRY pal_start
asm (".pushsection \".debug_gdb_scripts\", \"MS\",@progbits,1\r\n"
".byte 1\r\n"
".asciz \"" XSTRINGIFY(GDB_SCRIPT) "\"\r\n"
".popsection\r\n");
struct pal_linux_config pal_linux_config;
static size_t pagesz = PRESET_PAGESIZE;
static uid_t uid;
static gid_t gid;
#if USE_VDSO_GETTIME == 1
static ElfW(Addr) sysinfo_ehdr;
#endif
static void pal_init_bootstrap (void * args, int * pargc,
const char *** pargv,
const char *** penvp)
{
/*
* fetch arguments and environment variables, the previous stack
* pointer is in rdi (arg). The stack structure starting at rdi
* will look like:
* auxv[m - 1] = AT_NULL
* ...
* auxv[0]
* envp[n - 1] = NULL
* ...
* envp[0]
* argv[argc] = NULL
* argv[argc - 1]
* ...
* argv[0]
* argc
* ---------------------------------------
* user stack
*/
const char ** all_args = (const char **) args;
int argc = (uintptr_t) all_args[0];
const char ** argv = &all_args[1];
const char ** envp = argv + argc + 1;
/* fetch environment information from aux vectors */
void ** auxv = (void **) envp + 1;
for (; *(auxv - 1); auxv++);
ElfW(auxv_t) *av;
for (av = (ElfW(auxv_t) *)auxv ; av->a_type != AT_NULL ; av++)
switch (av->a_type) {
case AT_PAGESZ:
pagesz = av->a_un.a_val;
break;
case AT_UID:
case AT_EUID:
uid ^= av->a_un.a_val;
break;
case AT_GID:
case AT_EGID:
gid ^= av->a_un.a_val;
break;
#if USE_VDSO_GETTIME == 1
case AT_SYSINFO_EHDR:
sysinfo_ehdr = av->a_un.a_val;
break;
#endif
}
argv++;
argc--;
*pargc = argc;
*pargv = argv;
*penvp = envp;
}
unsigned long _DkGetPagesize (void)
{
return pagesz;
}
unsigned long _DkGetAllocationAlignment (void)
{
return pagesz;
}
static PAL_HANDLE try_open_runnable (const char * name, bool try_path,
const char ** uri)
{
PAL_HANDLE handle = NULL;
/* Try to open the manifest file specified by the first argument */
if (_DkStreamOpen(&handle, name, PAL_ACCESS_RDONLY, 0, 0, 0) == 0) {
if (uri)
*uri = name;
return handle;
}
if (!try_path)
return NULL;
/* might be a real path, let's try open it */
int fd = INLINE_SYSCALL(open, 3, name, O_RDONLY|O_CLOEXEC, 0);
if (IS_ERR(fd))
return NULL;
int len = strlen(name);
handle = malloc(HANDLE_SIZE(file) + len + 1);
SET_HANDLE_TYPE(handle, file);
handle->__in.flags |= RFD(0)|WFD(0)|WRITEABLE(0);
handle->file.fd = fd;
char * path = (void *) handle + HANDLE_SIZE(file);
memcpy(path, name, len + 1);
handle->file.realpath = path;
if (uri) {
char * new_uri = malloc(len + 6);
memcpy(new_uri, "file:", 5);
memcpy(new_uri + 5, name, len + 1);
*uri = new_uri;
}
return handle;
}
int read_shebang (const char ** argv)
{
/* must be a shebang */
int fd = INLINE_SYSCALL(open, 3, *argv, O_RDONLY|O_CLOEXEC, 0);
if (IS_ERR(fd)) {
bad_shebang:
INLINE_SYSCALL(close, 1, fd);
return -PAL_ERROR_INVAL;
}
/* the maximun length for shebang path is 80 chars */
char buffer[80];
int bytes = INLINE_SYSCALL(read, 3, fd, buffer, 80);
if (IS_ERR(bytes))
goto bad_shebang;
/* the format of shebang should be '#!/absoulte/path/of/pal' */
if (buffer[0] != '#' || buffer[1] != '!')
goto bad_shebang;
char * p = &buffer[2];
while (*p && *p != ' ' && *p != '\r' && *p != '\n')
p++;
int len = strlen(*argv);
PAL_HANDLE manifest = malloc(HANDLE_SIZE(file) + len + 1);
SET_HANDLE_TYPE(manifest, file);
manifest->__in.flags |= RFD(0)|WFD(0)|WRITEABLE(0);
manifest->file.fd = fd;
char * path = (void *) manifest + HANDLE_SIZE(file);
memcpy(path, *argv, len + 1);
manifest->file.realpath = path;
char * uri = malloc(len + 6);
memcpy(uri, "file:", 5);
memcpy(uri + 5, *argv, len + 1);
pal_config.manifest = uri;
pal_config.manifest_handle = manifest;
return 0;
}
#include "elf-x86_64.h"
#include "dynamic_link.h"
extern void setup_pal_map (const char * realname, ElfW(Dyn) ** dyn,
ElfW(Addr) addr);
void pal_linux_main (void * args)
{
int argc;
const char ** argv, ** envp;
/* parse argc, argv, envp and auxv */
pal_init_bootstrap(args, &argc, &argv, &envp);
ElfW(Addr) pal_addr = elf_machine_load_address();
ElfW(Dyn) * pal_dyn[DT_NUM + DT_THISPROCNUM + DT_VERSIONTAGNUM +
DT_EXTRANUM + DT_VALNUM + DT_ADDRNUM];
memset(pal_dyn, 0, sizeof(pal_dyn));
elf_get_dynamic_info((void *) pal_addr + elf_machine_dynamic(), pal_dyn,
pal_addr);
ELF_DYNAMIC_RELOCATE(pal_dyn, pal_addr);
allocsize = PRESET_PAGESIZE;
allocshift = PRESET_PAGESIZE - 1;
allocmask = ~(PRESET_PAGESIZE - 1);
init_slab_mgr();
setup_pal_map(XSTRINGIFY(SRCDIR) "/pal", pal_dyn, pal_addr);
/* jump to main function */
pal_main(argc, argv, envp);
}
int create_domain_dir (void)
{
int ret = 0;
const char * path;
ret = INLINE_SYSCALL(mkdir, 2, (path = GRAPHENE_PIPEDIR), 0777);
if (IS_ERR(ret) && ERRNO(ret) != EEXIST) {
if (ERRNO(ret) == ENOENT) {
ret = INLINE_SYSCALL(mkdir, 2, (path = GRAPHENE_TEMPDIR), 0777);
if (!IS_ERR(ret)) {
INLINE_SYSCALL(chmod, 2, GRAPHENE_TEMPDIR, 0777);
ret = INLINE_SYSCALL(mkdir, 2, (path = GRAPHENE_PIPEDIR), 0777);
}
}
if (IS_ERR(ret)) {
printf("Cannot create directory %s (%e), "
"please check permission\n", path, ERRNO(ret));
return -PAL_ERROR_DENIED;
}
}
if (!IS_ERR(ret))
INLINE_SYSCALL(chmod, 2, GRAPHENE_PIPEDIR, 0777);
char * pipedir = __alloca(sizeof(GRAPHENE_PIPEDIR) + 10);
unsigned int id;
do {
if (!getrand(&id, sizeof(unsigned int))) {
printf("Unable to generate random numbers\n");
return -PAL_ERROR_DENIED;
}
snprintf(pipedir, sizeof(GRAPHENE_PIPEDIR) + 10,
GRAPHENE_PIPEDIR "/%08x", id);
ret = INLINE_SYSCALL(mkdir, 2, pipedir, 0700);
if (IS_ERR(ret) && ERRNO(ret) != -EEXIST) {
printf("Cannot create directory %s (%e), "
"please fix permission\n", pipedir, ERRNO(ret));
return -PAL_ERROR_DENIED;
}
} while (IS_ERR(ret));
pal_sec_info.domain_id = id;
return 0;
}
#if USE_VDSO_GETTIME == 1
void setup_vdso_map (ElfW(Addr) addr);
#endif
static int loader_filter (const char * key, int len)
{
return memcmp(key, "loader.", 7);
}
int _DkInitHost (int * pargc, const char *** pargv)
{
int argc = *pargc;
const char ** argv = *pargv, * first_argv = NULL;
int ret = 0;
struct pal_proc_args proc_args;
void * proc_data;
bool in_child = false;
ret = INLINE_SYSCALL(read, 3, PROC_INIT_FD, &proc_args,
sizeof(proc_args));
if (IS_ERR(ret) && ERRNO(ret) != EBADF)
return -PAL_ERROR_DENIED;
if (!IS_ERR(ret)) {
in_child = true;
proc_data = __alloca(proc_args.data_size);
ret = INLINE_SYSCALL(read, 3, PROC_INIT_FD, proc_data,
proc_args.data_size);
if (IS_ERR(ret) || ret < proc_args.data_size)
return -PAL_ERROR_DENIED;
}
if (!in_child && !argc) {
printf("USAGE: libpal.so [executable|manifest] args ...\n");
return -PAL_ERROR_INVAL;
}
pal_linux_config.pid = INLINE_SYSCALL(getpid, 0);
pal_config.user_addr_end = (void *)
ALLOC_ALIGNDOWN(pal_config.lib_text_start);
/* look for lowest mappable address, starting at 0x400000 */
if (pal_sec_info.user_addr_base) {
pal_config.user_addr_start = pal_sec_info.user_addr_base;
} else {
void * base = (void *) 0x400000;
for (; base < pal_config.user_addr_end ;
base = (void *) ((unsigned long) base << 4)) {
void * mem = (void *) ARCH_MMAP(base, allocsize,
PROT_NONE,
MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE,
-1, 0);
if (IS_ERR_P(mem))
continue;
INLINE_SYSCALL(munmap, 2, mem, allocsize);
if (mem == base)
break;
}
pal_sec_info.user_addr_base = pal_config.user_addr_start = base;
}
signal_setup();
if (in_child) {
if ((ret = init_child_process(&proc_args, proc_data)) < 0)
return ret;
goto read_manifest;
}
/* occupy PROC_INIT_FD so no one will use it */
INLINE_SYSCALL(dup2, 2, 0, PROC_INIT_FD);
if (!(ret = read_shebang(argv)) < 0)
goto read_manifest;
PAL_HANDLE file = NULL;
const char * file_uri = NULL;
if (argv[0][0] != '-') {
file = try_open_runnable(argv[0], true, &file_uri);
if (!file)
return -PAL_ERROR_DENIED;
first_argv = argv[0];
argc--;
argv++;
/* the file laoded might be a executable */
if (check_elf_object(file)) {
pal_config.manifest = file_uri;
pal_config.manifest_handle = file;
goto read_manifest;
}
pal_config.exec = file_uri;
pal_config.exec_handle = file;
const char * manifest_uri;
char manifest_path[80];
snprintf(manifest_path, 80, "%s.manifest", pal_config.exec);
if ((file = try_open_runnable(manifest_path, false, &manifest_uri))) {
pal_config.manifest = manifest_uri;
pal_config.manifest_handle = file;
goto read_manifest;
}
}
if ((file = try_open_runnable("file:manifest", false, NULL))) {
pal_config.manifest = "file:manifest";
pal_config.manifest_handle = file;
goto read_manifest;
}
read_manifest:
if (!pal_config.manifest_handle) {
printf("Can't fine any manifest, going to run without one\n");
goto done_init;
}
PAL_STREAM_ATTR attr;
if ((ret = _DkStreamAttributesQuerybyHandle(pal_config.manifest_handle,
&attr)) < 0)
return ret;
pal_config.user_addr_end -= ALLOC_ALIGNUP(attr.size);
void * cfg_addr = pal_config.user_addr_end;
size_t cfg_size = attr.size;
if ((ret = _DkStreamMap(pal_config.manifest_handle, &cfg_addr,
PAL_PROT_READ, 0,
ALLOC_ALIGNUP(cfg_size))) < 0)
return ret;
struct config_store * root_config = malloc(sizeof(struct config_store));
root_config->raw_data = cfg_addr;
root_config->raw_size = cfg_size;
root_config->malloc = malloc;
root_config->free = free;
const char * errstring = NULL;
if ((ret = read_config(root_config, loader_filter, &errstring)) < 0) {
printf("Can't read manifest: %s\n", errstring);
return -PAL_ERROR_INVAL;
}
pal_config.root_config = root_config;
char cfgbuf[CONFIG_MAX];
int len;
if (!pal_linux_config.noexec && !pal_config.exec_handle) {
/* find executable in the manifest */
if ((len = get_config(root_config, "loader.exec", cfgbuf,
CONFIG_MAX)) > 0) {
if (!(file = try_open_runnable(cfgbuf, false, NULL)))
return -PAL_ERROR_DENIED;
if ((ret = check_elf_object(file)) < 0)
return ret;
pal_config.exec = remalloc(cfgbuf, len + 1);
pal_config.exec_handle = file;
}
}
if (!in_child) {
if ((len = get_config(root_config, "loader.execname", cfgbuf,
CONFIG_MAX)) > 0)
first_argv = remalloc(cfgbuf, len + 1);
if (!first_argv)
first_argv = pal_config.exec;
}
done_init:
if (!in_child && !pal_sec_info.domain_id) {
if ((ret = create_domain_dir()) < 0)
return ret;
}
PAL_HANDLE thread = malloc(HANDLE_SIZE(thread));
SET_HANDLE_TYPE(thread, thread);
thread->thread.tid = pal_linux_config.pid;
__pal_control.first_thread = thread;
#if USE_VDSO_GETTIME == 1
if (sysinfo_ehdr)
setup_vdso_map(sysinfo_ehdr);
#endif
if (!pal_sec_info.mcast_port) {
unsigned short mcast_port;
getrand(&mcast_port, sizeof(unsigned short));
if (mcast_port < 1024)
mcast_port += 1024;
pal_sec_info.mcast_port = mcast_port > 1024 ? mcast_port :
mcast_port + 1204;
}
__pal_control.broadcast_stream =
_DkBroadcastStreamOpen(pal_sec_info.mcast_port);
if (first_argv) {
argc++;
argv--;
argv[0] = first_argv;
}
*pargc = argc;
*pargv = argv;
return 0;
}