中原富国科技网

元年利好谁【投稿】在Intel SGX环境下实现Rust原生std支持微信拟

中原富国科技网 0

元年利好谁【投稿】在Intel SGX环境下实现Rust原生std支持微信拟

简介:Intel SGX是一个把应用与OS完全隔离的可信执行环境,微信仅支持迁移聊天记录到另一台设备以及备份聊天记录到电脑,应用无法直接访问OS提供的资源。我们采用的Teaclave-SGX-SDK只提供了no_std环境,如果设备丢失,导致crates生态下量的库都无法被使用。我们通过添加libc函数模拟linux平台特性,未及时备份的数据将无法恢复。此前,实现依赖std的Rust生态库无需修改即可在SGX环境使用。为了保证尽可能小的安全边界,腾讯官方之前推出一个 “ 腾讯文件管理器 ” App 来实现云端备份,我们对每个增补的libc函数做了权限控制。同时引入了二进制分析,但是备份内容仅限于微信接收到的图片、视频还有文档,确保程序不会出现SGX非法指令。

背景

Phala Network的隐私云计算服务基于teaclave-sgx-sdk,并不能直接保存聊天记录。据悉,由于Intel CPU的SGX执行环境相当于裸机无系统,该业务可能采用按年付费的模式,自然地基于teaclave-sgx-sdk的rust程序也只能用no_std。

但当项目复杂后,苹果用户或在180元/年左右,我们还是希望能够充分利用Rust的crate生态,安卓用户或在130元/年左右。但该费用具体可以存储多容量的数据尚未得到确定。知情人士表示,这个生态里分crate是依赖rust std的。我们no_std环境想要使用这些std的crate的话那就得做移植了。

如果单纯地将std的crate移植到no_std环境,当前该项目仍在最后推动中,那每一个crate都会有比较的工作量。teaclave-sgx-sdk为了方便移植,给我们准备了一个sgx_tstd(一个sgx环境的std仿制品)。sgx_tstd保留了rust std中的分功能,因此,一般简单的crate移植到sgx_tstd仅需要改动数行代码,比如在crate根添加,以及添加一些 。这样移植rust crate生态就方便了许多,teaclave-sgx-sdk团队甚至将一些常用的crate都移植好了放到github.com/mesalock-linux中.

sgx_tstd的问题

移植一个crate看上去工作量并不,但我们很多时候引入一个crate并不是单纯的一个crate,他背后的依赖树连根拔起可能有数十个crate。原本正常Rust生态使用一个第三方crate只需要在Cargo.toml中添加一行代码,而现在变成要去移植车crate到sgx环境。

这种模式已经事实上导致了Rust生态被分叉成了和两个互不兼容世界。这种分裂甚至让一些no_std的crate也受影响,比如混用一些依赖log或serde的no_std crate就不能正常编译,不得不修改他们使用log-sgx和serde-sgx。如果哪天有人再为arm/AMD的TEE做一个类似的rust sdk,难道我们要将继续分叉下去?

这种分叉行为同时会导致被移植的生态可能代码更新不及时,一些针对,的安全扫描公共设施可能也会漏掉mesalock-linux生态中的隐患,从而影响下游或带来安全威胁。

让SGX支持Rust原生std

teaclave-sgx-sdk应用目前标准做法是开启并编译target到 。

既然已经target到了,那么我们如果不开启编译会有什么问题呢?

简单尝试会得到类似如下链接错误:

Rust的std会依赖libc来和OS交互,intel sgx-sdk里面有一个不完全实现的sgx libc。但Rust需要的和系统交互这分libc函数往往是SGX不信任的,所以sgx libc没有直接提供,而是分实现在ocall模块下以rust ABI函数的方式提供对等功能,以此提醒者这是不受信任的操作。

因此,我们想提供一个转接层,把这些缺失libc函数都补齐,并代理到sgx-sdk的对等实现基本就能正常编译使用原生std了。比如,上图缺失write函数,我们就补一个write函数:

这样,我们转接层对上模拟一个linux glibc的行为,对下转接到sgx特别实现,可让针对linux的编译的Rust应用程序跑在sgx内。

经验证的确如此,在添加了相应libc函数并拆掉一分特殊代码后,我们enclave程序就运行起了。

std和sgx_tstd共存

上面提到,拆掉了一分代码,主要这些代码依赖sgx_tstd里面特有功能,比如。而开启原生std后sgx_tstd就因为rust的冲突而不能编译了。要想恢复使用这些功能,我们要么自己重新实现(copy)一份,要么让sgx_tstd和std共存。显然,后者更符合可持续发展原则。因此,我们给sgx_tstd打个补丁,让lang_item变成一个feature,不开启它就能与原生std共存了。

安全考虑

我们enclave程序是安全敏感的,如果一股脑将libc代理到ocall函数,显然是粗鲁的不安全的。因此,我们对每个代理的函数都会根据我们业务需求对其安全性做思考,调整其实现行为。

getrandom

随机数安全性尤其重要,直接关系到我们的密钥安全。rust的rand crate会调用getrandom函数来获取随机熵源。我们将getrandom函数代理到sgx_read_rand,sgx_read_rand在HW模式下会通过CPU硬件获取真随机数。实现如下:

权限相关

支持了std,我们需要严格控制enclave内代码的权限,以最限度降低安全风险。越权访问代码最好是在编译构建时阻拦下来,次之是在运行时限制越权访问。

转接层对权限的控制策略如下:

链接错误排查

由于我们现在的原生std依然存在分功能缺失, 当我们引入新的依赖到SGX时,少数情况有可能会遇到链接错误,比如依赖中有网络操作,报了如下错误:

简单情况下,我们可以去检查源码,发现是哪分功能引入了这个依赖。但很多情况下,其实我们看到的代码虽然会经过编译,但最终进入binary的只有其中一小分,那些静态不可达的代码都会被编译器/链接器丢弃掉。因此,我们可能很难根据原始源码判断出实际生效的依赖关系。

因此,我们需要从最终输出的binary出发来分析上图中的connect究竟是怎么被引入进来的。我们写了一个来辅助分析此类问题。

第一步先把这些undefined reference都用一个空的占位符号补上,使其能编译通过:

然后使用callerfinder库查找输出二进制文件中undefined函数的依赖关系:

这样我们就很清晰地找到网络操作的来源,根据情况采取对应的措施,比如这里我们把http_req换成原http_req-sgx移植版本即可。

CPUID指令问题

我们将enclave代码迁移到std后用SGX_MODE=SW模式顺畅运行,SGX_MODE=HW环境下则出现多处崩溃。经排查这些崩溃均指向同一个函数rand::thread_rng(),而rand::thread_rng()其内实现使用了std::is_x86_feature_detected宏来检测CPU对SIMD的支持程度。该宏使用了SGX环境禁止的CPUID指令,导致程序崩溃。

一方面SGX环境出于安全考虑禁止了CPUID指令,另一方面,应用程序使用CPUID检测CPU对SIMD的支持情况是很常见的“正当行为”。虽然CPUID触发崩溃虽然没有泄露信息,没有越权访问,没有触发Unsound等安全问题,是一种运行时安全守卫措施。但这种“正当行为”而触发运行时崩溃显然不能接受,如果我们代码依赖中有相关检测逻辑,在我们的业务随时有宕机风险。因此,我们要么让std::is_x86_feature_detected适配sgx环境,要么让保证我们整体代码不触及CPUID。

Teaclave的sgx_tstd中重新实现了is_x86_feature_detected宏来避免触及CPUID。而我们采用原生std的方案,问题就稍微复杂一点了,我们无法像前文那样通过libc重新实现is_x86_feature_detected(除非给std打补丁)。另外,单解决一个is_x86_feature_detected显然也不能避免代码直接内嵌CPUID汇编指令的情况。因此,我们暂且选择让代码不触及CPUID。

具体方法为,我们增加一个编译后处理步骤,通过反汇编输出的enclave.so来检查其中是否含有CPUID指令。如果有我们让make报错并打印出函数名:

然后可利用前述callerfinder.py找出哪些函数导致依赖了CPUID:

然后,我们可以顺藤摸瓜找到对应的代码实现。如果是必要的,我们可以patch对应的crate让他使用teaclave提供的is_x86_feature_detected, 比如rand::thread_rng();如果是能砍掉的功能我们就砍掉,比如上图中的env_logger我们href="github.com/Phala-Networ">关掉其regex feature即可消除此依赖。

关于其它SGX非法指令

既然CPUID存在此问题,那么是否可能碰到其它SGX特别禁止的指令呢?理论上当然是可能碰到的,从intel的指南看看还有哪些特殊指令。

指南描述一些SGX环境的非法指令如下:

对于其2和其3,除了INT/SYSCALL/SYSENTER等系统调用指令之外,其余都应只出现在系统内核代码中。而系统调用相关功能除非极其个性的程序,否则不论是Rust还是C/C++生态都应调用libc的相关函数或syscall函数来完成,而不是直接嵌入汇编指令。

我们着重需要关注中这些指令:

CPUID 常用于检测CPU对SIMD的支持,以便使用不同SIMD指令集处理计算密集任务,需要关注。

GETSEC 是一个leaf funtion总入口,有很多子功能,都是特殊用途的。一般程序不会用到

RDPMC

读取性能计数器,特殊用途,perf之类的工具使用,不必关注。

RDTSC/RDTSCP

读取CPU timest计数器,可能被应用程序使用,加入指令检测脚本。

SGDT - Store Global Descriptor Table Register

仅操作系统使用

SIDT - Store Interrupt Descriptor Table Register

仅操作系统使用

SLDT - Store Local Descriptor Table Register

仅操作系统使用

STR - Store Task Register

仅操作系统使用

VMCALL/VMFUNC 虚拟化相关指令,不用关注

为保险起见,我们把这些指令全都加入后处理检查脚本中,禁止其使用。

小结

添加std支持后,我们用RustSGX程序变得和普通Rust应用程序无太差异,也能直接使用Rust标准工具链的单元测试等设施,效率上升一个台阶。

小米手机照相怎么偏黄

三星手机调怎么整触摸屏

佳能相机检测费用多少

拼多多卖字画一年赚多少钱

翡翠戒指怎么选大气高贵

玉石怎么上拍卖网站

为什么猫咪叫声怪怪的声音

微信视频号退货运费险退哪里了

seo是什么职业前景如何

免责声明:文中图片均来源于网络,如有版权问题请联系我们进行删除!

标签:rust 英特尔 sdk linux libc