TrustZone:受信任的操作系统及其应用程序

在以前的文章中,我们研究了TrustZone硬件设备和安全监视器机制的操作。 今天,我们将专注于受信任的OS(TEE)及其应用程序。 如果上次有相当低的层次,那么现在一切都将处于很高的层次-操作系统的层次。

什么是TEE?


什么是TEE? 首先,这是受信任的执行环境(受信任的执行环境),这是程序的执行环境。 我们从功能和属性的角度来描述它,而不是从编程的意义上,而是从哲学的角度来描述。

例如,长途火车,火车和出租车具有最重要的功能之一-运送人员。 但是根据它们的属性,它们会有所不同,例如:火车在城市之间穿梭,城外有电动火车,出租车主要在城市内。 火车和火车票,出租车-不。 依此类推。

TEE功能是为我们安全地存储一些数据并为我们启动应用程序。 我们要传送TEE命令:启动此类应用程序,获取此类数据,并使用它们来执行此类操作。 同时,我们看不到应用程序代码和数据。 我们只会得到结果。 与TEE的交互与RPC非常相似。

此功能非常适合各种加密技术,例如电子签名:密钥存储在TEE中,我们要求TEE用存储在TEE中的密钥对传输的数据进行签名。 我们得到结果,但是没有访问密钥的权限。

TEE具有许多属性,但主要属性是:a)我们相信其实现,并且b)它与设备的主OS可靠地分离,受到保护,很难折断。 还有其他属性,但我们为此仅将其称为受信任的操作系统。 特性b)最重要的是,TEE是分离的且难以破坏,即受到保护。

如果从功能和属性的角度看待TEE,很显然TEE甚至与TrustZone无关。 TrustZone是将TEE与主(来宾)操作系统分离的方法之一。

TEE实施选项


如果TEE的主要属性是独立且难以破解,那么我们可以为实现TEE提供不同的选择:

  • 使用TrustZone-我们将TEE和主操作系统分离在同一处理器内核中。
  • 在芯片上系统内部的单独内核上运行TEE,并通过硬件接口与其进行通信。 某些专用处理器具有运行TEE的单独的受信任内核,但是,您不能在商店中购买它们。 但是您可以采用双核晶体,例如Cortex-A + Cortex-M0 / M4,并在Cortex-M TEE上运行它。
  • 在单独的芯片中运行TEE,并通过外部接口(例如SPI或SMbus)与其建立安全连接。 为了保护通信,请使用加密方法。
    与智能卡(例如,芯片上的塑料支付卡)建立连接时,将使用此方法。 从某种意义上说,TEE是在芯片中执行的,因为应我们的要求,它非常有信心地进行金融交易,存储数据等。
    在现代PC体系结构的TPM(受信任的平台模块)中使用了相同的方法。

我们将仅讨论TrustZone中TEE的实施,因为这是TEE实施的非常常见的版本。 但以上大部分内容通常适用于TEE。

TEE作为操作系统


在过去的文章中,我们一直将TEE称为受信任的OS,并说它非常类似于真实的操作系统。

不假装一般,我们说大部分TEE具有:
  • 应用程序和流程:TEE可以下载并执行应用程序;
  • 进程和内核内存分离:由MMU用于保护进程内存空间和保护TEE核心内存;
  • 线程,流程交互;
  • 数据存储。

例如,您可以提出更多的TEE截断版本,而无需动态应用程序加载,没有进程交互,没有线程,但是应用程序本身,数据存储以及进程内存和内核空间的分离将保留。
隐藏文字
现在可以在ARMv8-M平台上针对新一代Cortex-M微控制器的ARM Trusted Firmware-M项目中看到截断TEE的示例。 这是精简的TEE,现在在Cortex-M23和Cortex-M33内核上支持微控制器。 这些是基于闪存的微控制器,大致等效于Cortex-M0和Cortex-M3,但具有TrustZone支持。 它们的RAM很少,程序主要从Flash运行,因此在TEE中没有动态加载程序。 目前,TF-M也是单线程的。

TEE软件界面


为了与其他软件组件进行交互,TEE提供了一个API:

  • TEE通过系统调用(Supervisor调用,SVC命令)为程序提供API;
  • TEE通过调用安全监视器(SMC命令)为普通世界提供API。

通过系统调用,程序可以保存数据并调用OS功能。 像任何体面的OS一样,TEE尝试将程序从硬件抽象到一个程度。
例如,Linux抽象通过打开,读取,写入,关闭调用来处理文件-所有stdio函数基本上都属于OS系统调用。 TEE还允许其应用程序通过调用来处理存储的数据,这些调用将对象(数据块)抽象存储并加载到存储中。 TEE还可以在系统级别提供一些加密功能,等等。

有一系列针对TEE的GlobalPlatform规范,它们描述了API,要求,使用场景等。
TEE内部核心API规范中描述了其程序的核心TEE API。 它描述了数据存储功能,加密功能等。“ TEE客户端API”描述了如何从Normal World调用应用程序。

如果您的TEE实现了这些API,那么为其编写应用程序将非常容易。 由于有了一个API,程序的可移植性也得以实现。

TEE与常规操作系统之间的区别


TEE与Linux和我们熟悉的其他常见操作系统之间的两个主要区别是:

  1. TEE不在用户的命令上执行操作,而是在Normal World的命令上执行操作;
  2. TrustZone中的TEE没有自己的调度程序。

在常规OS中,用户生成一些输入-输入命令,单击图标,然后OS处理该输入,将其传输到程序,然后由程序处理。 在服务器版本中,输入不是来自用户,而是来自某些客户端(最有可能通过网络)。 但是,操作系统是基于外部输入来运行的。

TEE不会处理外部数据或将其传输到应用程序。 取而代之的是,它处理通过TEE Client API从Normal World传输的命令和数据,仅此而已。 事实证明,TEE为OS充当了具有RPC接口的某些库,该库的功能被调用。 处理完功能后,TEE可能什么也不做。

第二个区别是第一个。 受托人的TEE与Normal World共享CPU时间,被称为库。 TEE不会一直为自己分配处理器时间,它会花费所需的时间来完成请求,然后将控制权转移到Normal World。 如果是这样,那么她不应该拥有自己的调度程序-她需要客户机OS调度程序。

主OS调度程序将控制权间接传递给TEE:

  • 调度程序设置要完成的任务;
  • 任务调用内核系统调用;
  • 如有必要,系统调用将调用TEE;
  • TEE可以根据需要完成请求,并将控制权返回正常世界。

TEE应用


在TEE上运行的应用程序称为trustlet,类似于在智能卡上运行的applet。
引用维基百科:
Applet(来自应用程序-application和-let-小型后缀的英文applet)是在另一个全权重应用程序的上下文中工作的非自包含软件组件,设计用于一项狭窄的任务,并且没有与基础应用程序隔离的价值。

Trustlet是受信任的Applet。 正如我们已经发现的,这是一个TEE程序,它通过系统调用与TEE通信,具有生命周期等。

但是,该名称仍然表明它是一个非独立组件。 在此,独立性表示为这样一个事实,即trustlet将从Normal World发出呼叫,然后与TEE断开连接。 如果它旋转成无限循环,则处理器内核将停止充当OS,最终一切都将挂起。 但是常规OS的程序可能会陷入无尽的循环并挖掘一些任务,这对于程序来说是完全正常的。 在这方面,它独立于信任小程序。

trustlet必须具有某种标识符,以便Normal World可以调用它。 通常将信任集作为UUID(唯一标识符)提供。

Trustlet生命周期


考虑一下如何启动支架和执行命令。

将信任小程序加载到内存中并开始工作是合乎逻辑的,但是在GlobalPlatform TEE客户端API中,要启动信任小程序,您需要创建上下文并与信任小程序建立会话。

创建上下文是在正常世界和TEE之间建立联系。 在这种情况下,GlobalPlatform规范假定该设备可以具有多个TEE,并且在创建上下文时,您可以选择要联系的TEE。

在GlobalPlatform TEE客户端API中,为此提供了一个功能:

 TEEC_Result TEEC_InitializeContext(常量字符*名称,TEEC_Context *上下文)

从“正常世界”应用程序中调用此函数。 这里的名称表示可选的TEE。 如果默认情况下需要TEE或确定只有一个TEE,则将其替换为NULL。 在上下文中,将保存创建的上下文。

创建上下文后,您需要与信任建立会话。 在这里,trustlet的UUID对我们有用。 为此,该函数称为:

 TEEC_Result TEEC_OpenSession(
	 TEEC_Context *上下文,TEEC_Session *会话,
	 const TEEC_UUID * destination,uint32_t connectionMethod,
	 const void * connectionData,TEEC_Operation *操作,
	 uint32_t * returnOrigin)

会话等效于在常规操作系统中使用程序实例:操作系统中可能有多个同一个程序实例,并且它们将独立工作。 但是,TEE中有许多会话,从本质上讲,它们是到内存中trustlet唯一实例的连接。 在这种情况下,代码区域很可能是相同的,通过MMU映射到不同进程的存储器。 但是每个进程都有自己的数据区,从而允许实例独立工作。 就像在Linux上一样。

调用TEEC_OpenSession时,将传输目标信任的上下文和UUID作为输入。 建立的会话将保存在“会话”中。 在下文中我们将不考虑某些参数,它们对于理解并不是那么重要。

在创建会话时,可将trustlet加载到内存中。 这就是操作系统上的应用程序发生的情况。 在大型TEE中,链接器对此负责,它下载trustlet的二进制映像,这是一个经过签名的ELF文件。 如果这是一个小TEE,则应将trustlet加载到内存中-可以将它静态链接,或者对于Flash微控制器,可以将其写入指定地址的Flash中。

假设我们有一个较大的TEE,并且需要将trustlet加载到内存中。 他来自哪里? 原则上,TEE在加载时需要具有特定UUID的对象,并且获取该对象的机制可以是以下任何一种:

  • 该对象可能已经在内存中;
  • 可以将对象静态放置在闪存中(用于闪存微控制器);
  • 该对象可以与TEE静态链接-对于系统trustlet;
  • 最后,您可以从文件系统甚至通过网络将文件下载到RAM。

稍后问自己,此TEE如何从文件系统或网络下载数据?

在下载trustlet的图像后,将验证其数字签名。 使用证书系统,TEE将验证该信任是否由TEE信任的一方签署。 这非常重要,因为它消除了下载带有某些恶意软件的欺骗性trustlet的可能性。

接收到trustlet映像并验证签名后,TEE将为MMU中的trustlet实例创建地址空间,并且链接程序将代码区加载到内存中,将其映射到trustlet的地址空间并初始化数据区。 结果是完全初始化的trustlet实例,用于与特定的调用应用程序一起工作-这是会话的创建。

创建会话后,信任密钥已完全准备就绪,可以执行来自调用应用程序的请求。 为了从OS调用trustlet函数,使用了该函数:

 TEEC_Result TEEC_InvokeCommand(
	 TEEC_Session *会话,
	 uint32_t commandID,
	 TEEC_Operation *操作,
	 uint32_t * returnOrigin) 

在这里,“会话”表示我们的会话,即我们正在使用的TEE实例和trustlet实例。

“ CommandID”指示信任小程序的被调用功能。 这是trustlet函数,而不是TEE函数。 TEE唯一关心的就是启动trustlet并发送命令,分配给与trustlet通信的commandID号完全由您决定,没有规则或函数的全局列表。

如果您需要将参数传递给被调用的函数,它们将通过操作传递-这是指向TEEC_Operation结构的指针。 现在我们不会太深入,只需注意此结构最多包含4个函数参数(类型TEEC_Parameter)即可。 参数可以是简单的TEEC_Value或指向内存的指针。 参数在以下方向上也有代表:TEEC_VALUE_INPUT(输入),TEEC_VALUE_OUTPUT(输出)或TEEC_VALUE_INOUT(双向)。

如果将指针传递给TEEC_Operation结构,则必须首先对其进行初始化:设置所有值和方向。 调用完成后,我们可以检查此结构中的返回值(对于TEEC_VALUE_OUTPUT和TEEC_VALUE_INOUT)。

在会话期间,我们可以根据需要多次调用trustlet函数。 在工作结束时,您将需要结束会话并通过调用TEEC_CloseSession和TEEC_FinalizeContext释放上下文。

这一切都让人想起RPC,对吧? 原则上,所有使用TEE的操作都设计为RPC,因此,您可以使用多种TEE实现:在TrustZone中,在单独的内核中,在单独的芯片中。

恳求者


上面,我们问自己:TEE如何从文件系统或网络下载数据?
如果您考虑一下,TEE本身无权访问OS文件系统。 也就是说,在TrustZone中实现的TEE可以具有这种访问权限,但随后必须与Normal World共享它,这并不是那么简单。 例如,Linux一直在使用文件系统,并且其当前状态仅在Linux内核内存中,而不在磁盘上。 如果TEE想要并行干预和使用文件系统,这将非常困难。 与网络共享相同。

此外,TEE是一个相当小的操作系统,并且实现用于与媒体一起工作,与网络控制器一起使用并支持网络堆栈或FS驱动程序的低级驱动程序将无济于事。 另外,这大大增加了攻击面-可能会通过在ext2或类似的东西上滑动异常的inode来破解TEE。 我们不想要那样。
因此,当操作系统启动时,将加载所谓的请求方-辅助程序。 它始终连接到TEE,并且TEE使用它来访问“普通世界”资源。

因此,如果TEE要从文件系统下载trustlet映像,它将调用Supplicant:

TEE:具有这样的UUID的对象呢?
请求者:(从文件系统中加载对象)对不起,先生!

当然,应检查此类呼叫的安全性。 在这种情况下,我们将验证Trustlet中的签名,并且几乎没有风险-签名正确且Trustlet可以正常工作,或者签名不正确。 就是说,我们要冒这个风险-可能没有Trustlet,可能没有发起Supplicant,但这是威胁模型的另一部分。

用户空间库


程序接口(对TEEC_OpenSession的调用等)是使用库实现的,该库将调用从应用程序级别传输到TEE。

为此,在TrustZone中实现TEE时,该库必须首先将调用转移到OS内核级别,因为只有OS内核才能调用SMC(安全监视器调用)。
在Linux + OP-TEE软件包中,用户空间库为libteec。 它通过设备文件上的ioctl操作将GlobalPlatform TEE客户端API的调用转换为内核驱动程序:当操作系统启动时,内核模块(驱动程序)已加载,驱动程序创建了设备文件。 通过使用libteec打开设备文件,用户程序可以使用TEE客户端API。

也就是说,此设计有效:
应用程序> libteec>设备文件>内核驱动程序> SMC> TEE>信任。

Trustlet的示例


这是在实际应用程序中的工作方式:
图片
此处,trustlet用于对文档进行电子签名。 Linux上的程序调用了trustlet,为此目的创建了TEE上下文,与trustlet的会话,用于签名的数据被传输以及电子签名被返回。

结论


在本文中,我们弄清楚了什么是TEE和trustlet。 我们遇到了TEE API,并了解了如何调用trustlet。

我们特意抛开了许多事情,例如使用共享内存和编写trastlet,因为本文并不伪装成详尽的指南。

如果您对TEE主题感兴趣,那么请继续自己进行研究:您可以从研究GlobalPlatform规范或探索OP-TEE开始。 您也可以将标有“ TrustZone”的简历发送给我们。

Source: https://habr.com/ru/post/zh-CN428311/


All Articles