/* Copyright (C) 2014 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 Lesser 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ /* * db_process.c * * This source file contains functions to create a child process and terminate * the running process. Child does not inherit any objects or memory from its * parent pricess. A Parent process may not modify the execution of its * children. It can wait for a child to exit using its handle. Also, parent and * child may communicate through I/O streams provided by the parent to the child * at creation. */ #include "pal_defs.h" #include "pal_linux_defs.h" #include "pal.h" #include "pal_internal.h" #include "pal_linux.h" #include "pal_linux_error.h" #include "pal_debug.h" #include "pal_error.h" #include "pal_security.h" #include "pal_crypto.h" #include "spinlock.h" #include "api.h" #include #include #include typedef __kernel_pid_t pid_t; #include DEFINE_LIST(trusted_child); struct trusted_child { LIST_TYPE(trusted_child) list; sgx_measurement_t mr_enclave; char uri[]; }; DEFINE_LISTP(trusted_child); static LISTP_TYPE(trusted_child) trusted_children = LISTP_INIT; static spinlock_t trusted_children_lock = INIT_SPINLOCK_UNLOCKED; int register_trusted_child(const char * uri, const char * mr_enclave_str) { struct trusted_child * tc = NULL, * new; int uri_len = strlen(uri); spinlock_lock(&trusted_children_lock); LISTP_FOR_EACH_ENTRY(tc, &trusted_children, list) { if (!memcmp(tc->uri, uri, uri_len + 1)) { spinlock_unlock(&trusted_children_lock); return 0; } } spinlock_unlock(&trusted_children_lock); new = malloc(sizeof(struct trusted_child) + uri_len); if (!new) return -PAL_ERROR_NOMEM; INIT_LIST_HEAD(new, list); memcpy(new->uri, uri, uri_len + 1); char mr_enclave_text[sizeof(sgx_measurement_t) * 2 + 1] = "\0"; size_t nbytes = 0; for (; nbytes < sizeof(sgx_measurement_t) ; nbytes++) { char byte1 = mr_enclave_str[nbytes * 2]; char byte2 = mr_enclave_str[nbytes * 2 + 1]; unsigned char val = 0; if (byte1 == 0 || byte2 == 0) { break; } if (!(byte1 >= '0' && byte1 <= '9') && !(byte1 >= 'a' && byte1 <= 'f')) { break; } if (!(byte2 >= '0' && byte2 <= '9') && !(byte2 >= 'a' && byte2 <= 'f')) { break; } if (byte1 >= '0' && byte1 <= '9') val = byte1 - '0'; if (byte1 >= 'a' && byte1 <= 'f') val = byte1 - 'a' + 10; val *= 16; if (byte2 >= '0' && byte2 <= '9') val += byte2 - '0'; if (byte2 >= 'a' && byte2 <= 'f') val += byte2 - 'a' + 10; new->mr_enclave.m[nbytes] = val; snprintf(mr_enclave_text + nbytes * 2, 3, "%02x", val); } if (nbytes < sizeof(sgx_measurement_t)) { free(new); return -PAL_ERROR_INVAL; } SGX_DBG(DBG_S, "trusted: %s %s\n", mr_enclave_text, new->uri); spinlock_lock(&trusted_children_lock); LISTP_FOR_EACH_ENTRY(tc, &trusted_children, list) { if (!memcmp(tc->uri, uri, uri_len + 1)) { spinlock_unlock(&trusted_children_lock); free(new); return 0; } } LISTP_ADD_TAIL(new, &trusted_children, list); spinlock_unlock(&trusted_children_lock); return 0; } /* * For SGX, the creation of a child process requires a clean enclave and a secure channel * between the parent and child processes (enclaves). The establishment of the secure * channel must be resilient to a host-level, root-privilege adversary. Such an adversary * can either create arbitrary enclaves, or intercept the handshake protocol between the * parent and child enclaves to launch a man-in-the-middle attack. * * Prerequisites of a secure channel: * (1) A session key needs to be shared only between the parent and child enclaves. * * See the implementation in _DkStreamKeyExchange(). * When initializing an RPC stream, both ends of the stream needs to use * Diffie-Hellman to exchange a session key. The key will be used to both identify * the connection (to prevent man-in-the-middle attack) and for future encryption. * * (2) Both the parent and child enclaves need to be proven by the Intel CPU. * * See the implementation in _DkStreamReportRequest() and _DkStreamReportRespond(). * The two ends of the RPC stream need to exchange local attestation reports * signed by the Intel CPUs to prove themselves to be running inside enclaves * on the same platform. The local attestation reports contain no secret information * and can be verified cryptographically, and can be sent on an unencrypted channel. * * The flow of local attestation is as follows: * - Parent: Send targetinfo(Parent) to Child * - Child: Generate report(Child -> Parent) and send to Parent * - Parent: Verify report(Child -> Parent) * - Parent: Extract targetinfo(Child) from report(Child -> Parent) * and then generate report(Parent -> Child) * - Child: Verify report(Parent -> Child) * * (3) Both the parent and child enclaves need to have a white-listed measurement. * * See the implementation in check_child_mr_enclave() and check_parent_mr_enclave(). * For a child process, we check if the child's mr_enclave is listed as * "sgx.trusted_children.xxx = ..." in the manifest. * For a parent process, we currently don't check its mr_enclave in the child. * This is a limitation because listing the parent's mr_enclave in the child's * manifest will change the child's mr_enclave, which then needs to be updated * in the parent's manifest, and eventually falls into a loop of updating both * manifest files. * * (4) The two parties who create the session key need to be the ones proven by the CPU * (for preventing man-in-the-middle attacks). * * See the implementation in check_child_mr_enclave() and check_parent_mr_enclave(). * The local reports from both sides will contain a MAC, generated by hashing * the unique enclave ID (a 64-bit integer) using AES-CMAC with the session key. * Because both the enclave ID and the session key are randomly created for each * enclave, no report can be reused even from an enclave with the same mr_enclave. */ struct proc_data { sgx_mac_t eid_mac; }; static int generate_sign_data(const PAL_SESSION_KEY* session_key, uint64_t enclave_id, sgx_sign_data_t* sign_data) { struct proc_data data; int ret = lib_AESCMAC((uint8_t*)session_key, sizeof(*session_key), (uint8_t*)&enclave_id, sizeof(enclave_id), (uint8_t*)&data.eid_mac, sizeof(data.eid_mac)); if (ret < 0) return ret; SGX_DBG(DBG_P|DBG_S, "Enclave identifier: %016lx -> %s\n", enclave_id, ALLOCA_BYTES2HEXSTR(data.eid_mac)); /* Copy proc_data into sgx_sign_data_t */ assert(sizeof(data) <= sizeof(*sign_data)); memset(sign_data, 0, sizeof(*sign_data)); memcpy(sign_data, &data, sizeof(data)); return 0; } static int check_child_mr_enclave(PAL_HANDLE child, sgx_measurement_t* mr_enclave, struct pal_enclave_state* remote_state) { /* the process must be a clean process */ if (remote_state->enclave_flags & PAL_ENCLAVE_INITIALIZED) return 1; sgx_sign_data_t sign_data; int ret = generate_sign_data(&child->process.session_key, remote_state->enclave_id, &sign_data); if (ret < 0) return ret; /* must make sure the signer of the report is also the owner of the key, in order to prevent man-in-the-middle attack */ if (memcmp(&remote_state->enclave_data, &sign_data, sizeof(sign_data))) return 1; /* Always accept the same mr_enclave as child process */ if (!memcmp(mr_enclave, &pal_sec.mr_enclave, sizeof(sgx_measurement_t))) { SGX_DBG(DBG_S, "trusted child: \n"); return 0; } struct trusted_child * tc; spinlock_lock(&trusted_children_lock); /* Try to find a matching mr_enclave from the manifest */ LISTP_FOR_EACH_ENTRY(tc, &trusted_children, list) { if (!memcmp(mr_enclave, &tc->mr_enclave, sizeof(sgx_measurement_t))) { spinlock_unlock(&trusted_children_lock); SGX_DBG(DBG_S, "trusted child: %s\n", tc->uri); return 0; } } spinlock_unlock(&trusted_children_lock); return 1; } int _DkProcessCreate (PAL_HANDLE * handle, const char * uri, const char ** args) { /* only access creating process with regular file */ if (!strstartswith_static(uri, URI_PREFIX_FILE)) return -PAL_ERROR_INVAL; unsigned int child_pid; int stream_fd; int cargo_fd; int nargs = 0, ret; if (args) for (const char ** a = args ; *a ; a++) nargs++; ret = ocall_create_process(uri, nargs, args, &stream_fd, &cargo_fd, &child_pid); if (ret < 0) return ret; PAL_HANDLE child = malloc(HANDLE_SIZE(process)); SET_HANDLE_TYPE(child, process); HANDLE_HDR(child)->flags |= RFD(0)|WFD(0)|RFD(1)|WFD(1); child->process.stream = stream_fd; child->process.cargo = cargo_fd; child->process.pid = child_pid; child->process.nonblocking = PAL_FALSE; child->process.ssl_ctx = NULL; ret = _DkStreamKeyExchange(child, &child->process.session_key); if (ret < 0) goto failed; sgx_sign_data_t sign_data; ret = generate_sign_data(&child->process.session_key, pal_enclave_state.enclave_id, &sign_data); if (ret < 0) goto failed; ret = _DkStreamReportRequest(child, &sign_data, &check_child_mr_enclave); if (ret < 0) goto failed; ret = _DkStreamSecureInit(child, /*is_server=*/true, &child->process.session_key, (LIB_SSL_CONTEXT**)&child->process.ssl_ctx); if (ret < 0) goto failed; *handle = child; return 0; failed: free(child); return ret; } static int check_parent_mr_enclave(PAL_HANDLE parent, sgx_measurement_t* mr_enclave, struct pal_enclave_state* remote_state) { __UNUSED(mr_enclave); sgx_sign_data_t sign_data; int ret = generate_sign_data(&parent->process.session_key, remote_state->enclave_id, &sign_data); if (ret < 0) return ret; if (memcmp(&remote_state->enclave_data, &sign_data, sizeof(sign_data))) return 1; /* XXX: For now, accept any enclave, but eventually should challenge the parent process */ return 0; } int init_child_process (PAL_HANDLE * parent_handle) { PAL_HANDLE parent = malloc(HANDLE_SIZE(process)); SET_HANDLE_TYPE(parent, process); HANDLE_HDR(parent)->flags |= RFD(0)|WFD(0)|RFD(1)|WFD(1); parent->process.stream = pal_sec.stream_fd; parent->process.cargo = pal_sec.cargo_fd; parent->process.pid = pal_sec.ppid; parent->process.nonblocking = PAL_FALSE; parent->process.ssl_ctx = NULL; int ret = _DkStreamKeyExchange(parent, &parent->process.session_key); if (ret < 0) return ret; sgx_sign_data_t sign_data; ret = generate_sign_data(&parent->process.session_key, pal_enclave_state.enclave_id, &sign_data); if (ret < 0) return ret; ret = _DkStreamReportRespond(parent, &sign_data, &check_parent_mr_enclave); if (ret < 0) return ret; ret = _DkStreamSecureInit(parent, /*is_server=*/false, &parent->process.session_key, (LIB_SSL_CONTEXT**)&parent->process.ssl_ctx); if (ret < 0) return ret; *parent_handle = parent; return 0; } void print_alloced_pages (void); noreturn void _DkProcessExit (int exitcode) { #if PRINT_ENCLAVE_STAT print_alloced_pages(); #endif if (exitcode) SGX_DBG(DBG_I, "DkProcessExit: Returning exit code %d\n", exitcode); ocall_exit(exitcode, /*is_exitgroup=*/true); while (true) { /* nothing */; } } static int64_t proc_read (PAL_HANDLE handle, uint64_t offset, uint64_t count, void * buffer) { if (offset) return -PAL_ERROR_INVAL; if (count != (uint32_t)count) return -PAL_ERROR_INVAL; ssize_t bytes; if (handle->process.ssl_ctx) { bytes = _DkStreamSecureRead(handle->process.ssl_ctx, buffer, count); } else { bytes = ocall_read(handle->process.stream, buffer, count); bytes = IS_ERR(bytes) ? unix_to_pal_error(ERRNO(bytes)) : bytes; } return bytes; } static int64_t proc_write (PAL_HANDLE handle, uint64_t offset, uint64_t count, const void * buffer) { if (offset) return -PAL_ERROR_INVAL; if (count != (uint32_t)count) return -PAL_ERROR_INVAL; ssize_t bytes; if (handle->process.ssl_ctx) { bytes = _DkStreamSecureWrite(handle->process.ssl_ctx, buffer, count); } else { bytes = ocall_write(handle->process.stream, buffer, count); bytes = IS_ERR(bytes) ? unix_to_pal_error(ERRNO(bytes)) : bytes; } return bytes; } static int proc_close (PAL_HANDLE handle) { if (handle->process.stream != PAL_IDX_POISON) { ocall_close(handle->process.stream); handle->process.stream = PAL_IDX_POISON; } if (handle->process.cargo != PAL_IDX_POISON) { ocall_close(handle->process.cargo); handle->process.cargo = PAL_IDX_POISON; } if (handle->process.ssl_ctx) { _DkStreamSecureFree((LIB_SSL_CONTEXT*)handle->process.ssl_ctx); handle->process.ssl_ctx = NULL; } return 0; } static int proc_delete (PAL_HANDLE handle, int access) { int shutdown; switch (access) { case 0: shutdown = SHUT_RDWR; break; case PAL_DELETE_RD: shutdown = SHUT_RD; break; case PAL_DELETE_WR: shutdown = SHUT_WR; break; default: return -PAL_ERROR_INVAL; } if (handle->process.stream != PAL_IDX_POISON) ocall_shutdown(handle->process.stream, shutdown); if (handle->process.cargo != PAL_IDX_POISON) ocall_shutdown(handle->process.cargo, shutdown); return 0; } static int proc_attrquerybyhdl(PAL_HANDLE handle, PAL_STREAM_ATTR* attr) { int ret; if (handle->process.stream == PAL_IDX_POISON) return -PAL_ERROR_BADHANDLE; attr->handle_type = HANDLE_HDR(handle)->type; attr->nonblocking = handle->process.nonblocking; attr->disconnected = HANDLE_HDR(handle)->flags & ERROR(0); attr->secure = handle->process.ssl_ctx ? PAL_TRUE : PAL_FALSE; /* get number of bytes available for reading */ ret = ocall_fionread(handle->process.stream); if (IS_ERR(ret)) return unix_to_pal_error(ERRNO(ret)); attr->pending_size = ret; /* query if there is data available for reading */ struct pollfd pfd = {.fd = handle->process.stream, .events = POLLIN | POLLOUT, .revents = 0}; ret = ocall_poll(&pfd, 1, 0); if (IS_ERR(ret)) return unix_to_pal_error(ERRNO(ret)); attr->readable = ret == 1 && (pfd.revents & (POLLIN | POLLERR | POLLHUP)) == POLLIN; attr->writable = ret == 1 && (pfd.revents & (POLLOUT | POLLERR | POLLHUP)) == POLLOUT; return 0; } static int proc_attrsetbyhdl (PAL_HANDLE handle, PAL_STREAM_ATTR * attr) { if (handle->process.stream == PAL_IDX_POISON) return -PAL_ERROR_BADHANDLE; if (attr->nonblocking != handle->process.nonblocking) { int ret = ocall_fsetnonblock(handle->process.stream, handle->process.nonblocking); if (IS_ERR(ret)) return unix_to_pal_error(ERRNO(ret)); handle->process.nonblocking = attr->nonblocking; } if (!attr->secure && handle->process.ssl_ctx) { /* remove TLS protection from process.stream */ _DkStreamSecureFree((LIB_SSL_CONTEXT*)handle->process.ssl_ctx); handle->process.ssl_ctx = NULL; } else if (attr->secure && !handle->process.ssl_ctx) { /* adding TLS protection for process.stream is not yet implemented */ SGX_DBG(DBG_E, "Securing a non-secure process handle is not supported!\n"); return -PAL_ERROR_NOTSUPPORT; } return 0; } struct handle_ops proc_ops = { .read = &proc_read, .write = &proc_write, .close = &proc_close, .delete = &proc_delete, .attrquerybyhdl = &proc_attrquerybyhdl, .attrsetbyhdl = &proc_attrsetbyhdl, };