武大信安在读,最近在自学Risc-v架构的可信执行环境。

(实验报告多半是为了交差。临时起意写写博客,分享一些自己读代码的心得理解。)

本篇内容由队和我友总结而成,如有错误欢迎指正交流。

keystone是risc-v架构的开源tee。

利用risc-v的pmp来隔离页表,进一步缩小了可信基。

runtime和sm的解耦也很有意思:

可以近似理解为:

  将安全功能集中在sm中,作为保安。

  runtime则提供edge call等各种与安全关系不大的服务,可以理解为保姆。

目录

一、Keystone框架及enclave运行过程:

    其他感觉比较重要的函数

二、runtime和sdk的工作原理:

    调用、响应路线

    runtime中其他零零碎碎的东西


开始正文:

一、Keystone框架及enclave运行过程

  keystone的框架图如上所示

所以host端(图中的untrusted)需要调用keystone相关服务的时候,需要从U模式OS层,再从OS到SM层,然后SM调用opensbi接口完成针对寄存器的修改等操作。

在代码结构上就是

  sdk -> linux_kernel_driver -> sm -> opensbi

以host的enclave.run操作为例,从顶层往底层查看调用过程:

    首先在host端调用enclave.run方法:

  这个函数最终的效果应该是将程序执行流从host端转向eapp,并且保存寄存器组,修改寄存器组到eapp对应的值

host.cpp:

这个函数最终调用了pDevce的run方法:

  里面利用了Ioctl函数,和linux驱动层进行通信,将操作码request传到驱动层。

自此程序流进行到了OS驱动层。

OS驱动层在启动之前进行init初始化:

  之后用ioctl进行的通信,会转到注册的keystone_ioctl函数内部:

经过switch对cmd的分类,进入这个分支:

  这个函数内部,完成了对参数的保存和检查,之后进入sbi_sm_run_enclave函数内

这个函数最终调用sbi_ecall:

  sbi_ecall应该是用下述结构体传递,因为extid具体数据一致,应该是根据这一项进行识别

(注:猜测这个sbi_ecall函数应该是一个OpenSBI库函数,第一个参数代表的是底层实际代码ecall异常调用的a7的值,在riscv里面,约定用a7传递异常类型,之后sm通过这个a7去分配异常处理函数。)

  对应的注册函数如下:

    在sm初始化的时候完成

  之后sbi_ecall会进入到sm层:

这个sbi_sm_run_enclave:

  1. run_enclave中,完成以下操作:

    1.1修改寄存器组的值,对应需要run的那个enclave,并且把当前的寄存器组的值保存起来

    1.2翻转pmp的权限。

个人理解:对于host端来说高权限的pmp条目,对于eapp端来说就应该是低权限度。

例如eapp应该拥有对于自己的enclave的所有权限,但是os对其应该没有权限。

而host端而言,os应该拥有所有权限。

参考:http://docs.keystone-enclave.org/en/latest/Security-Monitor/index.html#pmp-internals

    1.3保存一些信息,用于之后的一些操作,例如检查之类的。比如保存当前的hart(硬件线程)对应的eid,以及是否在enclave中,用于之后的操作。

  2.sbi_trap_exit:

    这函数调用了opensbi的接口,功能是执行中断,并且重新加载寄存器组regs。

    因为在之前的函数中修改了寄存器组regs,配套到了eapp,所以执行完这个之后,执行流就到了eapp当中。

自此enclave.run()操作结束。

其他的enclave操作,比如enclave.init()等调用环节类似。功能上有一些异同。


其他感觉比较重要的函数:

/sm/pmp.c

参数:1.region_idx为之前通过需要配置的enclave的内存大小,提前存储下来的数据。

先mark一下Pmp机制的工作原理:

参考:https://zhuanlan.zhihu.com/p/139695407

pmp机制通过Pmp地址寄存器和Pmp配置寄存器共同配置。

PMP配置寄存器一方面决定了这个PMP条目下的权限,是否可读,可写,可执行,一方面决定了地址寄存器决定地址的方式。共有TOR,NA4,NAPOT,3种不同方式。

具体形式如下图所示

所以根据这两个寄存器可以共同决定一个PMP条目决定的地址空间和所具有的权限。

pmp_set_keystone()函数实现了两个事情:

  1.根据传入的region_idx对应的 pmp_region对应结构体的信息,计算需要写入PMP条目的PMP配置寄存器和PMP地址寄存器的值。

  2.判断是否需要多个PMP条目来共同写这一个地址空间。

  之后利用PMP_SET宏调用来写寄存器,这个PMP_SET宏内部展开之后是OPENSBI的接口和RISCV的内联汇编,用于写RISCV的状态寄存器。。


二、runtime和sdk的工作原理

edge_common封装了边缘调用的格式:

每次调用都用一个结构体,规定size来来限制访问权限。edge data和ret data都一样,是  指针 + size的形式。

参数用偏移量来寻找。

返回的数据单独定义一个结构体。

edge_call.c封装了syscall的io格式、边缘检查。每次edge call都需要检查指针有效性,在共享内存区中找到对应的结构体,来完成edge call setup call。

runtime中的syscall 依托上述edge_call实现,设计原则:

(参考:https://rmheng.github.io/2021/01/29/keystone-2020/)

runtime可以理解为负责为eapp提供与安全无关的服务的一个代理。因为只是将调用请求进行检查、封装,再交给sbi用ecall汇编处理,所以说是代理。

(handler syscall 用了pk的接口。不在keystone的范畴。)

syscall依靠edge_call实现:

    dispatch edgecall ocall

    dispatch edgecall syscall

  分别完成ocall和syscall的调用,共同的大致流程:

    在shared mem的位置个edge call结构。在shared mem中取地址,找到结构体的指针。赋值call id,拷贝call data等内存。

  边缘检查的过程在产生指针时进行。

调用、响应路线:

调用时:

  eapp发起syscall或ocall。

  syscall:

    被io_wrap封装成如下系统调用:

eg:

代理过程就是:在io_wrap中用dispatch_edgecall_syscall函数进行派遣。

  dispatch_edgecall_syscall具体工作:

    set up call,把共享内存区的一个指针变成一个安全可用的edge call结构体,赋值其中数据。

    派遣结果ret就是eapp想要知道的系统调用的返回值。

ocall在handle_syscall中经过pk被派遣出去:

(handle_syscall这个函数在pk里被调用,pk暂时还没研究)

dispatch_edgecall_ocall比syscall多一个拷贝用户内存的过程。

底层通过操作csr寄存器来实现,还没看。

响应时:

syscall:

  由sdk完成对调用的响应。

    每个enclave创建时都要把incoming dispatch注册到oFuncDispatch,意思就是,为即将到来的edge call留一个指针,到时候遇到边缘调用或者中断,就通过这个函数来响应。这里的函数,参数都是指针,所以响应时要通过call id来获悉自己要做什么事情。

  syscall dispatch.c文件中的incoming call dispatch进行检查:

  检查call id。判断是syscall 还是ocall还是无效。有效的call id需要在edge call table中注册。这里的buffer是edge call结构体的指针,可以理解为一个函数指针。

enclave中的registerOcallDispatch:

  把一个函数指针赋值给oFuncDispatch,后续在run的时候,会调用oFuncDispatch,这个函数的参数是个指针。

host处,进行赋值,将edge call绑定到一个指针上:

  把上面讲过的incoming_call_dispatch赋值给oFuncDispatch,该函数会解析指针处的内存,获得call id判断是syscall、ocall还是badcall,并进行相应处理。

enclave::run的定义:

  enclave的运行过程。error是个枚举结构。代表run的不同结果。

  运行交给pDevice后,host挂起。检测到edge call host或者发生中断后进入while循环。接着进入if语句,判断该调用是否安全。

enclave中的run函数,调用了oFuncDispatch,这东西就是刚才的edge call的指针,运行了这个edge call,就完成对call的响应:

  edge call会自己把返回值封装为结构体,写进共享内存区。不用在这里return。

  处理结束后,通过resume函数,将控制权还给enclave。eapp继续运行。

ocall要注册:

把函数指针写到edge call table里

响应流程还是经过incoming_call_dispatch:

  判断为用户注册过的edge call之后就用edge call table表里的函数指针运行,buffer同样是共享内存区的指针,指向了edge call。

  edge call会自己把返回值封装为结构体,写进共享内存区。不用在这里return。

runtime中其他零零碎碎的东西:

  interrupt:

    支持时钟中断:

linux_wrap封装了支持的linux系统调用:

这些函数会输出syscall的结果,例如:

sbi.c

sbi.h

  封装了最底层的sbi操作,通过ecall修改csr完成各种异常处理。

page_swap.c:

  封装了调页操作:

  如果定义了页表加密,就会用aes256加密页表。

  如果定义了页表哈希,就会用merkle树检验页表是否被非法改动过。用到的哈希算法是sha256

  两种密码学算法都在runtime文件夹中有c语言实现。

mm.c

mm.h

是内存管理

内容很多,但还是能看个大概的。具体用到了在细说吧。

vm.c

vm.h

实现虚拟地址和物理地址的转换

paging.c

paging.h

段页式管理的实现,看起来还更吃力一些,因为有时候看不懂函数名称。= =

freemem.c

freemem.h

free内存的函数,spa是simple page allocator 。没仔细看,但是代码可读性比较高,和之前看过的free实现比较类似。

再就没什么主要的文件了,runtime的大致结构就是这些。

感谢阅读  ̄▽ ̄ 欢迎交流!

第一次写博客,写的跟实验报告似的,比较简陋,见谅哈

2021-05-17

未经允许,禁止转载 !


【tee小白的第一篇随笔】keystone代码略读的更多相关文章

  1. linux-0.11分析:boot文件 bootsect.s 第一篇随笔

    boot文件 bootsect.s 第一篇随笔 参考 [github这个博主的][ https://github.com/sunym1993/flash-linux0.11-talk ] bootse ...

  2. 入住cnblogs第一篇随笔 Hello, world!

    在网上搜索计算机参考资料时经常看到各位大神的博客,甚是神往.今天我也在这里安家,记录自己的学习过程,也同各位共勉. 第一篇随笔,就用来测试一下这里的文本编辑器吧. //The C language # ...

  3. 第一篇随笔 - Hello world!

    第一篇随笔 - Hello world! 第一篇随笔 - Hello world! 第一篇随笔 - Hello world! 第一篇随笔 - Hello world! 第一篇随笔 - Hello wo ...

  4. Hello World ! 第一篇随笔

    Hello World ! 第一篇随笔 /* * Language: C++ * Code Name: Hello World ! * @author Metak */ #include <io ...

  5. .Net小白的第一篇博文

    说起来也比较惭愧,5个月之前,我早已创建了博客园账号,那时候的我雄心壮志,给自己定下了 很多目标.现在回想起来,除了体重的增长,头发的稀疏,似乎这段时间的消逝并没有带给我什么见识上的成长.哈哈,想必大 ...

  6. 新年伊始,.net菜鸟入院的第一篇随笔

    学习.net有半年了,大二一年都是微软校园的负责人,但是因为根本没有系统的学习过编程的知识,所以一直都是活动负责人的身份,忙忙碌碌也没有什么收获,大三一狠心就退了,想能够踏踏实实的敲敲代码,手上的学习 ...

  7. .Net小白的第一篇博客

    2016年8月8日,经过了一周的纠结.我决定放弃了,原来学了六年并且工作两年的学前教育,走上了开发的道路.我认为生活就应该这样,就应该充满挑战,而不是每天在重复相同的工作!作为插班生的我,于2016年 ...

  8. 第一篇随笔:用VB.NET搞点简单事情(1)

    网络上能搜索到的爬虫文章大多是用python做的,也有少部分是C#做的(小声:所以用VB.NET也可以做爬虫.本文写的是第一步:获取网页) 使用代码前先imports以下内容 Imports Syst ...

  9. 第一篇随笔, 正在做 ESP32 , STM32 , 树莓派 RaspberryPi 的创客工具

    先随便写写一些思路, 以后再整理. 这段时间笔者做了一些硬件开发, 领悟了一些事情. 1 - 在常规创客的角度上, 硬件开发所需的知识面比较广, 非常广, 但不算太深. 2 - 发现硬件开发由于其特殊 ...

随机推荐

  1. P1149_火柴棒等式(JAVA语言)

    题目描述 给你n根火柴棍,你可以拼出多少个形如"A+B=C"的等式?等式中的A.B.C是用火柴棍拼出的整数(若该数非零,则最高位不能是0).用火柴棍拼数字0-90−9的拼法如图所示 ...

  2. 攻防世界 reverse re4-unvm-me

    re4-unvm-me alexctf-2017 pyc文件,祭出大杀器EasyPythonDecompiler.exe 得到源代码: 1 # Embedded file name: unvm_me. ...

  3. C语言之简易了解程序环境

    C语言之简易了解程序环境 大纲: 程序的翻译环境 预编译 编译 汇编 链接 程序的运行环境 在ANSI C的任何一种实现中,存在两个不同的环境. 第1种是翻译环境,在这个环境中源代码被转换为可执行的机 ...

  4. Java学习笔记--异常机制

    简介 在实际的程序运行过程中,用户并不一定完全按照程序员的所写的逻辑去执行程序,例如写的某个模块,要求输入数字,而用户却在键盘上输入字符串:要求打开某个文件,但是文件不存在或者格式不对:或者程序运行时 ...

  5. [2020年10月28日普级组]1405.小B浇花

    区 间 和 的 和 区间和的和 区间和的和 题目解析 就直接模拟,从最低的花的高度向最高的花的高度枚举,如果当循环变量的值到达了顶峰,但还有花的数量大于2的,就把循环上线加一(所以数组要开大些) Co ...

  6. 【源码解析】- ArrayList源码解析,绝对详细

    ArrayList源码解析 简介 ArrayList是Java集合框架中非常常用的一种数据结构.继承自AbstractList,实现了List接口.底层基于数组来实现动态容量大小的控制,允许null值 ...

  7. 【剑指offer】9:变态跳台阶

    题目描述: 一只青蛙一次可以跳上1级台阶,也可以跳上2级--它也可以跳上n级.求该青蛙跳上一个n级的台阶总共有多少种跳法. 解题思路: 先考虑最简单情况就是只有一级台阶,仅有一种跳法.两级台阶,有两种 ...

  8. idea无法引入自己定义的包和类

    方法一:通过清理缓存解决: File -> Invalidate Caches / Restart...,在新窗口点击Invalidte and Restart,未奏效 方法二:导入依赖 如图, ...

  9. 数据结构☞二叉搜索树BST

    二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它可以是一棵空树,也可以是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值: 若它 ...

  10. 详细Tomcat安装及问题排查

    一.安装 1.下载官网:https://tomcat.apache.org/ 2.将下载后的包解压到目录中会出现以下页面 3.设置环境变量,向path中添加tomcat的bin目录地址 4.cmd进入 ...