Skip to content

Commit

Permalink
[LibOS] Add sys.disallowed_syscalls = [ ... ] manifest option
Browse files Browse the repository at this point in the history
This commit adds the manifest syntax `sys.disallowed_syscalls = [ ... ]`
to specify system calls that will be disallowed to be executed in
Gramine (i.e. a denylist). This resembles, though significantly less
flexible, seccomp profiles.

Signed-off-by: Dmitrii Kuvaiskii <dmitrii.kuvaiskii@intel.com>
  • Loading branch information
dimakuv committed Apr 24, 2024
1 parent f1258cc commit 0504ab3
Show file tree
Hide file tree
Showing 12 changed files with 175 additions and 0 deletions.
23 changes: 23 additions & 0 deletions Documentation/manifest-syntax.rst
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,29 @@ Python). Could be useful in SGX environments: child processes consume
to achieve this, you need to run the whole Gramine inside a proper security
sandbox.
Disallowing syscalls
^^^^^^^^^^^^^^^^^^^^

::

sgx.disallowed_syscalls = [
"syscall_name",
"syscall_name",
]

This syntax specifies the system calls that will be disallowed to be executed in
Gramine (i.e. a denylist). For example, to disallow eventfd completely, specify
two syscall variants: ``sgx.disallowed_syscalls = [ "eventfd", "eventfd2" ]``.
This is similar, though significantly less flexible, to seccomp profiles.

.. note ::
This option is *not* a replacement for ``sys.disallow_subprocesses`` (see
above). This is because the ``clone()`` syscall has two usages: (1) it is
used to spawn subprocesses by Glibc and many other libraries and runtimes and
(2) it is also used to create threads in the same process. The
``sys.disallow_subprocesses`` manifest option disables only the first usage,
whereas ``sys.disallowed_syscalls = ["clone"]`` disables both usages.
Root FS mount point
^^^^^^^^^^^^^^^^^^^

Expand Down
2 changes: 2 additions & 0 deletions libos/include/libos_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ int init_eventfd_mode(void);
void warn_unsupported_syscall(unsigned long sysno);
void debug_print_syscall_before(unsigned long sysno, ...);
void debug_print_syscall_after(unsigned long sysno, ...);
unsigned long get_syscall_number(const char* name);
int init_syscalls(void);

#ifndef __alloca
#define __alloca __builtin_alloca
Expand Down
1 change: 1 addition & 0 deletions libos/src/libos_init.c
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,7 @@ noreturn void libos_init(const char* const* argv, const char* const* envp) {
strlen(g_pal_public_state->dns_host.hostname));

RUN_INIT(init_eventfd_mode);
RUN_INIT(init_syscalls);

log_debug("LibOS initialized");

Expand Down
14 changes: 14 additions & 0 deletions libos/src/libos_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -1649,6 +1649,20 @@ void warn_unsupported_syscall(unsigned long sysno) {
log_warning("Unsupported system call %lu", sysno);
}

unsigned long get_syscall_number(const char* name) {
assert(LIBOS_SYSCALL_BOUND == ARRAY_SIZE(syscall_parser_table));
unsigned long sysno = LIBOS_SYSCALL_BOUND;
for (size_t i = 0; i < LIBOS_SYSCALL_BOUND; i++) {
if (!syscall_parser_table[i].name)
continue;
if (strcmp(name, syscall_parser_table[i].name) == 0) {
sysno = i;
break;
}
}
return sysno;
}

static int buf_write_all(const char* str, size_t size, void* arg) {
__UNUSED(arg);

Expand Down
58 changes: 58 additions & 0 deletions libos/src/libos_syscalls.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
#include "libos_table.h"
#include "libos_tcb.h"
#include "libos_thread.h"
#include "libos_utils.h"
#include "linux_abi/errors.h"
#include "toml_utils.h"

typedef arch_syscall_arg_t (*six_args_syscall_t)(arch_syscall_arg_t, arch_syscall_arg_t,
arch_syscall_arg_t, arch_syscall_arg_t,
Expand Down Expand Up @@ -84,3 +86,59 @@ noreturn void return_from_syscall(PAL_CONTEXT* context) {
#endif
_return_from_syscall(context);
}

int init_syscalls(void) {
assert(g_manifest_root);
int ret;

toml_table_t* manifest_sys = toml_table_in(g_manifest_root, "sys");
if (!manifest_sys)
return 0;

toml_array_t* toml_disallowed_syscalls = toml_array_in(manifest_sys, "disallowed_syscalls");
if (!toml_disallowed_syscalls)
return 0;

ssize_t toml_disallowed_syscalls_cnt = toml_array_nelem(toml_disallowed_syscalls);
if (toml_disallowed_syscalls_cnt < 0)
return -EPERM;
if (toml_disallowed_syscalls_cnt == 0)
return 0;

char* toml_disallowed_syscall_str = NULL;

for (ssize_t i = 0; i < toml_disallowed_syscalls_cnt; i++) {
toml_raw_t toml_disallowed_syscall_raw = toml_raw_at(toml_disallowed_syscalls, i);
if (!toml_disallowed_syscall_raw) {
log_error("Invalid disallowed syscall in manifest at index %ld", i);
return -EINVAL;
goto out;
}

ret = toml_rtos(toml_disallowed_syscall_raw, &toml_disallowed_syscall_str);
if (ret < 0) {
log_error("Invalid disallowed syscall in manifest at index %ld (not a string)", i);
return -EINVAL;
goto out;
}

uint64_t sysno = get_syscall_number(toml_disallowed_syscall_str);
if (sysno >= LIBOS_SYSCALL_BOUND) {
log_error("Unrecognized disallowed syscall `%s` in manifest at index %ld",
toml_disallowed_syscall_str, i);
return -EINVAL;
goto out;
}

/* force the syscall to be unrecognized by LibOS and thus return -ENOSYS */
libos_syscall_table[sysno] = NULL;

free(toml_disallowed_syscall_str);
toml_disallowed_syscall_str = NULL;
}

ret = 0;
out:
free(toml_disallowed_syscall_str);
return ret;
}
37 changes: 37 additions & 0 deletions libos/test/regression/disallowed_syscalls.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#define _GNU_SOURCE
#include <err.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/eventfd.h>
#include <sys/types.h>
#include <unistd.h>

int main(void) {
int ret;

errno = 0;
ret = eventfd(0, 0);
if (ret != -1 && errno != ENOSYS)
errx(1, "expected eventfd to fail with -ENOSYS but it returned ret=%d errno=%d", ret,
errno);

errno = 0;
ret = fork();
if (ret != -1 && errno != ENOSYS)
errx(1, "expected fork to fail with -ENOSYS but it returned ret=%d errno=%d", ret, errno);

errno = 0;
ret = getpid();
if (ret < 0)
errx(1, "expected getpid to succeed but it returned ret=%d errno=%d", ret, errno);

errno = 0;
ret = gettid();
if (ret < 0)
errx(1, "expected gettid to succeed but it returned ret=%d errno=%d", ret, errno);

puts("TEST OK");
return 0;
}
32 changes: 32 additions & 0 deletions libos/test/regression/disallowed_syscalls.manifest.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
loader.entrypoint = "file:{{ gramine.libos }}"
libos.entrypoint = "{{ entrypoint }}"

loader.env.LD_LIBRARY_PATH = "/lib"

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

sys.disallowed_syscalls = [
# even though glibc wrapper is called eventfd, glibc translates it into eventfd2;
# we specify both syscall variants to be on the safe side
"eventfd",
"eventfd2",

# even though glibc wrapper is called fork, glibc translates it into clone;
# we specify all syscall variants to be on the safe side
"fork",
"vfork",
"clone",
"clone3",
]

sgx.debug = true
sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }}

sgx.trusted_files = [
"file:{{ gramine.libos }}",
"file:{{ gramine.runtimedir(libc) }}/",
"file:{{ binary_dir }}/{{ entrypoint }}",
]
1 change: 1 addition & 0 deletions libos/test/regression/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ tests = {
},
'devfs': {},
'device_passthrough': {},
'disallowed_syscalls': {},
'double_fork': {},
'epoll_epollet': {},
'epoll_test': {},
Expand Down
4 changes: 4 additions & 0 deletions libos/test/regression/test_libos.py
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,10 @@ def test_010_syscall_restart(self):
self.assertIn('Got: P', stdout)
self.assertIn('TEST 2 OK', stdout)

def test_020_disallowed_syscalls(self):
stdout, _ = self.run_binary(['disallowed_syscalls'])
self.assertIn('TEST OK', stdout)

class TC_40_FileSystem(RegressionTestCase):
def test_000_proc(self):
stdout, _ = self.run_binary(['proc_common'])
Expand Down
1 change: 1 addition & 0 deletions libos/test/regression/tests.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ manifests = [
"debug_log_inline",
"devfs",
"device_passthrough",
"disallowed_syscalls",
"double_fork",
"env_from_file",
"env_from_host",
Expand Down
1 change: 1 addition & 0 deletions libos/test/regression/tests_musl.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ manifests = [
"debug_log_inline",
"devfs",
"device_passthrough",
"disallowed_syscalls",
"double_fork",
"env_from_file",
"env_from_host",
Expand Down
1 change: 1 addition & 0 deletions python/graminelibos/manifest_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
}],
'brk': {'max_size': _size},
'disallow_subprocesses': bool,
'disallowed_syscalls': [str],
'enable_extra_runtime_domain_names_conf': bool,
'enable_sigterm_injection': bool,
'experimental__enable_flock': bool,
Expand Down

0 comments on commit 0504ab3

Please sign in to comment.