Skip to content

Commit

Permalink
[LibOS,PAL,Docs] Introduce etc passthrough
Browse files Browse the repository at this point in the history
The first `etc` passthrough file is the `/etc/hostname`. Gramine reads
the option to global PAL state, and LibOS uses it to create a pseudo file
with its content. Gramine sanitizes hostname. It requires that it is a valid
domain. This is a difference from Linux, as Linux accepts any hostname value.
That said, Linux doesn't assume that the root user tries to exploit it through
hostname, and Gramine should.

Gramine uses pseudofs for `etc` passthrough.

Signed-off-by: Mariusz Zaborski <[email protected]>
  • Loading branch information
oshogbo committed Aug 17, 2022
1 parent 90f8b9d commit da6e879
Show file tree
Hide file tree
Showing 30 changed files with 491 additions and 54 deletions.
14 changes: 14 additions & 0 deletions Documentation/manifest-syntax.rst
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,20 @@ arguments to be provided at runtime from an external (trusted) source.
If none of the above arguments-handling manifest options is specified in the
manifest, the application will get ``argv = [ <libos.entrypoint value> ]``.

etc passthrough
^^^^^^^^^^^^^^^

::

libos.passthrough_etc_files = [true|false]
(Default: false)

This specifies whether to passthrough extra runtime files from ``/etc``.
Before the files are available to the application, they will be sanitized.
The set of extra runtime files is limited to:

- ``/etc/hostname``

Environment variables
^^^^^^^^^^^^^^^^^^^^^

Expand Down
1 change: 1 addition & 0 deletions libos/include/libos_fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 init_mount_etc(void);

/* file system operations */

Expand Down
3 changes: 3 additions & 0 deletions libos/include/libos_fs_pseudo.h
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,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_etc(void);
2 changes: 2 additions & 0 deletions libos/include/libos_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ bool handle_signal(PAL_CONTEXT* context);
*/
long pal_to_unix_errno(long err);

long libos_set_hostname(char* name, size_t len);

void warn_unsupported_syscall(unsigned long sysno);
void debug_print_syscall_before(unsigned long sysno, ...);
void debug_print_syscall_after(unsigned long sysno, ...);
Expand Down
78 changes: 78 additions & 0 deletions libos/src/fs/etc/fs.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* Copyright (C) 2022 Intel Corporation
* Mariusz Zaborski <[email protected]>
*/

/*
* This file contains the implementation of `etc` passthrough.
*/

#include "libos_checkpoint.h"
#include "libos_fs.h"
#include "libos_fs_pseudo.h"

static int provide_etc_hostname(struct libos_dentry* dent, char** out_data, size_t* out_size) {
__UNUSED(dent);
/* Use the string (without null terminator) as file data */
size_t size = strlen(g_pal_public_state->hostname);
char* data = malloc(size);
if (!data)
return -ENOMEM;
memcpy(data, g_pal_public_state->hostname, size);
*out_data = data;
*out_size = size;
return 0;
}

int init_etc(void) {
pseudo_add_str(NULL, "hostname", &provide_etc_hostname);
return 0;
}

int init_mount_etc(void) {
int ret;

if (!g_pal_public_state->passthrough_etc_files)
return 0;

ret = mount_fs(&(struct libos_mount_params){
.type = "pseudo",
.path = "/etc/hostname",
.uri = "hostname",
});
if (ret < 0)
return ret;

return 0;
}

BEGIN_CP_FUNC(etc_info) {
__UNUSED(size);
__UNUSED(obj);
__UNUSED(objp);

/* Propagate hostname */
size_t off = ADD_CP_OFFSET(sizeof(g_pal_public_state->hostname) +
sizeof(g_pal_public_state->passthrough_etc_files));
char* new_hostname = (char*)(base + off);
memcpy(new_hostname, g_pal_public_state->hostname, sizeof(g_pal_public_state->hostname));

/* Propagate passthrough_etc_files */
bool* new_passthrough_etc_files = (bool*)(new_hostname +
sizeof(g_pal_public_state->hostname));
*new_passthrough_etc_files = g_pal_public_state->passthrough_etc_files;
ADD_CP_FUNC_ENTRY(off);
}
END_CP_FUNC(etc_info)

BEGIN_RS_FUNC(etc_info) {
__UNUSED(offset);
__UNUSED(rebase);

const char* hostname = (const char*)(base + GET_CP_FUNC_ENTRY());
memcpy(&g_pal_public_state->hostname, hostname, sizeof(g_pal_public_state->hostname));

g_pal_public_state->passthrough_etc_files = *(bool*)(hostname +
sizeof(g_pal_public_state->hostname));
}
END_RS_FUNC(etc_info)
3 changes: 3 additions & 0 deletions libos/src/fs/libos_fs.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ int init_fs(void) {
if ((ret = init_sysfs()) < 0)
goto err;

if ((ret = init_etc()) < 0)
goto err;

return 0;

err:
Expand Down
4 changes: 4 additions & 0 deletions libos/src/libos_init.c
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,7 @@ noreturn void libos_init(const char* const* argv, const char* const* envp) {
RUN_INIT(init_threading);
RUN_INIT(init_mount);
RUN_INIT(init_std_handles);
RUN_INIT(init_mount_etc);

char** expanded_argv = NULL;
RUN_INIT(init_exec_handle, argv, &expanded_argv);
Expand Down Expand Up @@ -487,6 +488,9 @@ noreturn void libos_init(const char* const* argv, const char* const* envp) {
* communicates with server over a "loopback" IPC connection. */
RUN_INIT(init_sync_client);

RUN_INIT(libos_set_hostname, g_pal_public_state->hostname,
strlen(g_pal_public_state->hostname) + 1);

log_debug("LibOS initialized");

libos_tcb_t* cur_tcb = libos_get_tcb();
Expand Down
1 change: 1 addition & 0 deletions libos/src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
1 change: 1 addition & 0 deletions libos/src/sys/libos_clone.c
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,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
Expand Down
18 changes: 13 additions & 5 deletions libos/src/sys/libos_uname.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,26 @@ long libos_syscall_uname(struct new_utsname* buf) {
return 0;
}

long libos_syscall_sethostname(char* name, int len) {
if (len < 0 || (size_t)len >= sizeof(g_current_uname.nodename))
long libos_set_hostname(char* name, size_t len) {
if (len >= sizeof(g_current_uname.nodename))
return -EINVAL;

if (!is_user_memory_readable(name, len))
return -EFAULT;

memcpy(&g_current_uname.nodename, name, len);
memset(&g_current_uname.nodename[len], 0, sizeof(g_current_uname.nodename) - len);

return 0;
}

long libos_syscall_sethostname(char* name, int len) {
if (!is_user_memory_readable(name, len))
return -EFAULT;

if (len < 0)
return -EINVAL;

return libos_set_hostname(name, len);
}

long libos_syscall_setdomainname(char* name, int len) {
if (len < 0 || (size_t)len >= sizeof(g_current_uname.domainname))
return -EINVAL;
Expand Down
108 changes: 108 additions & 0 deletions libos/test/regression/hostname.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#define _DEFAULT_SOURCE BSD /* This is required for gethostname */

#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static void test_fork(const char* tag, const char* name, void (*f)(const char*, const char*)) {
int status;

pid_t pid = fork();
if (pid == -1) {
printf("Unable to fork %s\n", tag);
exit(1);
}

if (pid == 0) {
f(tag, name);
exit(0);
}

if (wait(&status) == -1) {
printf("Wait failed %s\n", tag);
exit(1);
}

if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
printf("Test failed %s\n", tag);
exit(1);
}
}

static void test_gethostname(const char* tag, const char* name) {
char buf[512] = {0};

if (gethostname(buf, sizeof(buf)) != 0) {
printf("%sgethostname: failed %d\n", tag, errno);
exit(1);
}

if (strcmp(buf, name) != 0) {
printf("%sgethostname dosen't match hostname (expected: %s, got: %s)\n",
tag, name, buf);
exit(1);
}
}

static void test_etc_hostname(const char* tag, const char* name) {
char buf[512] = {0};
int fd;

fd = open("/etc/hostname", O_RDONLY);

/*
* If the etc hostname was not provided, assume that etc shouldn't exists.
*/
if (strcmp(name, "") == 0) {
if (fd != -1 || errno != ENOENT) {
printf("The etc file shouldn't exists, but exists\n");
exit(1);
}
return;
}

if (fd == -1) {
printf("Unable to open /etc/hostname in %s\n", tag);
exit(1);
}

int ret = read(fd, buf, sizeof(buf));
if (ret <= 0) {
printf("Unable to read /etc/hostname in %s\n", tag);
exit(1);
}

/*
* Sometimes etc hostname might have a trailing '\n', gramine is romving it,
* do the same in the test.
*/
size_t len = strlen(buf);
if (len > 0 && buf[len - 1] == '\n') {
buf[len - 1] = '\0';
}

if (strcmp(buf, name) != 0) {
printf("%s etc don't have a expected value (expected: %s, got: %s)\n",
tag, name, buf);
exit(1);
}
}

int main(int argc, char** argv) {
if (argc != 3) {
printf("Usage: %s [hostname] [etc_hostname]\n", argv[0]);
return 1;
}

test_gethostname("", argv[1]);
test_etc_hostname("", argv[2]);
test_fork("fork gethostname", argv[1], test_gethostname);
test_fork("fork etc gethostname", argv[2], test_etc_hostname);

printf("hostname test passed\n");
return 0;
}
22 changes: 22 additions & 0 deletions libos/test/regression/hostname_pass_etc.manifest.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
loader.entrypoint = "file:{{ gramine.libos }}"
libos.entrypoint = "hostname"

loader.env.LD_LIBRARY_PATH = "/lib"
loader.insecure__use_cmdline_argv = true

fs.mounts = [
{ path = "/lib", uri = "file:{{ gramine.runtimedir(libc) }}" },
{ path = "/hostname", uri = "file:{{ binary_dir }}/hostname" },
]

sgx.debug = true
sgx.nonpie_binary = true
sgx.thread_num = 16

sgx.trusted_files = [
"file:{{ gramine.libos }}",
"file:{{ gramine.runtimedir(libc) }}/",
"file:{{ binary_dir }}/hostname",
]

libos.passthrough_etc_files = true
1 change: 1 addition & 0 deletions libos/test/regression/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ tests = {
'groups': {},
'helloworld': {},
'host_root_fs': {},
'hostname': {},
'init_fail': {},
'keys': {},
'kill_all': {},
Expand Down
10 changes: 10 additions & 0 deletions libos/test/regression/test_libos.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import subprocess
import unittest

from socket import gethostname

from graminelibos.regression import (
HAS_SGX,
ON_X86,
Expand Down Expand Up @@ -1119,6 +1121,14 @@ def test_060_synthetic(self):
stdout, _ = self.run_binary(['synthetic'])
self.assertIn("TEST OK", stdout)

def test_070_hostname_localhost(self):
stdout, _ = self.run_binary(['hostname', 'localhost', ''])
self.assertIn("hostname test passed", stdout)

def test_071_hostname_pass_etc(self):
stdout, _ = self.run_binary(['hostname_pass_etc', gethostname(), gethostname()])
self.assertIn("hostname test passed", stdout)


class TC_50_GDB(RegressionTestCase):
def setUp(self):
Expand Down
2 changes: 2 additions & 0 deletions libos/test/regression/tests.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ manifests = [
"gettimeofday",
"groups",
"helloworld",
"hostname",
"hostname_pass_etc",
"host_root_fs",
"init_fail",
"keys",
Expand Down
2 changes: 2 additions & 0 deletions libos/test/regression/tests_musl.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ manifests = [
"gettimeofday",
"groups",
"helloworld",
"hostname",
"hostname_pass_etc",
"host_root_fs",
"init_fail",
"keys",
Expand Down
11 changes: 11 additions & 0 deletions pal/include/host/linux-common/host_info.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later */
/* Copyright (C) 2022 Intel Corporation
* Mariusz Zaborski <[email protected]>
*/

#pragma once

#include "pal.h"

/* Function to fetch the hostname */
int get_hostname(char* hostname, size_t size);
Loading

0 comments on commit da6e879

Please sign in to comment.