Introduction:
Intel® Software Guard Extensions Evaluation SDK from Intel is a collection of APIs, sample source code, libraries and tools that enables the software developer to write and debug Intel® Software Guard Extensions applications in C/C++. In this article, we will unwrap the “SampleEnclave” application within the SDK and demonstrate APIs that allow for creation and initialization of an enclave, creating enclave files (.edl), some of the data marshalling and unmarshalling principles and how to transfer control between the application and the secure enclave. So let’s dive in!
Prerequisites:
If you are considering enabling your applications with SGX, it is important to understand the hardware and software requirements of the development platform as well as the device on which your applications is deployed. You can run the Platform Capability Tool utility to check if you have an SGX enabled device. You can also performs checks within the installer and through the application to determine device capabilities. Detailed instructions are available here. APIs specific to the creation and initialization of enclaves in your application are described in subsequent sections of this article.
Other requirements to understand this sample:
Download and install the Platform Software and the SGX SDK from here.
Also note that the Intel® SGX Enclave Project plugin is only available for Visual Studio 2012.
Terminology:
Before we take a look at the code, let’s clarify some of the commonly used terminology in the rest of this (and other SGX) article(s):
- Untrusted: refers to code or construct that runs in the application environment outside the enclave.
- Trusted: refers to code or construct that runs in the Trusted Execution Environment inside the enclave.
- ECALL: A call from the application into an interface function within the enclave.
- OCALL: A call made from within the enclave to the application.
- Untrusted Run-Time System (uRTS): Code that executes outside the enclave environment and performs functions such as:
- Loading and manipulating an enclave (Ex: destroying an enclave).
- Making calls (ECALLs) to an enclave and receiving calls (OCALLs) from an enclave.
- Trusted Run-Time System (tRTS): Code that executes within the enclave environment and performs functions such as:
- Receiving calls (ECALLs) from the application and making calls outside (OCALLs) the enclave.
- Managing the enclave itself.
Creating an enclave project in Visual Studio:
- On the menu bar of Microsoft* Visual Studio*, choose File-->New-->Project.
The New Project dialog box opens.
- Select Templates-->Visual C++-->Intel® SGX Enclave Project. Enter name, location, and solution name in the appropriate fields like any other Microsoft* Visual Studio* project.
Figure 1 Intel® SGX Wizard: New Project Creation
- Click OK and the welcome dialog appears.
Figure 2 Intel® SGX Wizard: Welcome Dialog
- Click Next to go to the Enclave Settings page.
Figure 3 Intel® SGX Wizard: Enclave Settings
- Configure the enclave with proper settings
- Project Type:
- Enclave – Create an enclave project.
- Enclave library – Create a static library for an enclave project.
- Additional Libraries:
- C++ STL – Link C++ STL with the enclave project.
- Signing Key:
- Import an existing signing key to the enclave project. A random key will be generated if no file is selected. The Enclave signer will sign the enclave with the key file (see File Formats).
- Project Type:
When the enclave project is created, the wizard ensures that the enclave project has proper settings.
NOTE:
The Wizard creates an enclave project with several files. See Enclave Project Files for a detailed file list.
Figure 4 Intel® SGX Wizard: Solution Explorer
Defining an enclave interface:
An EDL file is used to define the enclave interface. In the “SampleEnclave” example, there are four main EDL files – Arrays.edl, Functions.edl, Pointers.edl and Types.edl. These enclave files define the interfaces and data types the enclave will support. Let’s take a closer look at one of the files and examine each section. To follow through in Visual Studio, find the SampleEnclave project under c:\Program Files (x86)\Intel\IntelSGXSDK\src\SampleEnclave
There are two parts to an EDL file. The trusted section defines the ECALLS whereas the untrusted section defines the OCALLS. While an ECALL defines entry point into the enclave, the OCALL defines the transfer of control from inside the enclave to the application to perform system calls and other I/O operations. OCALLS could also be used in cases where the enclave needs to transfer data back to the application. It is important to note that an SGX enabled application should always have at least one public ECALL to enter the enclave. OCALLs are optional.
Importing enclaves into the application:
Defining the EDL file has several other benefits. You can import one EDL file from another. In the SampleEnclave application, the Enclave.edl file imports the other edl files. To import EDL files into your application, use the Visual Studio SGX plugin as shown below:
-
Right click the application project and select Intel® SGX Configuration -> Import Enclave. The Import Enclave dialog box opens.
- Check the sample_enclave.edl box and press OK.
An EDL defined as a library could also be imported into other EDL files (in which case the “trusted” section is optional). You can either choose to import select functions or import all functions. Example:
enclave {
// An EDL file can optionally import functions from other EDL files.
from “Edger8rSyntax/Arrays.edl” import ecall_array_in; // selective importing
from “another/Types.edl” import *; // import all functions
…
};
Configuring and enabling SGX:
The first step within an application is to dynamically query for the status of the device to ensure SGX is enabled. In the SampleEnclave application, the “query_sgx_status” function uses the sgx_enable_device API provided by the Platform Software (PSW) to determine the current status of the device.
API syntax:
sgx_status_t sgx_enable_device(
sgx_device_status_t *sgx_device_status
);
Device status values:
The [out] parameter sgx_device_status is populated by one of the below device status values:
SGX_ENABLED
Intel SGX device is already enabled
SGX_DISABLED_REBOOT_REQUIRED
Intel SGX device is currently disabled and a reboot is required to enable it.
SGX_DISABLED_LEGACY_OS
The operating system does not support enabling Intel SGX device
SGX_DISABLED
Intel SGX device is disabled
Recommended user action:
sgx_enable_device API re-enables SGX in case it has been disabled due to a platform update. In this case, a reboot is required to let SGX take effect. If the user chooses otherwise, the application logic should allow the app to run in non-SGX mode.
Loading an enclave:
Now we dive into creating an enclave. The sgx_create_enclave function provided by the untrusted Run Time System (uRTS) handles this. Use this API to load the enclave into enclave memory. The sgx_sign.exe tool should be used to first sign the enclave that is built as a DLL. If the enclave is being loaded for the first time, an all 0 token passed as a parameter would be populated with the launch token once the enclave is created successfully. Subsequent initializations of the enclave should use the same token. If using the enclave in debug mode, ensure that the 2nd parameter is set to 1.
API Syntax:
#if !defined(NDEBUG) || defined(EDEBUG)
#define SGX_DEBUG_FLAG ((int)1)
#else
#define SGX_DEBUG_FLAG ((int)0)
#endif
sgx_status_t sgx_create_enclave(
const char *file_name,
const int debug,
sgx_launch_token_t *launch_token,
int *launch_token_updated,
sgx_enclave_id_t *enclave_id,
sgx_misc_attribute_t *misc_attr
);
More details can be found in the SDK API documentation.
Note that once the enclave is loaded, it is initialized by the trusted Run Time Service (tRTS) after authenticating the enclave attributes. The uRTS and tRTS are part of the PSW. The control flow between the uRTS and tRTS is as shown below:
Now let’s go back to the EDL file definition and see how trusted and untrusted function calls are managed between the application and the enclave.
In the SampleEnclave source tree, you will see two projects – one for the untrusted portion of the app and the other for the enclave. On closer examination of both these projects, you will find Edger8r Syntax. Edger8r is a tool that parses the EDL files and generates the trusted and untrusted bridges and proxies to interface between the application and the enclave. The default files generated by Edger8r on the untrusted (application) side are the Enclave_u.h/.c and on the trusted (enclave) side, you have the corresponding Enclave_t.h/.c. The tool generates C wrappers to interface in and out of the enclave. The below figure depicts the glue code generated by Edger8r:
The basic flow of control is shown by the figure below:
It is important to note some of the EDL supported attributes, ex: direction attributes ([in], [out]) and [user_check]
[in]: For an ECALL, the parameter designated by [in] is passed from the application to the enclave, whereas for an OCALL, it is a parameter passed from enclave back to the application.
[out]: For an ECALL, the parameter designated by [out] is returned by the enclave to the application whereas for an OCALL, it is a parameter passed from application to the enclave.
[user_check]: In some cases, the data communication demands across the enclave boundary may require that you bypass the restrictions imposed by [in] and [out]. Example: a buffer might be too large to fit in enclave memory and needs to be fragmented into smaller blocks that are then processed in a series of ECALLs, or an application might require passing a pointer to trusted memory (enclave context) as an ECALL parameter. As an application developer, you have to understand the risks of passing data using [user_check] and has to perform additional checks manually inside the application to ensure none of the enclave secrets are let out.
A summary of how the above attributes are used with ECALLs and OCALLs are listed in the tables below:
More details about the use of [user_check] can be found in the SDK documentation.
With this insight, now let’s take a look at an example function see how this translates to a corresponding trusted function call into the enclave.
Firstly, from within the application, we use the edger8r generated wrapper file for marshalling purposes (refer to the Arrays.cpp, Pointers.cpp, Types.cpp, functions.cpp for a list of all interfaces). The edger8r_pointer_attributes() function verifies that the restrictions imposed by the [in], [out], [user_check] are met. The wrapper calls the untrusted version of the ECALL function declared within the Enclave_u.c with the enclave id. The sgx_is_outside_enclave function then transfers control to the tRTS that calls the trusted version of the ecall_pointer_user_check() function (see Enclave_t.c).
Data marshalling:
ECALL:
For ECALLs, it is the task of the trusted bridge to ensure that the marshalling structure does not overlap enclave memory. Consequently, it also automatically allocates space on the trusted stack to hold a copy of the structure. The trusted bridge then checks that pointer parameters with their full range do not overlap with enclave memory.
[in]: When a pointer to untrusted memory with attribute in is passed to the enclave, the trusted bridge allocates memory inside the enclave and copies the memory pointed to by the pointer from outside to the enclave memory.
[out]: When a pointer to untrusted memory with the out attribute is passed to the enclave, the trusted bridge allocates a buffer in trusted memory, zeroes the buffer contents to clear any previous secrets and passes a pointer to this buffer to the trusted function. After the trusted function returns, the trusted bridge copies the contents of the trusted buffer to untrusted memory.
[in/out]: When the in and out attributes are combined, the trusted bridge allocates memory inside the enclave, makes a copy of the buffer in trusted memory before calling the trusted function, and once the trusted function returns, the trusted bridge copies the contents of the trusted buffer to untrusted memory. The amount of data copied out is the same as the amount of data copied in.
OCALL:
For OCALLs, the trusted proxy allocates memory on the outside stack to pass the marshalling structure and checks that pointer parameters with their full range are within enclave.
[in]: When a pointer to trusted memory with attribute in is passed from an enclave (an OCALL), the trusted proxy allocates memory outside the enclave and copies the memory pointed by the pointer from inside the enclave to untrusted memory.
[out]: When a pointer to trusted memory with the out attribute is passed from an enclave (an OCALL), the trusted proxy allocates a buffer on the untrusted stack, and passes a pointer to this buffer to the untrusted function. After the untrusted function returns, the trusted proxy copies the contents of the untrusted buffer to trusted memory.
[in/out]: When the in and out attributes are combined, the trusted proxy allocates memory outside the enclave, makes a copy of the buffer in untrusted memory before calling the untrusted function, and after the untrusted function returns the trusted proxy copies the contents of the untrusted buffer to trusted memory. The amount of data copied out is the same as the amount of data copied in. For details on marshalling, refer to the SDK documentation.