Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
3741: Syscall TCB Reduction/Syscall Dispatching Rewrite r=anakrish a=anakrish

Syscalls are routed directly to their implementations rather than via a switch-case.
This has the following benefits:

- Reduced TCB
  Switch-case causes all the syscalls to be retained in an enclave.
  Routing directly allows the linker to eliminate those syscalls that are not used by
  an enclave.
  For example echo enclave reports only the following syscalls:
  ❯ objdump -t echo_enc | grep oe_SYS_
  000000000003002b l     F .text  00000000000000bc oe_SYS_clock_gettime_impl
  00000000000300e7 l     F .text  00000000000000c6 oe_SYS_gettimeofday_impl
  0000000000067e10 l     F .text  000000000000005f oe_SYS_futex_impl

- Syscall callgraphs can be analyzed easily.
  Since syscall implementations are directly called rather than via a switch-case,
  the static callgraphs are simpler and more accurate.
  ❯ callgraph.py echo_enc oe_SYS_gettimeofday_impl
 oe_SYS_gettimeofday_impl 0x300e7 /home/anakrish/work/syscall/libc/syscalls.c:75
  └── __clock_gettime 0x67d97 /home/anakrish/work/syscall/3rdparty/musl/musl/src/time/clock_gettime.c:25
      └── time 0x67d3f /home/anakrish/work/syscall/3rdparty/musl/musl/src/time/time.c:4
          └── x509_get_current_time 0x38ff8 /home/anakrish/work/syscall/3rdparty/mbedtls/mbedtls/library/x509.c:929
              ├── mbedtls_x509_time_is_past 0x3927a /home/anakrish/work/syscall/3rdparty/mbedtls/mbedtls/library/x509.c:994
              │   ├── x509_crt_verifycrl 0x36d69 /home/anakrish/work/syscall/3rdparty/mbedtls/mbedtls/library/x509_crt.c:1832
              │   │   └── x509_crt_verify_chain 0x37491 /home/anakrish/work/syscall/3rdparty/mbedtls/mbedtls/library/x509_crt.c:2288
              │   │       └── mbedtls_x509_crt_verify_restartable 0x37a3f /home/anakrish/work/syscall/3rdparty/mbedtls/mbedtls/library/x509_crt.c:2562
              │   │           └── mbedtls_x509_crt_verify 0x379e4 /home/anakrish/work/syscall/3rdparty/mbedtls/mbedtls/library

- It is possible to figure out the list of syscalls retained in an enclave via:
  `objdump -t enclave-filename | grep oe_SYS_`

Details

- Syscalls are most often performed by libc (in our case MUSL) rather than the user code.
  A syscall in MUSL of the form
      syscall(SYS_open, a, b)
  is converted to
      oe_SYS_open_impl(a, b)
  avoiding the dispatch via switch-case.
  This is accomplished by patching musl/src/internal/syscall.h.

  Note: OE currently (prior to this PR) relies on SYSCALL_NO_INLINE define to get MUSL to
  not use SYSCALL instruction and instead call `syscall` function for dispatching syscalls.
  SYSCALL_NO_INLINE macro has been removed in newer versions of MUSL and MUSL no longer
  provides the above customization that we want. Therefore we'd anyways have to patch
  musl/src/internal/syscall.h when we upgrade MUSL.

- The existing switch-case based dispatching have been retained for
   - use by libOS. It may turn out that libOS does not need the switch-case based
     dispatching at which point we can remove it.
   - the case where user code directly makes a syscall instead relying of MUSL.
     Such cases ought to be quite rare.

- Syscall implementations are declared WEAK so that they can be overridden.
  This is intended for use by tests and not by users.
  This provides a (cleaner IMO) alternative to syscall hooks.
  The syscall function names have oe_SYS_ prefix and the chance of user accidentally
  overriding a syscall implementation is quite low. The chance can be further minimized
  by obfuscating the prefix.

- N_M (N or More) syscalls.
  Some syscalls take N or more parameters. Such syscalls are declared and defined using
  the N_M macros.
  E.g:
      OE_DEFINE_SYSCALL2_M(Sys_open)
  Sys_open is called with atleast 2 parameters; but sometimes with 3. There is no easy
  way to capture this in C. It C++ it is possible to provide overloads and capture all
  the calling patterns. As a reasonable alternative, we declare SYS_open to take 2 or
  more parameters.

  Tracking such syscalls will allows us in future to make sure that OE implementations
  of syscalls do indeed consume all the supplied parameters.

  MUSL sometimes calls certain syscalls with syscall_cp macro which always supplies
  6 parameters to the syscall - the original supplied parameters followed by zeros.
  It is not clear why MUSL does this. But this has the consequence that
  syscalls that are called thus must be declared using the N_M macros.

Signed-off-by: Anand Krishnamoorthi <[email protected]>

Co-authored-by: Anand Krishnamoorthi <[email protected]>
  • Loading branch information
oeciteam and anakrish committed Dec 1, 2020
2 parents 49a00bd + 05ff3c5 commit 728d062
Show file tree
Hide file tree
Showing 7 changed files with 790 additions and 420 deletions.
133 changes: 61 additions & 72 deletions 3rdparty/musl/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

if (OE_SGX)
set(ARCH "x86_64")
else()
else ()
set(ARCH "aarch64")
endif()
endif ()

# NOTE: MUSL is NOT built by the `ExternalProject_Add` command. These
# C flags only apply to a configuration step that generates the MUSL
Expand All @@ -31,80 +31,67 @@ set(MUSL_DIR ${CMAKE_CURRENT_BINARY_DIR}/musl)
set(MUSL_INCLUDES ${OE_INCDIR}/openenclave/libc)

set(MUSL_APPEND_DEPRECATIONS
"${CMAKE_CURRENT_LIST_DIR}/append-deprecations ${MUSL_INCLUDES}")
"${CMAKE_CURRENT_LIST_DIR}/append-deprecations ${MUSL_INCLUDES}")
if (USE_CLANGW)
set(MUSL_CFLAGS "-target x86_64-pc-linux ${MUSL_CFLAGS}")
set(MUSL_CC clang)
set(MUSL_CXX clang++)
set(MUSL_APPEND_DEPRECATIONS "echo 'Deprecations not applied on Windows'")
endif ()

include (ExternalProject)
ExternalProject_Add(musl_includes
DOWNLOAD_COMMAND
${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_LIST_DIR}/musl
${MUSL_DIR}
include(ExternalProject)
ExternalProject_Add(
musl_includes
DOWNLOAD_COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_LIST_DIR}/musl ${MUSL_DIR}
PATCH_COMMAND
COMMAND ${CMAKE_COMMAND} -E copy
${MUSL_DIR}/arch/${ARCH}/syscall_arch.h
${MUSL_DIR}/arch/${ARCH}/__syscall_arch.h
COMMAND ${CMAKE_COMMAND} -E copy
${PATCHES_DIR}/syscall_arch.h
${MUSL_DIR}/arch/${ARCH}/syscall_arch.h
COMMAND ${CMAKE_COMMAND} -E copy
${PATCHES_DIR}/pthread_${ARCH}.h
${MUSL_DIR}/arch/${ARCH}/pthread_arch.h
COMMAND ${CMAKE_COMMAND} -E copy
${PATCHES_DIR}/setjmp.h
${MUSL_DIR}/include/setjmp.h
COMMAND ${CMAKE_COMMAND} -E copy
${PATCHES_DIR}/execinfo.h
${MUSL_DIR}/include/execinfo.h
COMMAND ${CMAKE_COMMAND} -E copy ${MUSL_DIR}/arch/${ARCH}/syscall_arch.h
${MUSL_DIR}/arch/${ARCH}/__syscall_arch.h
COMMAND ${CMAKE_COMMAND} -E copy ${PATCHES_DIR}/syscall.h
${MUSL_DIR}/src/internal/syscall.h
COMMAND ${CMAKE_COMMAND} -E copy ${PATCHES_DIR}/syscall_arch.h
${MUSL_DIR}/arch/${ARCH}/syscall_arch.h
COMMAND ${CMAKE_COMMAND} -E copy ${PATCHES_DIR}/pthread_${ARCH}.h
${MUSL_DIR}/arch/${ARCH}/pthread_arch.h
COMMAND ${CMAKE_COMMAND} -E copy ${PATCHES_DIR}/setjmp.h
${MUSL_DIR}/include/setjmp.h
COMMAND ${CMAKE_COMMAND} -E copy ${PATCHES_DIR}/execinfo.h
${MUSL_DIR}/include/execinfo.h
CONFIGURE_COMMAND
${CMAKE_COMMAND} -E chdir ${MUSL_DIR}
${OE_BASH} -x ./configure
--includedir=${MUSL_INCLUDES}
CFLAGS=${MUSL_CFLAGS}
CC=${MUSL_CC}
CXX=${MUSL_CXX}
${CMAKE_COMMAND} -E chdir ${MUSL_DIR} ${OE_BASH} -x ./configure
--includedir=${MUSL_INCLUDES} CFLAGS=${MUSL_CFLAGS} CC=${MUSL_CC}
CXX=${MUSL_CXX}
BUILD_COMMAND
COMMAND ${CMAKE_COMMAND} -E copy_directory
${MUSL_DIR}/include
${MUSL_INCLUDES}
COMMAND ${CMAKE_COMMAND} -E copy_directory
${MUSL_DIR}/arch/generic/bits
${MUSL_INCLUDES}/bits
COMMAND ${CMAKE_COMMAND} -E copy_directory
${MUSL_DIR}/arch/${ARCH}/bits
${MUSL_INCLUDES}/bits
# bash -c requires the command string to be in a single line
COMMAND ${OE_BASH} -c "sed -f ${MUSL_DIR}/tools/mkalltypes.sed ${MUSL_DIR}/arch/${ARCH}/bits/alltypes.h.in ${MUSL_DIR}/include/alltypes.h.in > ${MUSL_INCLUDES}/bits/alltypes.h"
COMMAND ${CMAKE_COMMAND} -E copy
${MUSL_DIR}/arch/${ARCH}/bits/syscall.h.in
${MUSL_INCLUDES}/bits/syscall.h
# bash -c requires the command string to be in a single line
COMMAND ${OE_BASH} -c "sed -n -e s/__NR_/SYS_/p < ${MUSL_DIR}/arch/${ARCH}/bits/syscall.h.in >> ${MUSL_INCLUDES}/bits/syscall.h"
COMMAND ${CMAKE_COMMAND} -E copy
${MUSL_INCLUDES}/endian.h
${MUSL_INCLUDES}/__endian.h
COMMAND ${CMAKE_COMMAND} -E copy
${PATCHES_DIR}/endian.h
${MUSL_INCLUDES}/endian.h
# Append deprecations.h to all C header files.
COMMAND ${OE_BASH} -c "${MUSL_APPEND_DEPRECATIONS}"
# Copy local deprecations.h to include/bits/deprecated.h.
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_LIST_DIR}/deprecations.h
${MUSL_INCLUDES}/bits/deprecations.h
BUILD_BYPRODUCTS
${MUSL_INCLUDES} ${MUSL_DIR}
COMMAND ${CMAKE_COMMAND} -E copy_directory ${MUSL_DIR}/include
${MUSL_INCLUDES}
COMMAND ${CMAKE_COMMAND} -E copy_directory ${MUSL_DIR}/arch/generic/bits
${MUSL_INCLUDES}/bits
COMMAND ${CMAKE_COMMAND} -E copy_directory ${MUSL_DIR}/arch/${ARCH}/bits
${MUSL_INCLUDES}/bits
# bash -c requires the command string to be in a single line
COMMAND
${OE_BASH} -c
"sed -f ${MUSL_DIR}/tools/mkalltypes.sed ${MUSL_DIR}/arch/${ARCH}/bits/alltypes.h.in ${MUSL_DIR}/include/alltypes.h.in > ${MUSL_INCLUDES}/bits/alltypes.h"
COMMAND ${CMAKE_COMMAND} -E copy ${MUSL_DIR}/arch/${ARCH}/bits/syscall.h.in
${MUSL_INCLUDES}/bits/syscall.h
# bash -c requires the command string to be in a single line
COMMAND
${OE_BASH} -c
"sed -n -e s/__NR_/SYS_/p < ${MUSL_DIR}/arch/${ARCH}/bits/syscall.h.in >> ${MUSL_INCLUDES}/bits/syscall.h"
COMMAND ${CMAKE_COMMAND} -E copy ${MUSL_INCLUDES}/endian.h
${MUSL_INCLUDES}/__endian.h
COMMAND ${CMAKE_COMMAND} -E copy ${PATCHES_DIR}/endian.h
${MUSL_INCLUDES}/endian.h
# Append deprecations.h to all C header files.
COMMAND ${OE_BASH} -c "${MUSL_APPEND_DEPRECATIONS}"
# Copy local deprecations.h to include/bits/deprecated.h.
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/deprecations.h
${MUSL_INCLUDES}/bits/deprecations.h
BUILD_BYPRODUCTS ${MUSL_INCLUDES} ${MUSL_DIR}
INSTALL_COMMAND "")

set_property(DIRECTORY PROPERTY ADDITIONAL_MAKE_CLEAN_FILES
${MUSL_INCLUDES}
${MUSL_DIR}
)
set_property(DIRECTORY PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${MUSL_INCLUDES}
${MUSL_DIR})

add_library(oelibc_includes INTERFACE)

Expand All @@ -124,15 +111,17 @@ add_dependencies(oelibc_includes musl_includes)
#
# TODO: Perhaps give this a less misleading name as it includes both C
# and C++ headers (but the latter only when the language is C++).
target_include_directories(oelibc_includes
target_include_directories(
oelibc_includes
INTERFACE
$<BUILD_INTERFACE:$<$<COMPILE_LANGUAGE:CXX>:${LIBCXX_INCLUDES}>>
$<INSTALL_INTERFACE:$<$<COMPILE_LANGUAGE:CXX>:$<INSTALL_PREFIX>/${CMAKE_INSTALL_INCLUDEDIR}/openenclave/3rdparty/libcxx>>
$<BUILD_INTERFACE:${MUSL_INCLUDES}>
$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/${CMAKE_INSTALL_INCLUDEDIR}/openenclave/3rdparty/libc>)
$<BUILD_INTERFACE:$<$<COMPILE_LANGUAGE:CXX>:${LIBCXX_INCLUDES}>>
$<INSTALL_INTERFACE:$<$<COMPILE_LANGUAGE:CXX>:$<INSTALL_PREFIX>/${CMAKE_INSTALL_INCLUDEDIR}/openenclave/3rdparty/libcxx>>
$<BUILD_INTERFACE:${MUSL_INCLUDES}>
$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/${CMAKE_INSTALL_INCLUDEDIR}/openenclave/3rdparty/libc>
)

if (CMAKE_C_COMPILER_ID MATCHES GNU AND
CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL "7.1.0")
if (CMAKE_C_COMPILER_ID MATCHES GNU AND CMAKE_C_COMPILER_VERSION
VERSION_GREATER_EQUAL "7.1.0")
# NOTE: This disables a warning that is only present with newer
# versions of GCC on Ubuntu 18.04.
target_compile_options(oelibc_includes INTERFACE -Wno-implicit-fallthrough)
Expand All @@ -141,4 +130,4 @@ endif ()
install(TARGETS oelibc_includes EXPORT openenclave-targets)

install(DIRECTORY ${MUSL_INCLUDES}
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/openenclave/3rdparty)
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/openenclave/3rdparty)
160 changes: 160 additions & 0 deletions 3rdparty/musl/patches/syscall.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// Copyright (c) Open Enclave SDK contributors.
// Licensed under the MIT License.

/* This file is a patched version of musl/src/internal/syscall.h.
The PATCH_COMMAND step of musl_include target replaces the original
file with this patched version in the build folder.
*/
#ifndef _OE_MUSL_PATCHES_INTERNAL_SYSCALL_H
#define _OE_MUSL_PATCHES_INTERNAL_SYSCALL_H

#include <features.h>

// Include OE's syscall declarations.
#include <openenclave/internal/syscall/declarations.h>

#include <sys/syscall.h>
#include "syscall_arch.h"

#ifndef SYSCALL_RLIM_INFINITY
#define SYSCALL_RLIM_INFINITY (~0ULL)
#endif

#ifndef SYSCALL_MMAP2_UNIT
#define SYSCALL_MMAP2_UNIT 4096ULL
#endif

#ifndef __SYSCALL_LL_O
#define __SYSCALL_LL_O(x) (x)
#endif

// This __SYSCALL_LL_PRW must be defined here to prevent compile errors.
#ifndef __SYSCALL_LL_PRW
#define __SYSCALL_LL_PRW(x) __SYSCALL_LL_O(x)
#endif

#ifndef __scc
#define __scc(X) ((long)(X))
typedef long syscall_arg_t;
#endif

hidden long __syscall_ret(unsigned long), __syscall(syscall_arg_t, ...),
__syscall_cp(
syscall_arg_t,
syscall_arg_t,
syscall_arg_t,
syscall_arg_t,
syscall_arg_t,
syscall_arg_t,
syscall_arg_t);

// Syscalls can be called with upto 6 parameters.
// Wrap each parameter within a __scc call.
#define SYSCALL_ARGS0()
#define SYSCALL_ARGS1(a) __scc(a)
#define SYSCALL_ARGS2(a, b) SYSCALL_ARGS1(a), __scc(b)
#define SYSCALL_ARGS3(a, b, c) SYSCALL_ARGS2(a, b), __scc(c)
#define SYSCALL_ARGS4(a, b, c, d) SYSCALL_ARGS3(a, b, c), __scc(d)
#define SYSCALL_ARGS5(a, b, c, d, e) SYSCALL_ARGS4(a, b, c, d), __scc(e)
#define SYSCALL_ARGS6(a, b, c, d, e, f) SYSCALL_ARGS5(a, b, c, d, e), __scc(f)

// Determine the number of supplied parameters.
#define SYSCALL_NARGS_X(a, b, c, d, e, f, g, n, ...) n
#define SYSCALL_NARGS(...) \
SYSCALL_NARGS_X(_, ##__VA_ARGS__, 6, 5, 4, 3, 2, 1, 0)

// Get the name of the correct SYSCALL_ARGS macro.
// E.g If 5 arguments are supplied, return SYSCALL_NARGS5
#define SYSCALL_CONCAT_X(a, b) a##b
#define SYSCALL_CONCAT(a, b) SYSCALL_CONCAT_X(a, b)

// Wrap each parameter within a __scc call.
#define SYSCALL_ARGS(...) \
SYSCALL_CONCAT(SYSCALL_ARGS, SYSCALL_NARGS(__VA_ARGS__))(__VA_ARGS__)

// MUSL makes call to __syscall as well as syscall.
// Convert each syscall to a call to the corresponding implementation.
// E.g:
// __syscall(SYS_open, a, b, c)
// is converted to
// oe_SYS_open_impl(__scc(a), __scc(b), __scc(c))
#define __syscall(index, ...) \
OE_SYSCALL_NAME(_##index)(SYSCALL_ARGS(__VA_ARGS__))
#define syscall(index, ...) \
__syscall_ret(OE_SYSCALL_NAME(_##index)(SYSCALL_ARGS(__VA_ARGS__)))

// __syscall_cp and syscall_cp function always pass 6 parameters to the
// underlying syscall implementation. MUSL sometimes calls these macros instead
// of the
// __syscall and syscall.
#define SYSCALL_CP_ARGS_X(a, b, c, d, e, f, ...) SYSCALL_ARGS6(a, b, c, d, e, f)
#define SYSCALL_CP_ARGS(...) SYSCALL_CP_ARGS_X(__VA_ARGS__, 0, 0, 0, 0, 0, 0)

#define __syscall_cp(index, ...) \
OE_SYSCALL_NAME(_##index)(SYSCALL_CP_ARGS(__VA_ARGS__))
#define syscall_cp(index, ...) \
__syscall_ret(OE_SYSCALL_NAME(_##index)(SYSCALL_CP_ARGS(__VA_ARGS__)))

// Sockets are dispatched via the following macros.
#ifndef SYSCALL_USE_SOCKETCALL
#define __socketcall(nm, a, b, c, d, e, f) syscall(SYS_##nm, a, b, c, d, e, f)
#define __socketcall_cp(nm, a, b, c, d, e, f) \
syscall_cp(SYS_##nm, a, b, c, d, e, f)
#else
#define __socketcall(nm, a, b, c, d, e, f) \
syscall( \
SYS_socketcall, \
__SC_##nm, \
((long[6]){(long)a, (long)b, (long)c, (long)d, (long)e, (long)f}))
#define __socketcall_cp(nm, a, b, c, d, e, f) \
syscall_cp( \
SYS_socketcall, \
__SC_##nm, \
((long[6]){(long)a, (long)b, (long)c, (long)d, (long)e, (long)f}))
#endif

#define socketcall __socketcall
#define socketcall_cp __socketcall_cp

/* socketcall calls */

#define __SC_socket 1
#define __SC_bind 2
#define __SC_connect 3
#define __SC_listen 4
#define __SC_accept 5
#define __SC_getsockname 6
#define __SC_getpeername 7
#define __SC_socketpair 8
#define __SC_send 9
#define __SC_recv 10
#define __SC_sendto 11
#define __SC_recvfrom 12
#define __SC_shutdown 13
#define __SC_setsockopt 14
#define __SC_getsockopt 15
#define __SC_sendmsg 16
#define __SC_recvmsg 17
#define __SC_accept4 18
#define __SC_recvmmsg 19
#define __SC_sendmmsg 20

// Open syscall is dispatched via the following macros.
#ifdef SYS_open
#define __sys_open(...) __syscall(SYS_open, __VA_ARGS__)
#define sys_open(...) __syscall_ret(__sys_open(__VA_ARGS__))
#define __sys_open_cp(...) __syscall_cp(SYS_open, __VA_ARGS__)
#define sys_open_cp(...) __syscall_ret(__sys_open_cp(__VA_ARGS__))
#else
#define __sys_open(...) __syscall(SYS_openat, __VA_ARGS__)
#define sys_open(...) __syscall_ret(__sys_open(__VA_ARGS__))
#define __sys_open_cp(...) __syscall_cp(SYS_openat, __VA_ARGS__)
#define sys_open_cp(...) __syscall_ret(__sys_open_cp(__VA_ARGS__))
#endif

// The calls must be declared here to prevent compile errors.
hidden void __procfdname(char __buf[static 15 + 3 * sizeof(int)], unsigned);

hidden void* __vdsosym(const char*, const char*);

#endif /* _OE_MUSL_PATCHES_INTERNAL_SYSCALL_H */
Loading

0 comments on commit 728d062

Please sign in to comment.