Browse Source

[LibOS] Allow Graphene-SGX to occupy the same process on execve()

The execve() syscall starts a new executable in the *same* process. Previously,
Graphene followed this convention *only* for non-SGX PALs. If PAL was Linux-SGX,
Graphene silently terminated the process and created a new one.

This deviation from standard execve() behavior resulted in the host shell
becoming detached from the Graphene-SGX process. In turn, this led to our
Bash example (on Ubuntu 18.04, bash version 4.4.19) "terminating" early
from the point of view of Jenkins, and SGX-18.04 pipeline failed.

This commit allows Graphene-SGX to execve() in the same process, but only if it
is the same executable (as in the Bash example). It is still impossible to
execve() in the same process for a different executable since this requires a
new SGX enclave measurement and thus demands a new process.
Dmitrii Kuvaiskii 4 years ago
parent
commit
e14bf7950f

+ 0 - 2
LibOS/shim/include/shim_defs.h

@@ -47,8 +47,6 @@
 
 #define CP_INIT_VMA_SIZE            (64 * 1024 * 1024)  /* 64MB */
 
-#define EXECVE_RTLD                 1
-
 #define ENABLE_ASLR                 1
 
 /* debug message printout */

+ 40 - 8
LibOS/shim/src/sys/shim_exec.c

@@ -38,6 +38,31 @@
 #include <sys/mman.h>
 #include <asm/prctl.h>
 
+/* returns 0 if normalized URIs are the same; assumes file URIs */
+static int normalize_and_cmp_uris(const char* uri1, const char* uri2) {
+    char norm1[STR_SIZE];
+    char norm2[STR_SIZE];
+    size_t len;
+    int ret;
+
+    if (!strstartswith_static(uri1, "file:") || !strstartswith_static(uri2, "file:"))
+        return -1;
+
+    uri1 += static_strlen("file:");
+    len = sizeof(norm1);
+    ret = get_norm_path(uri1, norm1, &len);
+    if (ret < 0)
+        return ret;
+
+    uri2 += static_strlen("file:");
+    len = sizeof(norm2);
+    ret = get_norm_path(uri2, norm2, &len);
+    if (ret < 0)
+        return ret;
+
+    return memcmp(norm1, norm2, len + 1);
+}
+
 static int close_on_exec (struct shim_fd_handle * fd_hdl,
                           struct shim_handle_map * map)
 {
@@ -451,16 +476,23 @@ err:
 
     SAVE_PROFILE_INTERVAL(open_file_for_exec);
 
-#if EXECVE_RTLD == 1
-    if (strcmp_static(PAL_CB(host_type), "Linux-SGX")) {
-        int is_last = check_last_thread(cur_thread) == 0;
-        if (is_last) {
-            debug("execve() in the same process\n");
-            return shim_do_execve_rtld(exec, argv, envp);
+    bool use_same_process = check_last_thread(cur_thread) == 0;
+    if (use_same_process && !strcmp_static(PAL_CB(host_type), "Linux-SGX")) {
+        /* for SGX PALs, can use same process only if it is the same executable (because a
+         * different executable has a different measurement and thus requires a new enclave);
+         * this special case is to correctly handle e.g. Bash process replacing itself */
+        assert(cur_thread->exec);
+        if (normalize_and_cmp_uris(qstrgetstr(&cur_thread->exec->uri), qstrgetstr(&exec->uri))) {
+            /* it is not the same executable, definitely cannot use same process */
+            use_same_process = false;
         }
-        debug("execve() in a new process\n");
     }
-#endif
+
+    if (use_same_process) {
+        debug("execve() in the same process\n");
+        return shim_do_execve_rtld(exec, argv, envp);
+    }
+    debug("execve() in a new process\n");
 
     INC_PROFILE_OCCURENCE(syscall_use_ipc);
 

+ 16 - 0
LibOS/shim/test/regression/exec_same.c

@@ -0,0 +1,16 @@
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int main(int argc, char** argv, char** envp) {
+    if (argc > 1) {
+        puts(argv[1]);
+        return 0;
+    }
+    char* const new_argv[] = {argv[0], "hello from execv process", NULL};
+    execv(new_argv[0], new_argv);
+
+    /* must never reach this */
+    return 1;
+}

+ 6 - 2
LibOS/shim/test/regression/test_libos.py

@@ -85,14 +85,18 @@ class TC_01_Bootstrap(RegressionTestCase):
             '000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ',
             stdout)
 
-    def test_201_fork_and_exec(self):
+    def test_201_exec_same(self):
+        stdout, stderr = self.run_binary(['exec_same'])
+        self.assertIn('hello from execv process', stdout)
+
+    def test_202_fork_and_exec(self):
         stdout, stderr = self.run_binary(['fork_and_exec'])
 
         # fork and exec 2 page child binary
         self.assertIn('child exited with status: 0', stdout)
         self.assertIn('test completed successfully', stdout)
 
-    def test_202_vfork_and_exec(self):
+    def test_203_vfork_and_exec(self):
         stdout, stderr = self.run_binary(['vfork_and_exec'])
 
         # vfork and exec 2 page child binary