Trusted Execution Environment on the example of Intel SGX. Basic principles in simple terms. "Hello World!"

This article is aimed primarily at a novice specialist who is only

started researching methods and ways of ensuring information security of executable program code. Sooner or later, all software developers and system engineers face such a task, which happened in one of the projects of Altiriks Systems, within the framework of which it was necessary to implement secure execution of program code in a conditionally unprotected environment. For this purpose, in addition to the already well-known and well-described methods and means of protecting information, the technology Trusted Execution Environment (TEE), which is rarely used in Russian projects, or, speaking in Russian, the technology of trusted execution environments, was chosen. Specifically, in this article we decided to describe a practical example of using Intel processor enclaves for a trusted code execution environment (Intel Software Guard Extensions or SGX).



Trusted runtimes are not only supported by processors from a given manufacturer. Also, TEE is supported by a number of AMD (Secure Execution Environment, Secure Technology) processors, ARM (TrustZone) processors, and RISC-V processors. In addition, TEE is supported by modern IBM Z mainframes.We chose Intel SGX for our example because we believe that at the time of this writing (summer 2020) Intel processors are the most popular and available for beginners in the post-Soviet space. For a complete listing of Intel processor models that support Intel SGX, visit the Intel website under Intel product specifications (ARK) by selecting the appropriate technology to search. And yes, maybe take advantage of Intel SGX emulations for educational or research purposes.Working with several of these emulations revealed a number of difficulties in setting them up. You also need to understand that for real "combat" projects, no emulation of technology based on the apparatus functionality, of course, is acceptable.



Any of your feedback, especially with comments and additions from specialists who already have experience in using TEE in their projects, or with questions from those who are just starting to immerse themselves in this technology, will contribute to a more detailed disclosure of this topic in the following articles. Thanks in advance!



Introduction



The main question we ask at the beginning of the journey of exploring trusted runtime environments is: can we trust the components of a computer system? And if we can, how? Developers, and in particular Intel engineers, give an unequivocal answer to this question: no one except Intel itself. What does this mean? I propose to understand this in more detail.



Privilege rings



For security purposes, the system components of any computer are divided into privilege levels. All modern systems based on Intel processors and not only have a privilege ring system. From external to internal, there is an expansion of authority for the code that is currently being processed by the processor.





Ring number 3. The outer ring contains all the user applications that we use on the computer in everyday life, they have the lowest level of access.

Ring No. 2 and No. 1. Operating systems and device drivers are located at these levels.

Ring number 0. Supervisor mode. This is where the operating system kernel (peripheral management, resource allocation between processes) is located, as well as system drivers.

Ring number-1. Hypervisor. Responsible for the allocation of resources in the event that several operating systems are running on the computer at the same time, and is also responsible for isolating them.

Ring number-2.System Management Mode (SMM - System Management Mode). Manages the power supply of the system, manages expansion cards.



We can form more and more rings to limit the powers of the components of the hierarchy, creating an increasingly complex and loaded system. However, this will only make it easier for an attacker: the more complex the system, the easier it is to find vulnerabilities in it. But how can you provide an extra layer of security where you need it? The answer is one word.



Enclaves



The main task of an attacker is to obtain a privilege level that would provide him with access to the necessary system resources. If this is the secret of the victim application, then the malicious application needs exactly the access level that is responsible for storing secrets in the system. This suggests the conclusion that the management of application secrets should be entrusted to the innermost ring, because access there is the most difficult of all. However, this approach has been somewhat rethought. Now all secrets are stored at the same level with user applications, as well as the code that manages these secrets under one condition: no one, absolutely no one, except the processor, can access them. The program and data are, as it were, packed into a storage, in this case this storage is called an enclave (Enclave - closed, locked),the key from which only the processor has.





Applications that work with a trusted environment



The simpler the system, the less code it contains, the more difficult it is to open it based on security holes (we are not talking about fundamentally unprotected systems), we get a certain axiom: the code that works with a secret should be as simple and short as possible. Packing the entire program code into an enclave is impractical, therefore an application using enclaves should be divided into two parts: "trusted" and "untrusted". The trusted one stores enclaves (there may be several of them), and the untrusted one stores the main program code.



The trusted part is a set of functions and procedures called ECALL (Enclave Call). The signature of such functions must be written in a special header file, and their implementation in the source code file. In general, the approach is similar to what we use in the usual writing of headers, however, in this context, a special C-like language EDL (Enclave Definition Language) is used. It is also necessary to write prototypes of those functions that can be called from within the enclave, such functions are called OCALL (Outside Call). Prototypes are written in the same header where ECALL functions are, and the implementation, unlike ECALL, is written accordingly in the untrusted part of the application.

Trusted and untrusted code are rigidly bound together by certification using the Diffie-Hellman protocol. The processor is responsible for the signing procedure, where the information exchange key is stored, which is updated every time the system is rebooted. The content of the enclaves is stored in shared memory used by user applications, but the storage is encrypted. Only the processor can decrypt the content. In an idealized world, where the enclave code is written without a single bug, and all hardware works exactly as the manufacturer intended and nothing else, we would get a universal, completely secure system. The main advantage of this system is the execution of the secret part on the same processor where all other programs, including user programs, are executed.



However, in the past few years, a large number of microarchitectural vulnerabilities of modern processors have appeared before a wide audience, allowing access to the inside of the enclave: Foreshadow (Specter class vulnerability), SGAxe, Zombieload, CacheOut and many others. There is no guarantee that this list will not be replenished with another serious hardware vulnerability, the software fix of which cannot be called a software "patch". Perhaps we will live to see the time when a completely new processor architecture will be presented to the world, in which all the shortcomings will be corrected, but for now, it is worth talking about what we have at hand. And at hand we have a versatile, powerful tool that dramatically increases the security of today's systems. Raising so muchthat it is implemented in one way or another in billions of devices around the world: from smart watches, smartphones to huge computing clusters.



Hello world!



Let's move from theory to practice. Let's write a small program that implements the already canonical task: print the string "Hello world!" In this interpretation, we will also indicate the place from which the message will be sent.



First you need to download and install the SDK to work with SGX from the official website. To download, you need to go through a simple registration procedure. At the installation stage, you will be prompted to integrate the development package into the version of VS available on your computer, do this. Everything is ready for the successful implementation of your first project using SGX.





Launch VS and create an Intel SGX project.





We choose a name for the project and for the solution and wait for "next".



Next, you will be prompted to select a project configuration, do not change anything, leave the values ​​that were originally proposed.







Then add another project to the created solution: a regular C ++ console application.

As a result, the following picture should appear in the projects dialog box:







Then you need to link the enclave to the untrusted part. Right click on the "Untrusted part" project.







Next, you need to change some properties of projects.



image






This must be done for the program to work correctly. We repeat the steps for both projects.



It is also necessary to indicate the main project in the properties of the solution.





That's it, our program is ready for implementation.



This program will have 3 files with which we will work: Enclave.edl (the same header), Enclave.cpp (implementation of ECALLs is spelled out), Untrusted Part.cpp (the main project file is the untrusted part). Let's put the



following code into files:



Untusted Part.cpp:



#define ENCLAVE_FILE "Enclave.signed.dll" //,     

#include "sgx_urts.h" // ,           
#include "Enclave_u.h" //   
#include "stdio.h"

void print_string(char* buf) //OCALL     -   
{
	printf("ocall output: %s\n", buf);
}

int main()
{
	sgx_enclave_id_t eid; // id ,      ,    id
	sgx_status_t ret = SGX_SUCCESS; //        
	sgx_launch_token_t token = { 0 }; //    
	int updated = 0; //      
	const int BUF_LEN = 30; //  ,     

	ret = sgx_create_enclave(ENCLAVE_FILE, SGX_DEBUG_FLAG, &token, &updated, &eid, NULL); //  

	if (ret != SGX_SUCCESS)
	{
		printf("Failed to create enclave with error number: %#x\n", ret); //  
		return 0;
	}
	char buf[BUF_LEN]; //  ,      

	enclaveChat(eid, buf, BUF_LEN); // ECALL  

	printf("\noutput form main(): %s\n", buf); //  
}


Enclave.edl:



enclave {
    from "sgx_tstdc.edl" import *;

    trusted {
        /* define ECALLs here. */
        public void enclaveChat([out, size=len] char* str, size_t len);
        /*   ,    . OUT -   ,   
                , out     .
           ,          ,
                 .
        */
    };

    untrusted {
        /* define OCALLs here. */
        void print_string([in, string] char* buf); //  ,     
    };
};


Enclave.cpp:



#include "Enclave_t.h"

#include "sgx_trts.h"
#include <cstring>

void enclaveChat(char* str, size_t len)
{
	char* secret = "Hello from better place"; //   

	memcpy(str, secret, len); //   ,  

	print_string(secret); // OCALL-  
}


Press f7 - build the solution, and then ctrl + f5 to run.



If you get an error like this:





make sure Intel SGX is enabled in BIOS: Bios: Security / IntelSGX / Enabled.



In case there were no errors, and in front of the screen on the console, you saw the following lines:





... congratulations, your first program with Intel SGX technology is ready. I hope the comments in the code were comprehensive for understanding, otherwise, you can always ask questions here in the comments or in private messages.



All Articles