From f6f7f5cc3576f432e3d79a6a4df7e24fa21c2f3f Mon Sep 17 00:00:00 2001 From: Mariusz Zaborski Date: Tue, 6 Sep 2022 13:56:17 -0400 Subject: [PATCH] [LibOS,PAL] Introduce extra runtime configuration (currently only 'resolv.conf') To accomplish this, Gramine obtains information from the host, sanitizes it, and stores it in the global PAL state. Later, LibOS uses it to create a pseudo file (using pseudofs filesystem). Signed-off-by: Mariusz Zaborski --- CI-Examples/python/python.manifest.template | 5 +- .../ra-tls-mbedtls/client.manifest.template | 3 +- .../secret_prov/client.manifest.template | 3 +- .../client.manifest.template | 3 +- .../secret_prov_pf/client.manifest.template | 3 +- Documentation/manifest-syntax.rst | 34 ++ common/include/api.h | 1 + common/src/string/ctype.c | 4 + libos/include/libos_fs.h | 1 + libos/include/libos_fs_pseudo.h | 3 + libos/src/fs/etc/fs.c | 157 ++++++++ libos/src/fs/libos_fs.c | 7 +- libos/src/meson.build | 1 + libos/src/sys/libos_clone.c | 1 + pal/include/host/linux-common/etc_host_info.h | 10 + .../linux-common/etc_host_info_internal.h | 13 + pal/include/pal/pal.h | 32 ++ pal/regression/ipv4_parser.c | 102 ++++++ pal/regression/ipv6_parser.c | 112 ++++++ pal/regression/meson.build | 29 +- pal/regression/test_pal.py | 9 + pal/regression/tests.toml | 2 + pal/src/host/linux-common/etc_host_info.c | 338 ++++++++++++++++++ pal/src/host/linux-common/meson.build | 1 + pal/src/host/linux-sgx/enclave_ecalls.c | 3 +- pal/src/host/linux-sgx/host_ecalls.c | 3 +- pal/src/host/linux-sgx/host_ecalls.h | 3 +- pal/src/host/linux-sgx/host_main.c | 28 +- pal/src/host/linux-sgx/pal_ecall_types.h | 19 +- pal/src/host/linux-sgx/pal_linux.h | 2 +- pal/src/host/linux-sgx/pal_main.c | 135 ++++++- pal/src/host/linux/pal_main.c | 23 ++ 32 files changed, 1060 insertions(+), 30 deletions(-) create mode 100644 libos/src/fs/etc/fs.c create mode 100644 pal/include/host/linux-common/etc_host_info.h create mode 100644 pal/include/host/linux-common/etc_host_info_internal.h create mode 100644 pal/regression/ipv4_parser.c create mode 100644 pal/regression/ipv6_parser.c create mode 100644 pal/src/host/linux-common/etc_host_info.c diff --git a/CI-Examples/python/python.manifest.template b/CI-Examples/python/python.manifest.template index c8f4219acf..9f64a4907f 100644 --- a/CI-Examples/python/python.manifest.template +++ b/CI-Examples/python/python.manifest.template @@ -22,10 +22,12 @@ fs.mounts = [ { type = "tmpfs", path = "/tmp" }, ] +sys.stack.size = "2M" +sys.enable_extra_runtime_domain_names_conf = true + sgx.debug = true sgx.nonpie_binary = true sgx.enclave_size = "512M" -sys.stack.size = "2M" sgx.thread_num = 32 sgx.remote_attestation = "{{ ra_type }}" @@ -53,5 +55,4 @@ sgx.allowed_files = [ "file:/etc/passwd", "file:/etc/gai.conf", "file:/etc/host.conf", - "file:/etc/resolv.conf", ] diff --git a/CI-Examples/ra-tls-mbedtls/client.manifest.template b/CI-Examples/ra-tls-mbedtls/client.manifest.template index 008d13aaea..3a96ab1e9e 100644 --- a/CI-Examples/ra-tls-mbedtls/client.manifest.template +++ b/CI-Examples/ra-tls-mbedtls/client.manifest.template @@ -22,6 +22,8 @@ fs.mounts = [ { path = "/client", uri = "file:client" }, ] +sys.enable_extra_runtime_domain_names_conf = true + sgx.debug = true sgx.enclave_size = "512M" sgx.thread_num = 4 @@ -39,7 +41,6 @@ sgx.trusted_files = [ sgx.allowed_files = [ "file:/etc/nsswitch.conf", "file:/etc/host.conf", - "file:/etc/resolv.conf", "file:/etc/ethers", "file:/etc/hosts", "file:/etc/group", diff --git a/CI-Examples/ra-tls-secret-prov/secret_prov/client.manifest.template b/CI-Examples/ra-tls-secret-prov/secret_prov/client.manifest.template index 48374a333e..eb946aba58 100644 --- a/CI-Examples/ra-tls-secret-prov/secret_prov/client.manifest.template +++ b/CI-Examples/ra-tls-secret-prov/secret_prov/client.manifest.template @@ -18,6 +18,8 @@ fs.mounts = [ { path = "/ca.crt", uri = "file:../ssl/ca.crt" }, ] +sys.enable_extra_runtime_domain_names_conf = true + sgx.enclave_size = "512M" sgx.debug = true @@ -42,5 +44,4 @@ sgx.allowed_files = [ "file:/etc/group", "file:/etc/passwd", "file:/etc/gai.conf", - "file:/etc/resolv.conf", ] diff --git a/CI-Examples/ra-tls-secret-prov/secret_prov_minimal/client.manifest.template b/CI-Examples/ra-tls-secret-prov/secret_prov_minimal/client.manifest.template index a9a707af92..2b2898d927 100644 --- a/CI-Examples/ra-tls-secret-prov/secret_prov_minimal/client.manifest.template +++ b/CI-Examples/ra-tls-secret-prov/secret_prov_minimal/client.manifest.template @@ -22,6 +22,8 @@ fs.mounts = [ { path = "/ca.crt", uri = "file:../ssl/ca.crt" }, ] +sys.enable_extra_runtime_domain_names_conf = true + sgx.enclave_size = "512M" sgx.debug = true @@ -46,5 +48,4 @@ sgx.allowed_files = [ "file:/etc/group", "file:/etc/passwd", "file:/etc/gai.conf", - "file:/etc/resolv.conf", ] diff --git a/CI-Examples/ra-tls-secret-prov/secret_prov_pf/client.manifest.template b/CI-Examples/ra-tls-secret-prov/secret_prov_pf/client.manifest.template index 9a691906f0..037728bdf7 100644 --- a/CI-Examples/ra-tls-secret-prov/secret_prov_pf/client.manifest.template +++ b/CI-Examples/ra-tls-secret-prov/secret_prov_pf/client.manifest.template @@ -24,6 +24,8 @@ fs.mounts = [ { path = "/files/", uri = "file:enc_files/", type = "encrypted" }, ] +sys.enable_extra_runtime_domain_names_conf = true + sgx.enclave_size = "512M" sgx.debug = true @@ -48,5 +50,4 @@ sgx.allowed_files = [ "file:/etc/group", "file:/etc/passwd", "file:/etc/gai.conf", - "file:/etc/resolv.conf", ] diff --git a/Documentation/manifest-syntax.rst b/Documentation/manifest-syntax.rst index 6892048f34..fe8231601a 100644 --- a/Documentation/manifest-syntax.rst +++ b/Documentation/manifest-syntax.rst @@ -145,6 +145,40 @@ source. that encryption key provisioning currently happens after setting up arguments. +Domain names configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sys.enable_extra_runtime_domain_names_conf = [true|false] + (Default: false) + +This option will generate following extra runtime files: + +- ``/etc/resolv.conf`` + Supported keywords: + + - ``nameserver`` + - ``search`` + - ``options`` (``inet6`` | ``rotate``) + +Unsupported keywords and malformed lines are ignored, and invalid values are +reported as an error. + +This functionality is achieved by taking the host's configuration via various +APIs and reading the host's configuration files. In the case of Linux PAL, +most information comes from the host's ``/etc``. The gathered information is +used to create ``/etc`` files inside Gramine's file system, or change Gramine +process configuration. For security-enforcing modes (such as SGX), Gramine +additionally sanitizes the information gathered from the host. + +Note that Gramine supports only a subset of the configuration. +Refer to the list of supported keywords. + +This option takes precedence over ``fs.mounts``. +This means that etc files provided via ``fs.mounts`` will be overridden with +the ones added via this option. + Environment variables ^^^^^^^^^^^^^^^^^^^^^ diff --git a/common/include/api.h b/common/include/api.h index 3ca74d409b..6f29ad1de7 100644 --- a/common/include/api.h +++ b/common/include/api.h @@ -236,6 +236,7 @@ int tolower(int c); int toupper(int c); int isalpha(int c); int isdigit(int c); +int isxdigit(int c); int isalnum(int c); char* strchr(const char* s, int c); diff --git a/common/src/string/ctype.c b/common/src/string/ctype.c index 813e2deb5b..80d3d50621 100644 --- a/common/src/string/ctype.c +++ b/common/src/string/ctype.c @@ -31,6 +31,10 @@ int isdigit(int c) { return (unsigned)c - '0' < 10; } +int isxdigit(int c) { + return isdigit(c) || (unsigned)tolower(c) - 'a' < 6; +} + int isalnum(int c) { return isalpha(c) || isdigit(c); } diff --git a/libos/include/libos_fs.h b/libos/include/libos_fs.h index eb1a75e12f..0d6f785f1f 100644 --- a/libos/include/libos_fs.h +++ b/libos/include/libos_fs.h @@ -518,6 +518,7 @@ extern struct libos_dentry* g_dentry_root; int init_fs(void); int init_mount_root(void); int init_mount(void); +int mount_etcfs(void); /* file system operations */ diff --git a/libos/include/libos_fs_pseudo.h b/libos/include/libos_fs_pseudo.h index 92461db131..70b0592173 100644 --- a/libos/include/libos_fs_pseudo.h +++ b/libos/include/libos_fs_pseudo.h @@ -257,3 +257,6 @@ int sys_print_as_ranges(char* buf, size_t buf_size, size_t count, int sys_print_as_bitmask(char* buf, size_t buf_size, size_t count, bool (*is_present)(size_t ind, const void* arg), const void* callback_arg); + +/* etcfs */ +int init_etcfs(void); diff --git a/libos/src/fs/etc/fs.c b/libos/src/fs/etc/fs.c new file mode 100644 index 0000000000..d531dc8000 --- /dev/null +++ b/libos/src/fs/etc/fs.c @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: LGPL-3.0-or-later */ +/* Copyright (C) 2022 Intel Corporation + * Mariusz Zaborski + */ + +/* + * This file contains the implementation of `etc` FS. + * LibOS assumes that contents of all data obtained from host were already sanitized. + */ + +#include "libos_checkpoint.h" +#include "libos_fs.h" +#include "libos_fs_pseudo.h" + +#define OPTION_INET6 "options inet6\n" +#define OPTION_ROTATE "options rotate\n" + +static int put_string(char** buf, size_t* bufsize, const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + int ret = vsnprintf(*buf, *bufsize, fmt, ap); + va_end(ap); + if (ret < 0) + return ret; + if ((size_t)ret >= *bufsize) + return -EOVERFLOW; + *bufsize -= ret; + *buf += ret; + + return 0; +} + +static int provide_etc_resolv_conf(struct libos_dentry* dent, char** out_data, size_t* out_size) { + __UNUSED(dent); + + size_t size = 0; + + /* Estimate the size of buffer: */ + /* nameservers - let's assume all entries will be IPv6 plus a new line */ + size += g_pal_public_state->dns_host.nsaddr_list_count + * (strlen("nameserver ") + MAX_IPV6_ADDR_LEN + 1); + /* search - let's assume maximum length of entries, plus a new line and white spaces */ + size += strlen("search"); + size += g_pal_public_state->dns_host.dn_search_count * (PAL_HOSTNAME_MAX + 1); + size += 1; + /* and let's add some space for each option */ + size += (g_pal_public_state->dns_host.inet6 ? strlen(OPTION_INET6) : 0) + + (g_pal_public_state->dns_host.rotate ? strlen(OPTION_ROTATE) : 0); + /* make space for terminating character */ + size += 1; + + char* data = malloc(size); + if (!data) + return -ENOMEM; + memset(data, 0, size); + + /* Generate data: */ + size_t space_left = size; + char* ptr = data; + int ret; + for (size_t i = 0; i < g_pal_public_state->dns_host.nsaddr_list_count; i++) { + if (!g_pal_public_state->dns_host.nsaddr_list[i].is_ipv6) { + uint32_t addr = g_pal_public_state->dns_host.nsaddr_list[i].ipv4; + ret = put_string(&ptr, &space_left, "nameserver %u.%u.%u.%u\n", + (addr & 0xFF000000) >> 24, (addr & 0x00FF0000) >> 16, + (addr & 0x0000FF00) >> 8, (addr & 0x000000FF)); + } else { + uint16_t* addrv6 = g_pal_public_state->dns_host.nsaddr_list[i].ipv6; + ret = put_string(&ptr, &space_left, "nameserver %x:%x:%x:%x:%x:%x:%x:%x\n", + addrv6[0], addrv6[1], addrv6[2], addrv6[3], addrv6[4], addrv6[5], + addrv6[6], addrv6[7]); + } + if (ret < 0) + goto out; + } + + if (g_pal_public_state->dns_host.dn_search_count > 0) { + ret = put_string(&ptr, &space_left, "search"); + if (ret < 0) + goto out; + for (size_t i = 0; i < g_pal_public_state->dns_host.dn_search_count; i++) { + ret = put_string(&ptr, &space_left, " %s", g_pal_public_state->dns_host.dn_search[i]); + if (ret < 0) + goto out; + } + ret = put_string(&ptr, &space_left, "\n"); + if (ret < 0) + goto out; + } + if (g_pal_public_state->dns_host.inet6) { + ret = put_string(&ptr, &space_left, OPTION_INET6); + if (ret < 0) + goto out; + } + if (g_pal_public_state->dns_host.rotate) { + ret = put_string(&ptr, &space_left, OPTION_ROTATE); + if (ret < 0) + goto out; + } + + /* Use the string (without null terminator) as file data */ + size_t finalsize = strlen(data); + char* finalbuf = malloc(finalsize); + if (!finalbuf) { + ret = -ENOMEM; + goto out; + } + assert(finalsize < size); + memcpy(finalbuf, data, finalsize); + + *out_data = finalbuf; + *out_size = finalsize; + + ret = 0; +out: + free(data); + return ret; +} + +int init_etcfs(void) { + pseudo_add_str(NULL, "emulated-etc-resolv-conf", &provide_etc_resolv_conf); + return 0; +} + +int mount_etcfs(void) { + if (!g_pal_public_state->extra_runtime_domain_names_conf) + return 0; + + return mount_fs(&(struct libos_mount_params){ + .type = "pseudo", + .path = "/etc/resolv.conf", + .uri = "emulated-etc-resolv-conf", + }); +} + +BEGIN_CP_FUNC(etc_info) { + __UNUSED(size); + __UNUSED(obj); + __UNUSED(objp); + + /* Propagate DNS configuration */ + size_t off = ADD_CP_OFFSET(sizeof(g_pal_public_state->dns_host)); + struct dns_host* new_dns_host = (struct dns_host*)(base + off); + memcpy(new_dns_host, &g_pal_public_state->dns_host, sizeof(g_pal_public_state->dns_host)); + + ADD_CP_FUNC_ENTRY(off); +} +END_CP_FUNC(etc_info) + +BEGIN_RS_FUNC(etc_info) { + __UNUSED(offset); + __UNUSED(rebase); + + const struct dns_host* dns_host = (const struct dns_host*)(base + GET_CP_FUNC_ENTRY()); + memcpy(&g_pal_public_state->dns_host, dns_host, sizeof(g_pal_public_state->dns_host)); +} +END_RS_FUNC(etc_info) diff --git a/libos/src/fs/libos_fs.c b/libos/src/fs/libos_fs.c index 7e850dad92..dc893fbe1b 100644 --- a/libos/src/fs/libos_fs.c +++ b/libos/src/fs/libos_fs.c @@ -75,6 +75,9 @@ int init_fs(void) { if ((ret = init_sysfs()) < 0) goto err; + if ((ret = init_etcfs()) < 0) + goto err; + return 0; err: @@ -650,7 +653,9 @@ int init_mount(void) { } /* Otherwise `cwd` is already initialized. */ - return 0; + /* The mount_etcfs takes precedence over user's fs.mounts, and because of that, + * it has to be called at the end. */ + return mount_etcfs(); } struct libos_fs* find_fs(const char* name) { diff --git a/libos/src/meson.build b/libos/src/meson.build index 37703611df..850e3cf577 100644 --- a/libos/src/meson.build +++ b/libos/src/meson.build @@ -19,6 +19,7 @@ libos_sources = files( 'fs/chroot/fs.c', 'fs/dev/attestation.c', 'fs/dev/fs.c', + 'fs/etc/fs.c', 'fs/eventfd/fs.c', 'fs/libos_dcache.c', 'fs/libos_fs.c', diff --git a/libos/src/sys/libos_clone.c b/libos/src/sys/libos_clone.c index ab27178cb1..cd41137e86 100644 --- a/libos/src/sys/libos_clone.c +++ b/libos/src/sys/libos_clone.c @@ -102,6 +102,7 @@ static BEGIN_MIGRATION_DEF(fork, struct libos_process* process_description, DEFINE_MIGRATE(brk, NULL, 0); DEFINE_MIGRATE(loaded_elf_objects, NULL, 0); DEFINE_MIGRATE(topo_info, NULL, 0); + DEFINE_MIGRATE(etc_info, NULL, 0); #ifdef DEBUG DEFINE_MIGRATE(gdb_map, NULL, 0); #endif diff --git a/pal/include/host/linux-common/etc_host_info.h b/pal/include/host/linux-common/etc_host_info.h new file mode 100644 index 0000000000..b2dead0829 --- /dev/null +++ b/pal/include/host/linux-common/etc_host_info.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-3.0-or-later */ +/* Copyright (C) 2022 Intel Corporation + * Mariusz Zaborski + */ + +#pragma once + +#include "pal.h" + +int parse_resolv_conf(struct pal_dns_host_conf* conf); diff --git a/pal/include/host/linux-common/etc_host_info_internal.h b/pal/include/host/linux-common/etc_host_info_internal.h new file mode 100644 index 0000000000..b6c3ec57af --- /dev/null +++ b/pal/include/host/linux-common/etc_host_info_internal.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: LGPL-3.0-or-later */ +/* Copyright (C) 2022 Intel Corporation + * Mariusz Zaborski + */ + +/* + * This file contains a list of external functions for which Gramine has unit tests. + */ + +#pragma once + +bool parse_ip_addr_ipv4(const char** pptr, uint32_t* out_addr); +bool parse_ip_addr_ipv6(const char** pptr, uint16_t out_addr[static 8]); diff --git a/pal/include/pal/pal.h b/pal/include/pal/pal.h index 435a2fdec3..e81ee7c656 100644 --- a/pal/include/pal/pal.h +++ b/pal/include/pal/pal.h @@ -31,6 +31,15 @@ typedef uint32_t PAL_IDX; /*!< an index */ /* maximum length of URIs */ #define URI_MAX 4096 +/* maximum length of hostname */ +#define PAL_HOSTNAME_MAX 255 + +/* DNS limits, used in resolv.conf emulation */ +#define PAL_MAX_NAMESPACES 3 +#define PAL_MAX_DN_SEARCH 6 + +#define MAX_IPV6_ADDR_LEN 40 + /* Common types used by host specific header. */ enum pal_socket_domain { PAL_DISCONNECT, @@ -91,6 +100,26 @@ enum { /********** PAL APIs **********/ +struct pal_dns_host_conf_addr { + bool is_ipv6; + union { + uint32_t ipv4; + uint16_t ipv6[8]; + }; +}; + +/* Used in resolv.conf emulation */ +struct pal_dns_host_conf { + struct pal_dns_host_conf_addr nsaddr_list[PAL_MAX_NAMESPACES]; + size_t nsaddr_list_count; + + char dn_search[PAL_MAX_DN_SEARCH][PAL_HOSTNAME_MAX]; + size_t dn_search_count; + + bool inet6; + bool rotate; +}; + /* Part of PAL state which is shared between all PALs and accessible (read-only) by the binary * started by PAL (usually our LibOS). */ struct pal_public_state { @@ -137,6 +166,9 @@ struct pal_public_state { struct pal_cpu_info cpu_info; struct pal_topo_info topo_info; /* received from untrusted host, but sanitized */ + + bool extra_runtime_domain_names_conf; + struct pal_dns_host_conf dns_host; }; /* We cannot mark this as returning a pointer to `const` object, because LibOS can diff --git a/pal/regression/ipv4_parser.c b/pal/regression/ipv4_parser.c new file mode 100644 index 0000000000..59abb39127 --- /dev/null +++ b/pal/regression/ipv4_parser.c @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: LGPL-3.0-or-later */ +/* Copyright (C) 2022 Intel Corporation + * Mariusz Zaborski + */ + +#include "api.h" +#include "etc_host_info_internal.h" +#include "pal_error.h" +#include "pal_regression.h" + +/* We define this to not link with many unneeded files, which are required by functions in + * etc_host_info.c which we don't use here. */ +void read_text_file_to_cstr(void); +void read_text_file_to_cstr(void) { + pal_printf("This function is a mock function and shouldn't be called\n"); + PalProcessExit(1); +} + +static int ipv4_valid(const char* buf, uint32_t reference_addr) { + uint32_t addr; + const char* ptr = buf; + + if (!parse_ip_addr_ipv4(&ptr, &addr)) { + pal_printf("Unable to parse \"%s\"\n", buf); + return -1; + } + + if (reference_addr != addr) { + pal_printf("Invalid result of parsing \"%s\" (expected: %.8x, got: %.8x)\n", buf, + reference_addr, addr); + return -1; + } + + return 0; +} + +static int ipv4_invalid(const char* buf) { + uint32_t addr; + const char* ptr = buf; + + if (parse_ip_addr_ipv4(&ptr, &addr)) { + pal_printf("We parsed \"%s\" successfully, but it's an invalid IPv4 address\n", buf); + return -1; + } + + return 0; +} + +int main(void) { + CHECK(ipv4_valid("255.255.255.255", 0xffffffff)); + CHECK(ipv4_valid("8.8.8.8", 0x08080808)); + CHECK(ipv4_valid("8.8.8.8 with suffix", 0x08080808)); + CHECK(ipv4_valid("0.0.0.0", 0x00000000)); + CHECK(ipv4_valid("8.8.10", 0x0808000a)); + CHECK(ipv4_valid("8.8.100", 0x08080064)); + CHECK(ipv4_valid("8.243", 0x080000f3)); + CHECK(ipv4_valid("8.193000", 0x0802f1e8)); + CHECK(ipv4_valid("7", 0x00000007)); + CHECK(ipv4_valid("999000123", 0x3b8b883b)); + + CHECK(ipv4_invalid("")); + CHECK(ipv4_invalid("255.255.255.930")); + CHECK(ipv4_invalid("255.255.300.255")); + CHECK(ipv4_invalid("255.400.255.255")); + CHECK(ipv4_invalid("400.255.255.255")); + CHECK(ipv4_invalid("0.255.255.1000000000")); + CHECK(ipv4_invalid("1000000000000000.255.255.0")); + CHECK(ipv4_invalid("8.8.8.8a")); + CHECK(ipv4_invalid("8.8.8.b8")); + CHECK(ipv4_invalid("8.8.8a.8")); + CHECK(ipv4_invalid("8.8.b8.8")); + CHECK(ipv4_invalid("8.8b.8.8")); + CHECK(ipv4_invalid("8.a8.8.8")); + CHECK(ipv4_invalid("8c.8.8.8")); + CHECK(ipv4_invalid("d8.8.8.8")); + CHECK(ipv4_invalid("8.8.8. 8")); + CHECK(ipv4_invalid("8.8.8.")); + CHECK(ipv4_invalid("8.8.")); + CHECK(ipv4_invalid("8.")); + CHECK(ipv4_invalid("8.8..8")); + CHECK(ipv4_invalid(".8.8.8.8")); + CHECK(ipv4_invalid(".8.8.8")); + CHECK(ipv4_invalid("8:8.8.8")); + CHECK(ipv4_invalid("8.8\r.8.8")); + CHECK(ipv4_invalid("8.8.8.\t8")); + CHECK(ipv4_invalid("8.8.+8.8")); + CHECK(ipv4_invalid("8.8.-8.8")); + CHECK(ipv4_invalid("8.b1.8.8")); + CHECK(ipv4_invalid("b1.8.8.8")); + CHECK(ipv4_invalid("8.8.8.018")); + CHECK(ipv4_invalid("8.0b1.8.8")); + CHECK(ipv4_invalid("0b1.8.8.8")); + + /* These addresses are valid ones, but (at least for now) we don't want to support other notions + * than decimal, because other notions are (probably) not used widely. + */ + CHECK(ipv4_invalid("8.8.0x8.8")); + CHECK(ipv4_invalid("8.8.8.017")); + CHECK(ipv4_invalid("0x8.8.8.8")); + + pal_printf("TEST OK\n"); +} diff --git a/pal/regression/ipv6_parser.c b/pal/regression/ipv6_parser.c new file mode 100644 index 0000000000..d1b5d77ad0 --- /dev/null +++ b/pal/regression/ipv6_parser.c @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: LGPL-3.0-or-later */ +/* Copyright (C) 2022 Intel Corporation + * Mariusz Zaborski + */ + +#include "api.h" +#include "etc_host_info_internal.h" +#include "pal_error.h" +#include "pal_regression.h" + +/* We define this to not link with many unneeded files, which are required by functions in + * etc_host_info.c which we don't use here. */ +void read_text_file_to_cstr(void); +void read_text_file_to_cstr(void) { + pal_printf("This function is a mock function and shouldn't be called\n"); + PalProcessExit(1); +} + +static int ipv6_valid(const char* buf, uint16_t reference_addr[static 8]) { + uint16_t addr[8]; + const char* ptr = buf; + + if (!parse_ip_addr_ipv6(&ptr, addr)) { + pal_printf("Unable to parse \"%s\"\n", buf); + return -1; + } + + if (memcmp(reference_addr, addr, sizeof(addr)) != 0) { + pal_printf( + "Invalid result of parsing \"%s\" " + "(expected: %.4x:%.4x:%.4x:%.4x:%.4x:%.4x:%.4x:%.4x, " + "got: %.4x:%.4x:%.4x:%.4x:%.4x:%.4x:%.4x:%.4x)\n", + buf, reference_addr[0], reference_addr[1], reference_addr[2], reference_addr[3], + reference_addr[4], reference_addr[5], reference_addr[6], reference_addr[7], + addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7]); + return -1; + } + + return 0; +} + +static int ipv6_invalid(const char* buf) { + uint16_t addr[8]; + const char* ptr = buf; + + if (parse_ip_addr_ipv6(&ptr, addr)) { + pal_printf("We parsed \"%s\" successfully, but it's an invalid IPv6 address\n", buf); + return -1; + } + + return 0; +} + +int main(void) { + struct { + const char* str; + uint16_t addr[8]; + } valid_test_cases[] = { + {"1::1", {1, 0, 0, 0, 0, 0, 0, 1}}, + {"1337:3333::2137:ffff", {0x1337, 0x3333, 0, 0, 0, 0, 0x2137, 0xFFFF}}, + {"1:2::1", {1, 2, 0, 0, 0, 0, 0, 1}}, + {"1:2:3::1", {1, 2, 3, 0, 0, 0, 0, 1}}, + {"1:2:3:4::1", {1, 2, 3, 4, 0, 0, 0, 1}}, + {"1:2:3:4:5::1", {1, 2, 3, 4, 5, 0, 0, 1}}, + {"1:2:3:4:5:6::1", {1, 2, 3, 4, 5, 6, 0, 1}}, + {"::1", {0, 0, 0, 0, 0, 0, 0, 1}}, + {"::", {0, 0, 0, 0, 0, 0, 0, 0}}, + {"1337:1:2:3:4:5:6:7", {0x1337, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}}, + {"1337:1:2:3:4:5:6:7 suffix", {0x1337, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}}, + {":: suffix", {0, 0, 0, 0, 0, 0, 0, 0}}, + {"::1 suffix", {0, 0, 0, 0, 0, 0, 0, 1}}, + {"1::", {1, 0, 0, 0, 0, 0, 0, 0}}, + }; + + for (size_t i = 0; i < ARRAY_SIZE(valid_test_cases); i++) { + CHECK(ipv6_valid(valid_test_cases[i].str, valid_test_cases[i].addr)); + } + + CHECK(ipv6_invalid("1337:3333:2137")); + CHECK(ipv6_invalid("1337:1:2:3:4:5:6:7:")); + CHECK(ipv6_invalid("1337:1:2:3:4:5:6:")); + CHECK(ipv6_invalid("1337:1:2:3:4:5:")); + CHECK(ipv6_invalid("1337:1:2:3:4:")); + CHECK(ipv6_invalid("1337:1:2:3:")); + CHECK(ipv6_invalid("1337:1:2:")); + CHECK(ipv6_invalid("1337:1:")); + CHECK(ipv6_invalid("1337:")); + CHECK(ipv6_invalid("255.255.255.255")); + CHECK(ipv6_invalid("1337:1:2:3:4:5:6:0x7")); + CHECK(ipv6_invalid("1337:1:2:3:4:5:6:-7")); + CHECK(ipv6_invalid("1337:1:2:3:4:5:6:+7")); + CHECK(ipv6_invalid("-1337:1:2:3:4:5:6:7")); + CHECK(ipv6_invalid("+1337:1:2:3:4:5:6:7")); + CHECK(ipv6_invalid("FFFFF:1:2:3:4:5:6:7")); + CHECK(ipv6_invalid("FFFF:FFFFF:2:3:4:5:6:7")); + CHECK(ipv6_invalid("FFFF:FFFF:FFFFF:3:4:5:6:7")); + CHECK(ipv6_invalid("FFFF:FFFF:FFFF:3:4:5:6:AAAAAA")); + CHECK(ipv6_invalid("1::\r\r1")); + CHECK(ipv6_invalid("1:\n:1")); + CHECK(ipv6_invalid("1::1\r\r:1")); + CHECK(ipv6_invalid("1::1::")); + CHECK(ipv6_invalid("::1::1")); + CHECK(ipv6_invalid("2::1::1")); + CHECK(ipv6_invalid("2::1::1::3")); + CHECK(ipv6_invalid("1:::")); + CHECK(ipv6_invalid("1::::")); + CHECK(ipv6_invalid("1::0x12")); + + pal_printf("TEST OK\n"); + + return 0; +} diff --git a/pal/regression/meson.build b/pal/regression/meson.build index 8f12b71a6f..2f57732db0 100644 --- a/pal/regression/meson.build +++ b/pal/regression/meson.build @@ -19,6 +19,26 @@ tests = { 'File2': {}, 'HelloWorld': {}, 'Hex': {}, + 'ipv4_parser': { + 'filenames': [ + 'ipv4_parser.c', + '../src/host/linux-common/etc_host_info.c', + ], + 'include_directories': include_directories( + # for `etc_host_info_internal.h` + join_paths('../include/host/linux-common'), + ), + }, + 'ipv6_parser': { + 'filenames': [ + 'ipv6_parser.c', + '../src/host/linux-common/etc_host_info.c', + ], + 'include_directories': include_directories( + # for `etc_host_info_internal.h` + join_paths('../include/host/linux-common'), + ), + }, 'Memory': {}, 'Misc': {}, 'Pie': { @@ -98,8 +118,15 @@ foreach name, params : tests # compiler builds it by default. See issue: https://github.com/mesonbuild/meson/issues/4651 pie = params.get('pie', false) + filenames = '' + if (params.has_key('filenames')) + filenames = params.get('filenames') + else + filenames = '@0@.c'.format(name) + endif + executable(name, - '@0@.c'.format(name), + filenames, user_start_src, include_directories: [ diff --git a/pal/regression/test_pal.py b/pal/regression/test_pal.py index 8d353cd209..ddabe6a577 100644 --- a/pal/regression/test_pal.py +++ b/pal/regression/test_pal.py @@ -473,6 +473,15 @@ def test_000_send_handle(self): self.assertIn('Parent: test OK', stderr) self.assertIn('Child: test OK', stderr) +class TC_30_IPParser(RegressionTestCase): + def test_000_ipv4(self): + _, stderr = self.run_binary(['ipv4_parser']) + self.assertIn('TEST OK', stderr) + + def test_010_ipv6(self): + _, stderr = self.run_binary(['ipv6_parser']) + self.assertIn('TEST OK', stderr) + @unittest.skipUnless(HAS_SGX, 'This test is only meaningful on SGX PAL') class TC_50_Attestation(RegressionTestCase): def test_000_attestation_report(self): diff --git a/pal/regression/tests.toml b/pal/regression/tests.toml index e68628e052..9f138d841a 100644 --- a/pal/regression/tests.toml +++ b/pal/regression/tests.toml @@ -13,6 +13,8 @@ manifests = [ "File2", "HelloWorld", "Hex", + "ipv4_parser", + "ipv6_parser", "Memory", "Misc", "Pie", diff --git a/pal/src/host/linux-common/etc_host_info.c b/pal/src/host/linux-common/etc_host_info.c new file mode 100644 index 0000000000..373a0a4909 --- /dev/null +++ b/pal/src/host/linux-common/etc_host_info.c @@ -0,0 +1,338 @@ +/* SPDX-License-Identifier: LGPL-3.0-or-later */ +/* Copyright (C) 2022 Intel Corporation + * Mariusz Zaborski + */ + +/* + * This file contains the APIs to retrieve information from the host: + * - parses host file `/etc/resolv.conf` into `struct pal_dns_host_conf` + */ + +#include "api.h" +#include "etc_host_info.h" +#include "etc_host_info_internal.h" +#include "linux_utils.h" + +static void jmp_to_end_of_line(const char** pptr) { + const char* ptr = *pptr; + + while (*ptr != 0x00 && *ptr != '\n') + ptr++; + + *pptr = ptr; +} + +static void skip_whitespaces(const char** pptr) { + const char* ptr = *pptr; + + while (*ptr == ' ' || *ptr == '\t') + ptr++; + + *pptr = ptr; +} + +static bool is_end_of_word(char ch) { + return ch == 0x00 || ch == '\n' || ch == ' ' || ch == '\t'; +} + +bool parse_ip_addr_ipv4(const char** pptr, uint32_t* out_addr) { + const char* ptr = *pptr; + char* next; + uint32_t addr[4]; + size_t i; + + for (i = 0; i < 4; i++) { + /* NOTE: Gramine strtoll/strtol skips white spaces that are before the number, and doesn't + * treat this as an error, this behavior is different from glibc. + */ + if (!isdigit(*ptr)) + return false; + long long octet = strtoll(ptr, &next, 10); + if (ptr == next) + return false; + if (octet < 0 || octet > UINT32_MAX) + return false; + /* strtoll skips a prefix with 0 */ + if (next - ptr > 1 && *ptr == '0') + return false; + + addr[i] = octet; + + if (is_end_of_word(*next)) + break; + if (*next != '.') + return false; + + ptr = next + 1; + } + if (!is_end_of_word(*next)) + return false; + assert(i < 4); + + uint32_t result = 0; + if (i == 0) { + /* Address A has to be converted to A[31:24].A[23:16].A[15:8].A[7:0] + * The value A is interpreted as a 32-bit. */ + result = addr[0]; + } else if (i == 1) { + /* Address A.B has to be converted to A.B[23:16].B[15:8].B[7:0] + * Part B is interpreted as a 24-bit. */ + if (addr[0] > 0xFF || addr[1] > 0xFFFFFF) { + return false; + } + result = addr[0] << 24 | addr[1]; + } else if (i == 2) { + /* Address A.B.C has to be converted to A.B.C[15:8].C[7:0] + * Part C is interpreted as a 16-bit value. */ + if (addr[0] > 0xFF || addr[1] > 0xFF || addr[2] > 0xFFFF) + return false; + result = addr[0] << 24 | addr[1] << 16 | addr[2]; + } else { + /* Address A.B.C.D */ + if (addr[0] > 0xFF || addr[1] > 0xFF || addr[2] > 0xFF || addr[3] > 0xFF) + return false; + result = addr[0] << 24 | addr[1] << 16 | addr[2] << 8 | addr[3]; + } + + *pptr = ptr; + *out_addr = result; + + return true; +} + +bool parse_ip_addr_ipv6(const char** pptr, uint16_t addr[static 8]) { + const char* ptr = *pptr; + ssize_t double_colon_pos = -1; + size_t parts_seen = 0; + + if (ptr[0] == ':' && ptr[1] == ':') { + double_colon_pos = 0; + ptr += 2; + } + + memset(addr, 0, sizeof(*addr) * 8); + for (size_t i = 0; i < 8; i++) { + if (is_end_of_word(ptr[0])) { + break; + } + + if (!isxdigit(ptr[0])) { + return false; + } + + char* next; + long val = strtol(ptr, &next, 16); + if (val < 0 || val > 0xFFFF) { + return false; + } + /* strtol skips 0x prefix, this prefix is invalid in IPv6 */ + if (next - ptr >= 2 && !isxdigit(ptr[1])) { + return false; + } + addr[parts_seen] = val; + parts_seen++; + ptr = next; + + if (ptr[0] == ':' && ptr[1] == ':') { + if (double_colon_pos != -1) { + return false; + } + + double_colon_pos = parts_seen; + ptr += 2; + } else if (ptr[0] == ':') { + ptr++; + } else { + break; + } + } + + if (!is_end_of_word(ptr[0])) { + return false; + } + if (parts_seen > 0 && !isxdigit(*(ptr - 1)) && (ssize_t)parts_seen != double_colon_pos) { + assert(ptr[-1] == ':'); + return false; + } + + if (double_colon_pos == -1) { + if (parts_seen != 8) + return false; + /* `addr` already correct. */ + } else { + if (parts_seen == 8) + return false; + if (parts_seen > 0) { + ssize_t i = parts_seen - 1; + for (ssize_t j = 7; i >= double_colon_pos; j--, i--) { + addr[j] = addr[i]; + addr[i] = 0; + } + } + } + + *pptr = ptr; + return true; +} + +static void resolv_nameserver(struct pal_dns_host_conf* conf, const char** pptr) { + const char* ptr = *pptr; + bool is_ipv6 = false; + + if (conf->nsaddr_list_count >= PAL_MAX_NAMESPACES) { + log_error("Host's /etc/resolv.conf contains more than %d nameservers, skipping", + PAL_MAX_NAMESPACES); + return; + } + + /* + * Check if nameserver is using IPv6 or IPv4. + * If address contains ':', it is a IPv6 address. + * If address contains '.', it is a IPv4 address. + * If we haven't found ':' nor '.' it means it is IPv4 address. + */ + while (!is_end_of_word(*ptr)) { + if (*ptr == ':') { + is_ipv6 = true; + break; + } else if (*ptr == '.') { + break; + } + ptr++; + } + + if (is_ipv6) { + if (!parse_ip_addr_ipv6(pptr, conf->nsaddr_list[conf->nsaddr_list_count].ipv6)) { + log_error("Host's /etc/resolv.conf has invalid or unsupported notation in nameserver " + "keyword"); + return; + } + } else { + if (!parse_ip_addr_ipv4(pptr, &conf->nsaddr_list[conf->nsaddr_list_count].ipv4)) { + log_error("Host's /etc/resolv.conf has invalid or unsupported notation in nameserver " + "keyword"); + return; + } + } + + conf->nsaddr_list[conf->nsaddr_list_count].is_ipv6 = is_ipv6; + conf->nsaddr_list_count++; +} + +static void parse_values_one_line(struct pal_dns_host_conf* conf, const char** pptr, + void (*setter)(struct pal_dns_host_conf*, const char*, size_t)) { + const char* ptr = *pptr; + const char* namestart = ptr; + + while (*ptr != 0x00 && *ptr != '\n' && *ptr != '#' && *ptr != ';') { + if (*ptr == ' ' || *ptr == '\t') { + setter(conf, namestart, ptr - namestart); + skip_whitespaces(&ptr); + namestart = ptr; + continue; + } + ptr++; + } + setter(conf, namestart, ptr - namestart); + + *pptr = ptr; +} + +static void resolv_search_setter(struct pal_dns_host_conf* conf, const char* ptr, size_t length) { + if (length >= PAL_HOSTNAME_MAX) { + log_error("One of the search domains in host's /etc/resolv.conf is too long " + "(larger than %d), skipping it", PAL_HOSTNAME_MAX); + return; + } + if (length == 0) { + return; + } + if (conf->dn_search_count >= PAL_MAX_DN_SEARCH) { + log_error("Host's /etc/resolv.conf contains too many search domains in single search " + "keyword"); + return; + } + + memcpy(conf->dn_search[conf->dn_search_count], ptr, length); + conf->dn_search[conf->dn_search_count][length] = 0x0; + conf->dn_search_count++; +} + +static void resolv_search(struct pal_dns_host_conf* conf, const char** pptr) { + /* Each search keyword overrides previous one. */ + conf->dn_search_count = 0; + parse_values_one_line(conf, pptr, resolv_search_setter); +} + +static void resolv_options_setter(struct pal_dns_host_conf* conf, const char* ptr, size_t length) { + char option[32]; + + if (length == 0) + return; + if (length >= sizeof(option)) + return; + memcpy(option, ptr, length); + option[length] = 0x00; + + if (strcmp(option, "inet6") == 0) { + conf->inet6 = true; + } else if (strcmp(option, "rotate") == 0) { + conf->rotate = true; + } +} + +static void resolv_options(struct pal_dns_host_conf* conf, const char** pptr) { + parse_values_one_line(conf, pptr, resolv_options_setter); +} + +static struct { + const char* keyword; + void (*set_value)(struct pal_dns_host_conf* conf, const char** pptr); +} resolv_keys[] = { + { "nameserver", resolv_nameserver }, + { "search", resolv_search }, + { "options", resolv_options }, +}; + +static void parse_resolv_buf_conf(struct pal_dns_host_conf* conf, const char* buf) { + const char* ptr = buf; + + /* + * From resolv.conf(5): + * The keyword and value must appear on a single line, and the keyword (e.g., nameserver) must + * start the line. The value follows the keyword, separated by white space. + */ + while (*ptr != 0x00) { + for (size_t i = 0; i < ARRAY_SIZE(resolv_keys); i++) { + if (strncmp(ptr, resolv_keys[i].keyword, strlen(resolv_keys[i].keyword)) == 0) { + ptr += strlen(resolv_keys[i].keyword); + /* Because the buffer in strncmp is not ended with 0x00, let's + * verify that this is end of word. */ + if (!is_end_of_word(*ptr)) + break; + skip_whitespaces(&ptr); + resolv_keys[i].set_value(conf, &ptr); + break; + } + } + /* Make sure we are at the end of line, even if parsing of this line failed */ + jmp_to_end_of_line(&ptr); + if (*ptr != 0x00) { + assert(*ptr == '\n'); + ptr++; + } + } +} + +int parse_resolv_conf(struct pal_dns_host_conf* conf) { + char* buf; + int ret = read_text_file_to_cstr("/etc/resolv.conf", &buf); + if (ret < 0) { + return ret; + } + + parse_resolv_buf_conf(conf, buf); + + free(buf); + return 0; +} diff --git a/pal/src/host/linux-common/meson.build b/pal/src/host/linux-common/meson.build index 182b72af06..8abfbae2d7 100644 --- a/pal/src/host/linux-common/meson.build +++ b/pal/src/host/linux-common/meson.build @@ -5,6 +5,7 @@ pal_linux_common_sources_enclave = files( ) pal_linux_common_sources_host = files( 'debug_map.c', + 'etc_host_info.c', 'file_utils.c', 'main_exec_path.c', 'proc_maps.c', diff --git a/pal/src/host/linux-sgx/enclave_ecalls.c b/pal/src/host/linux-sgx/enclave_ecalls.c index 0666d44871..1c7a754170 100644 --- a/pal/src/host/linux-sgx/enclave_ecalls.c +++ b/pal/src/host/linux-sgx/enclave_ecalls.c @@ -94,7 +94,8 @@ void handle_ecall(long ecall_index, void* ecall_args, void* exit_target, void* e COPY_UNTRUSTED_VALUE(&ms->ms_parent_stream_fd), COPY_UNTRUSTED_VALUE(&ms->ms_qe_targetinfo), COPY_UNTRUSTED_VALUE(&ms->ms_topo_info), - COPY_UNTRUSTED_VALUE(&ms->rpc_queue)); + COPY_UNTRUSTED_VALUE(&ms->rpc_queue), + COPY_UNTRUSTED_VALUE(&ms->ms_dns_host_conf)); } else { // ENCLAVE_START already called (maybe successfully, maybe not), so // only valid ecall is THREAD_START. diff --git a/pal/src/host/linux-sgx/host_ecalls.c b/pal/src/host/linux-sgx/host_ecalls.c index 11e28ae2c7..9387266bd9 100644 --- a/pal/src/host/linux-sgx/host_ecalls.c +++ b/pal/src/host/linux-sgx/host_ecalls.c @@ -8,7 +8,7 @@ int ecall_enclave_start(char* libpal_uri, char* args, size_t args_size, char* env, size_t env_size, int parent_stream_fd, sgx_target_info_t* qe_targetinfo, - struct pal_topo_info* topo_info) { + struct pal_topo_info* topo_info, struct pal_dns_host_conf* dns_conf) { g_rpc_queue = NULL; if (g_pal_enclave.rpc_thread_num > 0) { @@ -30,6 +30,7 @@ int ecall_enclave_start(char* libpal_uri, char* args, size_t args_size, char* en ms.ms_parent_stream_fd = parent_stream_fd; ms.ms_qe_targetinfo = qe_targetinfo; ms.ms_topo_info = topo_info; + ms.ms_dns_host_conf = dns_conf; ms.rpc_queue = g_rpc_queue; return sgx_ecall(ECALL_ENCLAVE_START, &ms); } diff --git a/pal/src/host/linux-sgx/host_ecalls.h b/pal/src/host/linux-sgx/host_ecalls.h index 149daace0c..efb5a5c6b6 100644 --- a/pal/src/host/linux-sgx/host_ecalls.h +++ b/pal/src/host/linux-sgx/host_ecalls.h @@ -3,11 +3,12 @@ #include #include "sgx_arch.h" +#include "pal.h" #include "pal_topology.h" int ecall_enclave_start(char* libpal_uri, char* args, size_t args_size, char* env, size_t env_size, int parent_stream_fd, sgx_target_info_t* qe_targetinfo, - struct pal_topo_info* topo_info); + struct pal_topo_info* topo_info, struct pal_dns_host_conf* host_conf); int ecall_thread_start(void); diff --git a/pal/src/host/linux-sgx/host_main.c b/pal/src/host/linux-sgx/host_main.c index 025d456522..a9a656f985 100644 --- a/pal/src/host/linux-sgx/host_main.c +++ b/pal/src/host/linux-sgx/host_main.c @@ -12,6 +12,7 @@ #include "asan.h" #include "debug_map.h" +#include "etc_host_info.h" #include "gdb_integration/sgx_gdb.h" #include "host_ecalls.h" #include "host_internal.h" @@ -627,7 +628,8 @@ static int initialize_enclave(struct pal_enclave* enclave, const char* manifest_ } /* Parses only the information needed by the untrusted PAL to correctly initialize the enclave. */ -static int parse_loader_config(char* manifest, struct pal_enclave* enclave_info) { +static int parse_loader_config(char* manifest, struct pal_enclave* enclave_info, + bool* extra_runtime_domain_names_conf) { int ret = 0; toml_table_t* manifest_root = NULL; char* dummy_sigfile_str = NULL; @@ -890,6 +892,13 @@ static int parse_loader_config(char* manifest, struct pal_enclave* enclave_info) } g_host_log_level = log_level; + ret = toml_bool_in(manifest_root, "sys.enable_extra_runtime_domain_names_conf", + /*defaultval=*/false, extra_runtime_domain_names_conf); + if (ret < 0) { + log_error("Cannot parse 'sys.enable_extra_runtime_domain_names_conf'"); + goto out; + } + ret = 0; out: @@ -911,7 +920,8 @@ static int load_enclave(struct pal_enclave* enclave, char* args, size_t args_siz int ret; struct timeval tv; struct pal_topo_info topo_info = {0}; - + struct pal_dns_host_conf dns_conf = {0}; + bool extra_runtime_domain_names_conf; uint64_t start_time; DO_SYSCALL(gettimeofday, &tv, NULL); start_time = tv.tv_sec * 1000000UL + tv.tv_usec; @@ -920,7 +930,7 @@ static int load_enclave(struct pal_enclave* enclave, char* args, size_t args_siz /* only print during main process's startup (note that this message is always printed) */ log_always("Gramine is starting. Parsing TOML manifest file, this may take some time..."); } - ret = parse_loader_config(enclave->raw_manifest_data, enclave); + ret = parse_loader_config(enclave->raw_manifest_data, enclave, &extra_runtime_domain_names_conf); if (ret < 0) { log_error("Parsing manifest failed"); return -EINVAL; @@ -934,12 +944,20 @@ static int load_enclave(struct pal_enclave* enclave, char* args, size_t args_siz if (!is_wrfsbase_supported()) return -EPERM; - /* Get host topology information only for the first process. This information will be + /* Get host information and topology only for the first process. This information will be * checkpointed and restored during forking of the child process(es). */ if (parent_stream_fd < 0) { ret = get_topology_info(&topo_info); if (ret < 0) return ret; + + if (extra_runtime_domain_names_conf) { + ret = parse_resolv_conf(&dns_conf); + if (ret < 0) { + log_error("Unable to parse host's /etc/resolv.conf"); + return ret; + } + } } enclave->libpal_uri = alloc_concat(URI_PREFIX_FILE, URI_PREFIX_FILE_LEN, g_libpal_path, -1); @@ -999,7 +1017,7 @@ static int load_enclave(struct pal_enclave* enclave, char* args, size_t args_siz /* start running trusted PAL */ ecall_enclave_start(enclave->libpal_uri, args, args_size, env, env_size, parent_stream_fd, - &qe_targetinfo, &topo_info); + &qe_targetinfo, &topo_info, &dns_conf); unmap_tcs(); DO_SYSCALL(munmap, alt_stack, ALT_STACK_SIZE); diff --git a/pal/src/host/linux-sgx/pal_ecall_types.h b/pal/src/host/linux-sgx/pal_ecall_types.h index b215f8a8c1..86859aca5c 100644 --- a/pal/src/host/linux-sgx/pal_ecall_types.h +++ b/pal/src/host/linux-sgx/pal_ecall_types.h @@ -18,15 +18,16 @@ enum { struct rpc_queue; typedef struct { - char* ms_libpal_uri; - size_t ms_libpal_uri_len; - char* ms_args; - size_t ms_args_size; - char* ms_env; - size_t ms_env_size; - int ms_parent_stream_fd; - sgx_target_info_t* ms_qe_targetinfo; - struct pal_topo_info* ms_topo_info; + char* ms_libpal_uri; + size_t ms_libpal_uri_len; + char* ms_args; + size_t ms_args_size; + char* ms_env; + size_t ms_env_size; + int ms_parent_stream_fd; + sgx_target_info_t* ms_qe_targetinfo; + struct pal_topo_info* ms_topo_info; + struct pal_dns_host_conf* ms_dns_host_conf; struct rpc_queue* rpc_queue; /* pointer to RPC queue in untrusted mem */ } ms_ecall_enclave_start_t; diff --git a/pal/src/host/linux-sgx/pal_linux.h b/pal/src/host/linux-sgx/pal_linux.h index 894e76bd74..7ee4aafe5f 100644 --- a/pal/src/host/linux-sgx/pal_linux.h +++ b/pal/src/host/linux-sgx/pal_linux.h @@ -71,7 +71,7 @@ extern size_t g_pal_internal_mem_size; noreturn void pal_linux_main(void* uptr_libpal_uri, size_t libpal_uri_len, void* uptr_args, size_t args_size, void* uptr_env, size_t env_size, int parent_stream_fd, void* uptr_qe_targetinfo, void* uptr_topo_info, - void* uptr_rpc_queue); + void* uptr_rpc_queue, void* uptr_dns_conf); void pal_start_thread(void); extern char __text_start, __text_end, __data_start, __data_end; diff --git a/pal/src/host/linux-sgx/pal_main.c b/pal/src/host/linux-sgx/pal_main.c index 50105ce26d..caf46e38fa 100644 --- a/pal/src/host/linux-sgx/pal_main.c +++ b/pal/src/host/linux-sgx/pal_main.c @@ -277,8 +277,116 @@ static int import_and_sanitize_topo_info(void* uptr_topo_info) { return sanitize_topo_info(topo_info); } -extern uintptr_t g_enclave_base; -extern uintptr_t g_enclave_top; +/* + * Gramine assumes that the hostname is valid when: + * - the length of the hostname is below or equal to 255 characters (including '\0'), + * - the length of a single label is between 1 and 63, + * - every label is separated with '.', + * - the hostname doesn't start or end with '.' and '-', + * - the hostname contains only alphanumeric characters, '-', and '.', + * These rules were taken from: + * - RFC1123, + * - RFC0952, + * - RFC2181. + */ +static bool is_hostname_valid(const char* hostname) { + const char* ptr = hostname; + size_t label_len = 0; + + if (*ptr == '-') + return false; + + while (*ptr != 0x00) { + if (('a' <= *ptr && *ptr <= 'z') + || ('A' <= *ptr && *ptr <= 'Z') + || ('0' <= *ptr && *ptr <= '9') + || *ptr == '-') { + label_len++; + } else if (*ptr == '.') { + if (label_len == 0 || label_len > 63) { + return false; + } + label_len = 0; + } else { + return false; + } + + ptr++; + } + + if (ptr - hostname >= PAL_HOSTNAME_MAX) + return false; + if (label_len == 0 || label_len > 63) + return false; + /* rewind to last character */ + ptr--; + if (*ptr == '-') + return false; + + return true; +} + +static int import_and_init_extra_runtime_domain_names(struct pal_dns_host_conf* uptr_dns_conf) { + struct pal_dns_host_conf* pub_dns = &g_pal_public_state.dns_host; + + if (!g_pal_public_state.extra_runtime_domain_names_conf) + return 0; + + struct pal_dns_host_conf untrusted_dns; + if (!sgx_copy_to_enclave(&untrusted_dns, sizeof(untrusted_dns), uptr_dns_conf, + sizeof(*uptr_dns_conf))) { + log_error("Unable to read host dns info"); + return -EINVAL; + } + + if (untrusted_dns.nsaddr_list_count > PAL_MAX_NAMESPACES) { + log_error("Too many nameservers provided"); + return -EINVAL; + } + + pub_dns->nsaddr_list_count = untrusted_dns.nsaddr_list_count; + for (size_t i = 0; i < pub_dns->nsaddr_list_count; i++) { + coerce_untrusted_bool(&untrusted_dns.nsaddr_list[i].is_ipv6); + /* All binary IP addresses are valid. */ + if (!untrusted_dns.nsaddr_list[i].is_ipv6) { + pub_dns->nsaddr_list[i].ipv4 = untrusted_dns.nsaddr_list[i].ipv4; + pub_dns->nsaddr_list[i].is_ipv6 = false; + } else { + memcpy(&pub_dns->nsaddr_list[i].ipv6, &untrusted_dns.nsaddr_list[i].ipv6, + sizeof(pub_dns->nsaddr_list[i].ipv6)); + pub_dns->nsaddr_list[i].is_ipv6 = true; + } + } + + if (untrusted_dns.dn_search_count > PAL_MAX_DN_SEARCH) { + log_error("Too many search entries provided"); + return -EINVAL; + } + + size_t j = 0; + for (size_t i = 0; i < untrusted_dns.dn_search_count; i++) { + untrusted_dns.dn_search[i][PAL_HOSTNAME_MAX - 1] = 0x00; + if (!is_hostname_valid(untrusted_dns.dn_search[i])) { + log_warning("The search domain name %s is invalid, skipping it", untrusted_dns.dn_search[i]); + continue; + } + + memcpy(pub_dns->dn_search[j], untrusted_dns.dn_search[i], sizeof(pub_dns->dn_search[j])); + j++; + } + pub_dns->dn_search_count = j; + + coerce_untrusted_bool(&untrusted_dns.inet6); + coerce_untrusted_bool(&untrusted_dns.rotate); + + pub_dns->inet6 = untrusted_dns.inet6; + pub_dns->rotate = untrusted_dns.rotate; + + return 0; +} + +extern void* g_enclave_base; +extern void* g_enclave_top; extern bool g_allowed_files_warn; static int print_warnings_on_insecure_configs(PAL_HANDLE parent_process) { @@ -438,7 +546,7 @@ __attribute_no_stack_protector noreturn void pal_linux_main(void* uptr_libpal_uri, size_t libpal_uri_len, void* uptr_args, size_t args_size, void* uptr_env, size_t env_size, int parent_stream_fd, void* uptr_qe_targetinfo, void* uptr_topo_info, - void* uptr_rpc_queue) { + void* uptr_rpc_queue, void* uptr_dns_conf) { /* All our arguments are coming directly from the host. We are responsible to check them. */ int ret; @@ -605,7 +713,7 @@ noreturn void pal_linux_main(void* uptr_libpal_uri, size_t libpal_uri_len, void* /* Parse, sanitize and store host topology info into g_pal_public_state struct */ ret = import_and_sanitize_topo_info(uptr_topo_info); if (ret < 0) { - log_error("Failed to copy and sanitize topology information"); + log_error("Failed to copy and sanitize topology information: %d", ret); ocall_exit(1, /*is_exitgroup=*/true); } } @@ -658,6 +766,25 @@ noreturn void pal_linux_main(void* uptr_libpal_uri, size_t libpal_uri_len, void* ocall_exit(1, /*is_exitgroup=*/true); } + ret = toml_bool_in(g_pal_public_state.manifest_root, + "sys.enable_extra_runtime_domain_names_conf", /*defaultval*/false, + &g_pal_public_state.extra_runtime_domain_names_conf); + if (ret < 0) { + log_error("Cannot parse 'sys.enable_extra_runtime_domain_names_conf'"); + ocall_exit(1, /*is_exitgroup=*/true); + } + + /* Get host information about domain name configuration only for the first process. + * This information will be checkpointed and restored during forking of the child + * process(es). */ + if (parent_stream_fd < 0) { + ret = import_and_init_extra_runtime_domain_names(uptr_dns_conf); + if (ret < 0) { + log_error("Failed to initialize host info: %d", ret); + ocall_exit(1, /*is_exitgroup=*/true); + } + } + enum sgx_attestation_type attestation_type; ret = parse_attestation_type(g_pal_public_state.manifest_root, &attestation_type); if (ret < 0) { diff --git a/pal/src/host/linux/pal_main.c b/pal/src/host/linux/pal_main.c index a7d8768b67..fd370da4f5 100644 --- a/pal/src/host/linux/pal_main.c +++ b/pal/src/host/linux/pal_main.c @@ -14,6 +14,7 @@ #include "cpu.h" #include "debug_map.h" #include "elf/elf.h" +#include "etc_host_info.h" #include "init.h" #include "linux_utils.h" #include "pal.h" @@ -121,6 +122,15 @@ noreturn static void print_usage_and_exit(const char* argv_0) { _PalProcessExit(1); } +static void get_host_etc_configs(void) { + if (!g_pal_public_state.extra_runtime_domain_names_conf) + return; + + if (parse_resolv_conf(&g_pal_public_state.dns_host) < 0) { + INIT_FAIL("Unable to parse /etc/resolv.conf"); + } +} + #ifdef ASAN __attribute_no_stack_protector __attribute_no_sanitize_address @@ -409,6 +419,19 @@ noreturn void pal_linux_main(void* initial_rsp, void* fini_callback) { INIT_FAIL("Cannot parse 'loader.pal_internal_mem_size'"); } + ret = toml_bool_in(g_pal_public_state.manifest_root, + "sys.enable_extra_runtime_domain_names_conf", /*defaultval=*/false, + &g_pal_public_state.extra_runtime_domain_names_conf); + if (ret < 0) { + INIT_FAIL("Cannot parse 'sys.enable_extra_runtime_domain_names_conf'"); + } + + /* Get host /etc information only for the first process. This information will be + * checkpointed and restored during forking of the child process(es). */ + if (first_process) { + get_host_etc_configs(); + } + void* internal_mem_addr = (void*)DO_SYSCALL(mmap, NULL, g_pal_internal_mem_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);