summaryrefslogtreecommitdiff
path: root/chap/chap4.tex
blob: e2de2f7a50082b47973b9364672ccff3529a99ae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
\chapter{针对 Spectre 攻击的微架构设计}\label{sec:mywork}

本章讲解本文提出的一种防御 Spectre 攻击的方法,该方法使用动态信息流追
踪的方法,检测 Spectre 组件指令流中可能泄露秘密数据的访存指令,并使用
InvisiSpec 的方法执行这些访存指令。

%这个设计有如下特点:
%\begin{itemize}
%\item 它不需要任何软件修改
%\item 

\section{威胁模型}

本文对攻击者做如下假设:攻击者知道受害者程序的代码、进程中的地址分布信
息,攻击者和受害者可以在同一进程,共享同一处理器核,或在不同处理器核上。

本文设计的方法用于防御利用控制流推测式执行的 Spectre-PHT, Spectre-BTB,
Spectre-RSB 攻击,此方法可以扩展至 Spectre-STL 攻击。攻击者在 Spectre
攻击中使用高速缓存作为隐蔽信道,其他信道不在本文的考虑范围。本文不考虑
其他的侧信道攻击。本文针对攻击者通过 Spectre 攻击获取内存中秘密数据的
情形,不考虑攻击者通过攻击获取寄存器中秘密数据的情形。

\section{基于动态信息流追踪的Spectre检测方法}

动态信息流追踪(Dynamic Information Flow Tracking)\supercite{dift}是
一种硬件安全策略,通过识别可疑的信息流,并限制可疑信息的使用,保护程序
的安全。它最早用于防止攻击者利用缓冲区溢出攻击执行恶意代码,也可以用于
检测跨站脚本攻击、SQL注入等攻击。\supercite{raksha}

DIFT 可以作为 Spectre 攻击的检测手段之一。Spectre 的论文中指出处理器可
以追踪数据是否在推测式执行中获取,进而阻止在后续可能泄露这个数据的操作
中使用,作为阻止数据进入隐蔽信道的方法。\supercite{spectre}
CSF\supercite{context-sensitive-fencing} 中的译码级信息流追踪框架 DIFT,
用于追踪处理器使用的数据是否来源于用户输入,从而处理器可以根据此信息判
断是否需要插入 fence 微码。OISA\supercite{oisa} 在指令系统的定义中即包
含了 DIFT 技术,用于追踪一个数据是否为秘密数据。

在本文的工作中,DIFT 的作用是在运行时动态识别可能泄露秘密数据的指令。
以图\ref{lst:spectre_v1}的 Spectre v1 组件代码为例。攻击者希望通过 Spectre
攻击泄露 \verb|array1[x]| 的值,方法是让处理器在分支的推测式执行中,访问 \verb|array2[array1[x] * 4096]|,
从而将一个依赖于 \verb|array1[x]| 的地址处的数据写入了缓存中。使用了基于 DIFT 的检测手段后,
可以识别出 \verb|array2[array1[x] * 4096]| 的地址依赖于 \verb|array1[x]| 的值,
从而处理器可以阻止对这个地址的访问。

为了达到这个目的,我们为所有的物理寄存器都添加一个标记,用于表示它的值
是否来源于推测式执行中,从内存中读出的值。在上述例子中,有一条将内
存 \verb|array1[x]| 读取至寄存器的指令,该指令的目的寄存器的标记将会被
设为1. 

\begin{figure}
\begin{minted}[frame=single,linenos=true]{nasm}
  xor eax, eax
  cmp qword [rip + 0x2b157f], rdi
  jbe loc.funcret
  lea rax, [rip + 0x2b14ae]
  add rax, rdi
  movzx eax, byte [rax]
  shl eax, 12
  lea rdx, [rip + 0x2b425d]
  mov eax, dword [rdx + rax]
  loc.funcret: ret
\end{minted}
\caption{Spectre 组件的汇编代码}
\label{lst:spectre_v1_asm}
\end{figure}

上述 Spectre v1 的组件代码可以产生图\ref{lst:spectre_v1_asm}所示的指令。
我们用表\ref{tab:spectre_dift}展示 DIFT 在推测式执行分支中的指令的行为,
其中 T 表示寄存器的标记,在分支推测式执行前,所有寄存器的标记为0,为了
描述方便,这里用体系结构寄存器进行描述,实现中为物理寄存器的标记。

\begin{table}
\begin{tabular}{|c|c|c|}
\hline 
指令 & 指令的语义 & DIFT 行为\tabularnewline
\hline 
\hline 
lea rax, {[}rip + 0x2b14ae{]} & rax <- rip + 0x2b14ae & T{[}rax{]} <- T{[}rip{]} = 0\tabularnewline
\hline 
add rax, rdi & rax <- rax + rdi & T{[}rax{]} <- T{[}rax{]} | T{[}rdi{]} = 0\tabularnewline
\hline 
\multirow{2}{*}{movzx eax, byte {[}rax{]}} & \multirow{2}{*}{eax <- (uint8\_t){[}eax{]}} & T{[}rax{]} = 0, 指令安全\tabularnewline
\cline{3-3} 
 &  & T{[}rax{]} <- 1\tabularnewline
\hline 
shl eax, 12 & eax <- eax {*} 4096 & T{[}rax{]} <- T{[}rax{]} = 1\tabularnewline
\hline 
lea rdx, {[}rip + 0x2b425d{]} & rdx <- rip + 0x2b425d & T{[}rdx{]} <- T{[}rip{]} = 0\tabularnewline
\hline 
\multirow{2}{*}{eax, dword {[}rdx + rax{]}} & \multirow{2}{*}{eax <- {[}rdx + rax{]}} & (T{[}rdx{]} | T{[}rax{]}) = 1, 指令不安全 \tabularnewline
\cline{3-3} 
 &  & T{[}rax{]} <- 1\tabularnewline
\hline 
\end{tabular} 
\caption{分支中代码产生的 DIFT 行为}
\label{tab:spectre_dift}
\end{table}

\Todo: 解释为什么使用这种方法,和其他相似方法(DLIFT, TPBuf, SG(Full))的比较

\Todo: 增加结构示意图和代码描述

以下描述这个检测方法的具体细节。

标记的设置:在推测式执行时,对于所有从内存读取数据的指令,将其所有目的
寄存器的标记设为1.

标记的传播:标记的传播在处理器的执行阶段进行,在这个阶段,处理器在读源
寄存器的同时,将它们对应的标记读出。对于不同类型的指令,标记传播的方式
如下:

\begin{itemize}
\item 对于算术类指令,它的源操作数均来源于寄存器,其目的寄存器的标记设
  为所有源寄存器标记做或运算的结果。
\item 对于装载类指令,它从内存中读取数据,这是标记设置的基本条件,因此
  目的寄存器的标志设为1.
\item 对于存储类指令,它没有目的寄存器。和一般的 DIFT 实现不同,我们不
  对任何内存进行标记。因为要使用存入内存中的数据,需要有一套指令将其重
  新从内存中读出,这个操作会设置这条指令目的寄存器的标记。
\item 对于转移类指令,我们为每个指令添加一位标记,如果源寄存器中有标记
  为1的指令,则设置这个指令的标记为1,否则设置为0.转移指令的的标记将在
  下文描述。
\end{itemize}

标记的清除:当一条指令不再处于推测式执行的状态,即该指令此前的所有分支
都执行完成并通过验证,则要将这条指令所有目的寄存器的标记重置为0.对于转
移类指令,则清除该转移指令的标记。

寄存器中的标记可以处理读取内存中数据的指令,和泄露内存中数据的指令,存
在直接或间接数据相关的情形。读取内存的指令使得存放它的寄存器被标记,此
后和它存在直接或间接数据相关的指令,会将这个标记传播至这些指令的目的寄
存器。

考虑攻击者在 Spectre 攻击中利用缓存侧信道泄露内存中的数据,则
在 Spectre 的组件代码中,泄露数据的指令为装载指令,该装载指令的地址直接
或间接依赖于内存中的数据。在加入上述的 DIFT 检测机制后,由于该装载指令
依赖于内存中的数据,它必定有一个被标记的源寄存器,从而处理器可以得知该
指令不安全。

转移指令的标记用于处理可能泄露数据的指令,和内存中数据的值存在控制相关
的情形,如图\ref{lst:victim_v10}的例子\supercite{msvc}:

\begin{figure}[htbp]
\begin{minted}{C}[frame=single,linenos=true]
void victim(size_t x, uint8_t k) {
     if (x < array1_size) {
          if (array1[x] == k)
               temp &= array2[0];
     }
}
\end{minted}
\caption{泄露数据的指令和内存中的数据存在控制相关的情形}
\label{lst:victim_v10}
\end{figure}

这个例子读取的 \verb|array1[x]| 和攻击者猜测的值 \verb|k| 进行比较,如
果两个值相等,则会访问 \verb|array2[0]|,即访问 \verb|array2[0]| 的指令
和访问 \verb|array1[x]| 的指令存在控制相关,攻击者可以通过探
测\verb|array2[0]| 是否在缓存中,判断 \verb|array1[x]| 的值是否
和\verb|k| 相等。只使用寄存器的标记,并不能检测出读取 \verb|array2[0]|
的指令和 \verb|array1[x]| 的值存在依赖关系。

因此我们需要转移指令中的标记。如果一条指令在被标记的转移指令之后执行,
则该指令和一个被标记的数据存在依赖关系,如果指令为装载指令,由于它改变
了缓存的状态,攻击者可以通过缓存信道得知该指令被执行过,推测出被标记转
移指令的执行结果,从而推测出内存中秘密数据的值,因此这样的装载指令也是
不安全的指令。

\section{使用推测式执行缓冲区}

\Todo: 说明在检测到不安全的访存指令后,推迟它的执行和用 InvisiSpec 的 SpecBuf 执行的区别

\Todo: 为什么只对部分指令使用,对推测式执行中的部分指令使用 InvisiSpec 是否需要修改

\Todo: 本文使用的基于 DIFT 的检测方法和 InvisiSpec 结合

\section{针对 Spectre 攻击的微架构在 gem5 中的实现}

\Todo: 这节介绍怎样在 gem5 中实现我的工作

以下介绍这种可抵抗 Spectre 攻击的微架构在 gem5 模拟器中的实现。首先分
析 gem5 中乱序执行处理器的实现,然后分别介绍 InvisiSpec 和本文使用的
DIFT 方案在 gem5 中的实现。

\subsection{gem5 的乱序执行处理器}

\Todo: 做一个 gem5 流水线的示意图?

gem5 的乱序执行处理器实现在 FullO3CPU 类中,它又用类实现类处理器的以下
流水级:取指(Fetch)、译码(Decode)、重命名(Rename)、发射/执行/回
写(IEW)、提交(Commit)。

gem5 的取指和译码阶段由 DefaultFetch 和 DefaultDecode 两个类实现。在
DefaultFetch 中,取指部件从指令缓存中取出处理器 PC 位置的指令,并用指
令系统对应的译码器进行译码,再取出指令对应的微指令,将微指令传至译码阶
段,译码阶段再将其传到重命名阶段。取指阶段取出的指令在 DynInst 类的实
例中保存。

gem5 的重命名阶段由 DefaultRename 类实现,它对指令的源寄存器和目的寄存
器进行重命名。重命名后,指令的 DynInst 实例中的源寄存器和目的寄存器均
保存它们对应的物理寄存器,同时还保存目的寄存器原来对应的物理寄存器用于
恢复。

gem5 的发射、执行和回写三个阶段由一个类 DefaultIEW 实现,它模拟了处理
器将指令发射至功能单元和处理器执行指令的过程。gem5 中用一个专门的语言
定义了每个指令系统的指令的语义,为每个指令和微指令生成一个 StaticInst
类,里面定义了指令的执行方式。对于存储访问类指令,gem5 用 LSQ 类定义处
理器中的装载和存储指令队列,这些指令在执行时添加至队列中,进行存储访问
操作。

gem5 的提交阶段由 DefaultCommit 类实现,它提交 ROB 队列头部的指令,更
新 ROB 的状态。

\subsection{InvisiSpec的LSQ的实现}

\Todo: InvisiSpec 所用的 load 指令执行逻辑在 gem5 LSQ 中的实现

\subsection{推测式执行缓冲区的实现}

\Todo: InvisiSpec 的 SpecBuf 在 gem5 中的实现

\subsection{InvisiSpec对缓存一致性协议的修改}

\Todo: InvisiSpec 的 SpecBuf 在 gem5 中的实现

\subsection{动态信息流追踪的实现}

\Todo: 本文所用的 DIFT 方案在 gem5 中的实现,和已有 InvisiSpec 代码的结合

\Todo: 如何添加一个总结性的章节?