Cloud computing is an appealing paradigm as it saves costs due to the sharing of resources, is widely applicable to many services (IaaS, PaaS, SaaS, etc.) and offers a simple interface with users. It is also tempting for attacks given that the resources are accessible remotely and a single cloud service provider can hold sensitive data from multiple companies.
Until recently no practical solution existed for secure data processing in cloud environment: service provider either had to use homomorphic encryption, which is an expensive cryptographic technique that allows processing on encrypted data, or special purpose cryptographic co-processors that are expensive both in terms of economic cost and performance. In 2015 Intel released Software Guard eXtensions (SGX), a new set of instructions available in CPUs. Intel SGX introduces the notion of enclaves, which are isolated memory regions for code and data. Enclaves are protected from a strong attacker that has access to the entire software stack, including the operating system, as well as the hardware with the exception of the CPU package. The enclave memory is encrypted and integrity-protected by the hardware when it leaves the CPU. As a result no plaintext secrets are stored in main memory.
This blog post is an introduction to the Intel SGX SDK. It is composed of two exercises: (i) how to write a Hello World application; and (ii) how to write a shim layer so that the same code can be executed with and without Intel SGX. The source code is available here.
For this first exercise we are going to write a very simple application that displays the traditional "Hello World!" message from within an SGX enclave.
The source code is in the exercise1/
directory. It contains the following files:
TCSNum
)
and the heap maximal size in bytes (HeapMaxSize
);The code does not compile when you execute make . Instead the compiler outputs the following error:
enclave.o: In function `ecall_entrypoint': enclave.c:21: undefined reference to `puts'The standard library of the SGX SDK does not contain any I/O functions, such as
printf
, write
, or read
.
Indeed, the I/O operations are not supported inside enclaves. Instead it is
necessary to make a call outside the enclave that will then execute the
appropriate function. To overcome this problem we are going to replace the
call to printf
in enclave.c by a call to my_printf
,
defined as follows:
int my_printf(const char *format, ...) { return 0; }
The compilation is now successful. However executing the program does not display anything yet:
$ make $ source /opt/intel/sgxsdk/environment $ ./ex1 $
In this section we are going to copy the message to be displayed from inside the enclave to outside of the enclave, by defining a new ocall . The printf function takes a variable number of arguments. Unfortunately this is not supported by enclave interfaces. We thus first need to create the buffer that will have to be displayed in our my_printf function:
/* In enclave.c */ int my_printf(const char *format, ...) { char buf[BUFSIZ] = {'\0'}; va_list ap; va_start(ap, format); vsnprintf(buf, BUFSIZ, format, ap); va_end(ap); //TODO: need to make an ocall, passing buf as an argument return 0; }BUFSIZ is defined by the Intel SGX SDK in tlibc/stdio.h and has a value of 8kB.
We also need to define a function in the untrusted code that will be called by our ocall:
/* In main.h */ int ocall_printf(const char* buf); /* In main.c */ int ocall_printf(const char* buf) { return printf("\%s", buf); }Now we can add our ocall to the enclave interface:
/* In enclave.edl */ untrusted { int ocall_printf(const char* buf); };
We are not done yet. Indeed, as we try to compile the program once again, we observe the following error:
error: pointer/array should have direction attribute or 'user_check' Makefile:96: recipe for target 'enclave_t.c' failedThe pointer arguments in the enclave interface must have a direction attribute:
/* In enclave.edl */ untrusted { int ocall_printf([in, string]const char* buf); };
Finally we need to call our ocall from inside the enclave.
/* In enclave.c */ int my_printf(const char *format, ...) { char buf[BUFSIZ] = {'\0'}; va_list ap; va_start(ap, format); vsnprintf(buf, BUFSIZ, format, ap); va_end(ap); int r; sgx_status_t ret = ocall_printf(&r, buf); if (ret != SGX_SUCCESS) { //there was a problem during the ocall r = -1; } return r; }Note that the value returned by our ocall function defined outside the enclave and by the call inside the enclave are different: int vs sgx_status_t . Furthermore, the integer returned by the ocall is passed as reference as the first argument of the ocall . This is the way to make ecalls and ocalls. These functions with a slightly different signatures are the wrappers constructed by the SGX SDK, based on the content of the enclave interface file. The same calling convention is used for the ecall ecall_entrypoint in main.c. Let’s compile and execute the program one last time:
$ ./ex1 Hello World! $
Congratulations! You can now write your own applications using Intel SGX!
In this exercise you will create a transparent wrapper around a library that will be executed inside an enclave. The goal is to use the same code both with and without Intel SGX. You will also use some of the synchronisation primitives provided by the SGX SDK. The provided code, in the exercise3/ directory, can already be compiled without Intel SGX by using the Makefile.nosgx . It allows you to create an SQL database and execute arbitrary statements, entered from the command line (one statement per line). The database engine used is SQLite [3]. The database can either be stored in memory (using ":memory:" as its name) or on disk. The input.txt file contains a few SQL statements that you can use to test it.
Compared to the previous exercises you will find several new files that will help us to build a wrapper around the in-enclave library:
Your task is to add the necessary ecalls so that the code can be compiled and executed with an SGX enclave. While not necessary, you should read Makefile.sgx . First, it defines the COMPILE_WITH_INTEL_SGX macro that you can use to detect if the code has to be compiled with Intel SGX or not. Second, you will notice that the source files are first pre-processed (flag -E ) and then compiled to an object file. This step is necessary to ensure that all the definitions missing from the Intel SGX SDK libraries have first been resolved by using the headers of the system.