-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
515 lines (305 loc) · 331 KB
/
atom.xml
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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>rww's Blog</title>
<icon>https://www.gravatar.com/avatar/252dd8f805587344d8d0dd0919c6d079</icon>
<subtitle>半夏花已开,未来诚可期</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://yoursite.com/"/>
<updated>2022-02-06T11:54:01.879Z</updated>
<id>http://yoursite.com/</id>
<author>
<name>rmheng</name>
<email>[email protected]</email>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>enarx</title>
<link href="http://yoursite.com/2022/02/06/2022-02-06-enarx%20/"/>
<id>http://yoursite.com/2022/02/06/2022-02-06-enarx /</id>
<published>2022-02-06T11:13:54.000Z</published>
<updated>2022-02-06T11:54:01.879Z</updated>
<content type="html"><![CDATA[<p><a href="https://github.com/enarx/enarx" target="_blank" rel="noopener">enarx</a> 的阅读理解。基于 <a href>enarx</a></p><a id="more"></a><p>官方比较详细的 slides:<a href="https://github.com/enarx/enarx/wiki/In-depth-Project-Presentation-(slides)" target="_blank" rel="noopener">https://github.com/enarx/enarx/wiki/In-depth-Project-Presentation-(slides)</a></p><p>enarx运行需求(二选一)</p><ul><li>主机支持 SGX,有 /dev/sgx_enclave 驱动</li><li>主机主持 SEV,有 /dev/sev_snp 驱动</li></ul><h1 id="enarx-入口"><a href="#enarx-入口" class="headerlink" title="enarx 入口"></a>enarx 入口</h1><p>用户在终端执行 <code>enarx run --backend=sgx target/wasm32-wasi/release/hello-world.wasm</code> 时,程序会进入到 <code>src/main.rs</code> 中的 <code>cli::Command::Run(run)</code> ,程序解析输入的参数,向 keep_exec 函数中传入 backend.shim()(以 sgx 为例,”/bin/shim-sgx”)和 workldr.exec() (”/bin/workldr”)(目前我对 workldr 和传进来的 hello-world.wasm 的关系的理解是 hello-world.wasm 应该是在 workldr 上面执行的一段代码,workldr 是一个 static PIE ELF binary,但是在它之上运行的 hello-world.wasm 不是)</p><p>要明确的是程序在 <code>src</code> 文件夹下表示它还在非 enclave/SNP-VM 内,也就是在 host 区域内,如果程序走到了 <code>internal</code> 部分时,是走到了 enclave/SNP-VM 中。</p><p> 以下为<code>keep_exec</code> 函数的内容,先简单介绍一下,生成 keep 和 thread 还是位于 host 端,当进入 thread 中时,是进入 enclave/SNP-VM 的标志,当 thread.enter() 产生返回值,就是从 enclave/SNP-VM 中回到 host 端。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fn</span> <span class="title">keep_exec</span></span>(backend: &dyn Backend, shim: <span class="keyword">impl</span> <span class="built_in">AsRef</span><[<span class="built_in">u8</span>]>, exec: <span class="keyword">impl</span> <span class="built_in">AsRef</span><[<span class="built_in">u8</span>]>) -> <span class="built_in">Result</span><()> {</span><br><span class="line"> <span class="keyword">let</span> keep = backend.keep(shim.as_ref(), exec.as_ref())?;</span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">mut</span> thread = keep.clone().spawn()?.unwrap();</span><br><span class="line"> <span class="keyword">loop</span> {</span><br><span class="line"> <span class="keyword">match</span> thread.enter()? {</span><br><span class="line"> Command::SysCall(block) => <span class="keyword">unsafe</span> {</span><br><span class="line"> block.msg.rep = block.msg.req.syscall();</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> Command::CpuId(block) => <span class="keyword">unsafe</span> {</span><br><span class="line"> <span class="keyword">let</span> cpuid = core::arch::x86_64::__cpuid_count(</span><br><span class="line"> block.msg.req.arg[<span class="number">0</span>].try_into().unwrap(),</span><br><span class="line"> block.msg.req.arg[<span class="number">1</span>].try_into().unwrap(),</span><br><span class="line"> );</span><br><span class="line"></span><br><span class="line"> block.msg.req.arg[<span class="number">0</span>] = cpuid.eax.into();</span><br><span class="line"> block.msg.req.arg[<span class="number">1</span>] = cpuid.ebx.into();</span><br><span class="line"> block.msg.req.arg[<span class="number">2</span>] = cpuid.ecx.into();</span><br><span class="line"> block.msg.req.arg[<span class="number">3</span>] = cpuid.edx.into();</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> Command::Continue => (),</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="SEV部分代码解析"><a href="#SEV部分代码解析" class="headerlink" title="SEV部分代码解析"></a>SEV部分代码解析</h1><h2 id="SEV-host-端"><a href="#SEV-host-端" class="headerlink" title="SEV host 端"></a>SEV host 端</h2><p>以 <code>src/main.rs</code> 文件中的 <code>keep_exec</code> 作为项目的入口点开始阅读。</p><ul><li><p>首先,执行的是 <code>let keep = backend.keep(shim.as_ref(), exec.as_ref())?;</code><br>shim 是生成的 shim-sev, exec 是将会加载到 shim-sev 中的 wasmldr。这一步的作用是创建了一个 enclave(安全区),在 SEV 中的作用是创建了一个 VM。</p><ul><li><p>keep 函数的声明在 <code>src/backend/sev/mod.rs</code> 中,函数体调用了 <code>builder::Builder::load(shim, exec);</code> 。</p></li><li><p>load 函数的具体实现在 <code>src/backend/binary.rs</code> 中,这部分的逻辑实现和 SGX 是共用的。slot 是从 sbin(shim) 中提取出来的 <code>sallyport::elf::pt::EXEC</code> 段,用于执行 ebin(同时也是exec,也就是装载 wasmldr 的地方),并检查 slot 的范围是否大于等于 ebin 的大小。</p></li><li><p>执行 <code>let mut loader: Self = Self::Config::new(&sbin, &ebin)?.try_into()?;</code> 创建一个 builder.</p><ul><li><p>Config::new这部分基本就是一个检查的功能(KVM shim must contains exectly one sallyport PT_LOAD segment,shim-sev 的头部有且仅有一个 PT_LOAD 段)</p></li><li><p><code>try_into</code> 是 <code>Config</code> 类型变成 <code>Builder</code> 类型,具体实现在 <code>src/backend/sev/builder.rs</code> 中的 Builder::TryFrom</p><ul><li>kvm 建立了一个 vm,返回了 vm_fd;</li><li>通过和 <code>/dev/sev</code> (ioctl) 交互执行 KVM_SNP_INIT (hypervisor uses this to initialize the SEV-SNP platform contexts)</li><li>执行 KVM_SEV_SNP_LAUNCH_START (begins the launch process for an SEV_SNP guest, initializes a cryptographic digest context used to construct the measurement of the guest)</li></ul></li></ul></li><li><p>回到 <code>src/backend/binary.rs</code> 中,将 sbin 和 ebin 的各个 segments 连接到 vec 向量中,调用 <code>loader.map(map, seg.range.start, flags)?;</code> map 函数的具体实现在 <code>src/backend/sev/builder.rs</code> 中 </p><ul><li><p>调用 <code>kvm_builder_map</code> (具体实现在 <code>src/backend/kvm/builder.rs</code> 中)向 sallyport 中写入 sbin 和 ebin 各个 block(4KB大小,可以看成页)的虚拟地址,并在该函数的最后通过 <code>vm_fd.set_user_memory_region</code> 将创建一个 guest physical slot(创建 guest 物理地址空间与 kvm(VMM) 虚拟地址空间中的内存区域的映射(GPA -> HVA))</p></li><li><p>根据 block 的属性(SECRETS, NORMAL, CPUID)来新建 update 结构体,然后调用 self.launcher.update_data(update) (通过 ioctl 交互调用了 SEV_SNP_LAUNCH_UPDATE (inserts secrets page, contains encryption keys used for encrypting the bios image used for booting the SEV-SNP images, inserts CPUID page, contains hypervisor provided CPUID function values that it passes to the guest)</p></li><li><p>给 Builder 结构体中的 regions push 了 kvm_userspace_memory_regions 以及对应的页属性(注意,这里对于的 map 是对 segments 中的每个seg 都会调用一次,所以每次 map 都会向 regions 中 push 一个)。</p></li></ul></li><li><p>在 <code>src/backend/binary.rs</code> 的最后,Builder 类型 try_into 为Keep 类型</p><ul><li>调用 <code>kvm_try_from_builder</code> <ul><li>创建一个vcpu</li><li>调用 set_cpuid2</li><li>返回 vcpu 描述符和 sallyport_block_start</li></ul></li><li>调用 <code>SNP_LAUNCH_FINISH</code> (finalizes the cryptographic digest and stores it as the measurement of the guest at launch, hypervisor then may invoke VMRUN to execute the guest)</li></ul></li><li><p>返回 Keep 结构体</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="literal">Ok</span>(Arc::new(RwLock::new(super::Keep::<SnpKeepPersonality> {</span><br><span class="line"> kvm_fd: builder.kvm_fd,</span><br><span class="line"> vm_fd,</span><br><span class="line"> cpu_fds: <span class="built_in">vec!</span>[vcpu_fd],</span><br><span class="line"> regions: builder.regions,</span><br><span class="line"> sallyports: builder.sallyports,</span><br><span class="line"> sallyport_start: sallyport_block_start,</span><br><span class="line"> personality: SnpKeepPersonality { _sev_fd: sev_fd },</span><br><span class="line"> })))</span><br></pre></td></tr></table></figure></li></ul></li><li><p>调用 <code>keep.spawn</code> ,其实现在 <code>src/backend/kvm/thread.rs</code> 中,新建一个 Thread 类型的对象,其中包含 keep 和 vcpu_fd </p></li><li><p>调用 <code>thread.enter</code> ,进入该函数,执行 <code>vcpu_fd.run()</code> ,这一步执行的就是给 VM 上电,执行 VM_RUN 操作。接下来就是进入到 SEV 的 enclave 中,也就是 internal/shim-sev 部分中</p><h2 id="SEV-VM-端"><a href="#SEV-VM-端" class="headerlink" title="SEV VM 端"></a>SEV VM 端</h2></li></ul><p>在 layout.ld 文件中,reset_vector 设置为 0xFFFF_F000,此处应该是装载代码的页(正好是4KB,是一个 block 的大小),代码开始执行的是从 0xFFFF_FFF0,通过该地址进入到实模式(16位)</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// reset vector @ 0xFFFF_FFF0</span></span><br><span class="line"><span class="string">".code16"</span>,</span><br><span class="line"><span class="string">"jmp 20b"</span>,</span><br><span class="line"><span class="string">"hlt"</span>,</span><br></pre></td></tr></table></figure><p>接着,关中断,加载 32 bit GDT,设置CR0,开启保护模式(进入32位的准备工作)</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">/ *****************************</span><br><span class="line"><span class="comment">// 32-bit setup code</span></span><br><span class="line"><span class="comment">// *****************************</span></span><br><span class="line"><span class="string">"define_addr code32_start 30f"</span>,</span><br><span class="line"><span class="string">"30:"</span>,</span><br><span class="line"><span class="string">".code32"</span>,</span><br></pre></td></tr></table></figure><p>在保护模式中,加载 CPUID_PAGE,获取 pte bit position 来开启 memory encryption,初始化 PML3, PML4,激活 CR3,设置CR4(控制寄存器),设置EFER(启动 long mode,system call等),设置 CR0 启动分页模式<br>准备完成之后,跳转到 64 位模式。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// *****************************</span></span><br><span class="line"><span class="comment">// 64-bit setup code</span></span><br><span class="line"><span class="comment">// *****************************</span></span><br><span class="line"><span class="string">"define_addr code64_start 40f"</span>,</span><br><span class="line"><span class="string">"40:"</span>,</span><br><span class="line"><span class="string">".code64"</span>,</span><br></pre></td></tr></table></figure><p>rsi = {SHIM_VIRT_OFFSET}</p><p>rdi = {_DYNAMIC}</p><p>将栈加载到 shim virtual address space 中,</p><p>最后,调用 pre_main</p><p>__pre_main 执行完毕之后跳转到 main 函数,main 函数中初始化 gdt,sse,interrupt,最后执行 payload。</p><p>payload 中 先调用了 crt0setup (Create the initial stack frame to start an ELF binary on Linux),然后调用了 usermode 函数,usermode 函数进入到 Ring 3 中</p><h1 id="sallyport-部分"><a href="#sallyport-部分" class="headerlink" title="sallyport 部分"></a>sallyport 部分</h1><p>sallyport 就是 keep 中关于 系统调用部分的实现。部分系统调用都是由 keep 传递给 host,由 host 来执行的,出于安全考虑,keep 仅会给 host 提供最少的 register context(rdi, rsi, rdx, r10, r8, r9),这里的 register context 在 sallyport 项目中就是 Message,Message 是有两个变量的枚举类型。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="class"><span class="keyword">union</span> <span class="title">Message</span></span> {</span><br><span class="line"> <span class="comment">/// A request</span></span><br><span class="line"> <span class="keyword">pub</span> req: Request,</span><br><span class="line"></span><br><span class="line"> <span class="comment">/// A reply</span></span><br><span class="line"> <span class="keyword">pub</span> rep: Reply,</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>request 结构体由 microkernel 传递给 hypervisor(keep) ,其中包含两个参数,syscall identifier(从rust-lang/libc 中得到 syscall table,调用号存在 rax 中)和最多7个系统调用参数。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="class"><span class="keyword">struct</span> <span class="title">Request</span></span> {</span><br><span class="line"> <span class="comment">/// The syscall number for the request</span></span><br><span class="line"> <span class="comment">///</span></span><br><span class="line"> <span class="comment">/// See, for example, libc::SYS_exit.</span></span><br><span class="line"> <span class="keyword">pub</span> num: Register<<span class="built_in">usize</span>>,</span><br><span class="line"></span><br><span class="line"> <span class="comment">/// The syscall argument registers</span></span><br><span class="line"> <span class="comment">///</span></span><br><span class="line"> <span class="comment">/// At most 7 syscall arguments can be provided.</span></span><br><span class="line"> <span class="keyword">pub</span> arg: [Register<<span class="built_in">usize</span>>; <span class="number">7</span>],</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>rax 和 rdx 在作为输入的同时作为输出</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">asm!(</span><br><span class="line"><span class="string">"syscall"</span>,</span><br><span class="line">inlateout(<span class="string">"rax"</span>) <span class="built_in">usize</span>::from(<span class="keyword">self</span>.num) => rax,</span><br><span class="line"><span class="keyword">in</span>(<span class="string">"rdi"</span>) <span class="built_in">usize</span>::from(<span class="keyword">self</span>.arg[<span class="number">0</span>]),</span><br><span class="line"><span class="keyword">in</span>(<span class="string">"rsi"</span>) <span class="built_in">usize</span>::from(<span class="keyword">self</span>.arg[<span class="number">1</span>]),</span><br><span class="line">inlateout(<span class="string">"rdx"</span>) <span class="built_in">usize</span>::from(<span class="keyword">self</span>.arg[<span class="number">2</span>]) => rdx,</span><br><span class="line"><span class="keyword">in</span>(<span class="string">"r10"</span>) <span class="built_in">usize</span>::from(<span class="keyword">self</span>.arg[<span class="number">3</span>]),</span><br><span class="line"><span class="keyword">in</span>(<span class="string">"r8"</span>) <span class="built_in">usize</span>::from(<span class="keyword">self</span>.arg[<span class="number">4</span>]),</span><br><span class="line"><span class="keyword">in</span>(<span class="string">"r9"</span>) <span class="built_in">usize</span>::from(<span class="keyword">self</span>.arg[<span class="number">5</span>]),</span><br><span class="line">lateout(<span class="string">"rcx"</span>) _, <span class="comment">// clobbered</span></span><br><span class="line">lateout(<span class="string">"r11"</span>) _, <span class="comment">// clobbered</span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line">Reply {</span><br><span class="line"> ret: [rax.into(), rdx.into()],</span><br><span class="line"> err: <span class="built_in">Default</span>::<span class="keyword">default</span>(),</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>下文是 x86_64 中对于系统调用的一部分解释。所以 keep 是位于 kernel 层的系统调用。</p><blockquote><p>User-level applications use as integer registers for passing the sequence %rdi, %rsi, %rdx, %rcx, %r8 and %r9. The kernel interface uses %rdi, %rsi, %rdx, %r10, %r8 and %r9.</p><p>A system-call is done via the syscall instruction. The kernel destroys registers %rcx and %r11.(uses rcx to store the address of the next instruction to return to, and r11 to save the value of the rflag register. These values will then be restored by the sysret instruction) </p><p>The number of the syscall has to be passed in register %rax.</p><p>System-calls are limited to six arguments,no argument is passed directly on the stack.</p><p>Returning from the syscall, register %rax contains the result of the system-call. A value in the range between -4095 and -1 indicates an error, it is -errno.</p><p>Only values of class INTEGER or class MEMORY are passed to the kernel.</p></blockquote><p>reply 结构体是由 hypervisor 返回给 microkernel,proxied system call 的返回值。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="class"><span class="keyword">struct</span> <span class="title">Reply</span></span> {</span><br><span class="line"> ret: [Register<<span class="built_in">usize</span>>; <span class="number">2</span>],</span><br><span class="line"> err: Register<<span class="built_in">usize</span>>,</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>Block (a page-sized buffer)结构体用于包装 Message’s register context,同时还提供了用于存储 request 可能需要的数据的 data buffer(untrusted,trusted microkernel 和 untrusted hypervisor 共用,In the context of a syscall, this would be the sequence bytes to be written with a write syscall)Keep 需要保证写入 Block 中的额外参数反映在 register context 中(也就是 Message),比如 Message 中的参数 Request/Reply 中的第二个参数是需要写入的 string bytes 的指针(注,这边的地址必需是写入 Block 中的地址,而不是受保护的地址,接着 Keep 可以自由决定将这部分地址中的内容再复制到自己的 protected pages 中)。</p><blockquote><p>If the Keep forms a request that requires additional parameter data to be written to the Block, the register context must reflet this. For example, the second parameter to the write syscall is a pointer to the string of bytes to be written. In this case, the Keep must ensure the second register parameter points to the location where the bytes have been written within the Block, Not a pointer to its protected address space.</p></blockquote><h1 id="SEV-系统调用步骤"><a href="#SEV-系统调用步骤" class="headerlink" title="SEV 系统调用步骤"></a>SEV 系统调用步骤</h1><p>接下来介绍的是 shim-sev 如何处理系统调用的过程。</p><p> <code>intermal/shim-sev/src/main.rs</code> 中调用 <code>gdt::init()</code> 初始化 GDT,其中, <code>LStar::write(VirtAddr::new(_syscall_enter as usize as u64)</code> 设置了 kernel 进行系统调用的入口为_syscall_enter,当用户态的应用程序触发系统调用的时候,kernel 层就会走到 syscall 入口处 _syscall_enter。</p><p>进入到 <code>internal/shim-sev/src/syscall.rs</code> 中的 _syscall_enter() 的具体实现中,在准备好 syscall register 以及 syscall number 之后,调用 syscall_rust 函数,syscall_rust 调用的是 Handler 中的 syscall 函数,跳转到 <code>sallyport/src/syscall/mod.rs</code> 中的 syscall,syscall 调用 do_syscall,do_syscall 的逻辑是根据 nr(系统调用编号)去调用不同的 Handler (MemorySyscallHander, ProcessSyscallHandler, SystemSyscallHandler, FileSyscallHandler, NetworkSyscallHandler) 实现的系统调用函数。</p><p>进行 syscall 时,VM 无法处理所有的 syscall,一些 syscall 都需要转发给 host 来完成。</p><p>以 read 函数为例,read 函数的具体实现在 <code>sallyport/src/syscall/file.rs</code> 中。该函数的逻辑是首先分配了 Block 中的一块 untrusted memory(hostbuf),然后将该 hostbuf 地址从 shim virtual address 转变为 host virtual address,将 read 的系统调用编号,hostbuf 的 host virtual address 以及大小,打包成一个 request,传递到 proxy 函数。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fn</span> <span class="title">read</span></span>(&<span class="keyword">mut</span> <span class="keyword">self</span>, fd: libc::c_int, buf: UntrustedRefMut<<span class="built_in">u8</span>>, count: libc::size_t) -> <span class="built_in">Result</span> {</span><br><span class="line"> <span class="keyword">self</span>.trace(<span class="string">"read"</span>, <span class="number">4</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> buf = buf.validate_slice(count, <span class="keyword">self</span>).ok_or(libc::EFAULT)?;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> c = <span class="keyword">self</span>.new_cursor();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Limit the read to `Block::buf_capacity()`</span></span><br><span class="line"> <span class="keyword">let</span> count = <span class="built_in">usize</span>::min(count, Block::buf_capacity());</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> (_, hostbuf) = c.alloc::<<span class="built_in">u8</span>>(count).or(<span class="literal">Err</span>(libc::EMSGSIZE))?;</span><br><span class="line"> <span class="keyword">let</span> hostbuf = hostbuf.as_ptr();</span><br><span class="line"> <span class="keyword">let</span> host_virt = Self::translate_shim_to_host_addr(hostbuf);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> ret = <span class="keyword">unsafe</span> { <span class="keyword">self</span>.proxy(request!(libc::SYS_read => fd, host_virt, count))? };</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> result_len: <span class="built_in">usize</span> = ret[<span class="number">0</span>].into();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> count < result_len {</span><br><span class="line"> <span class="keyword">self</span>.attacked();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> c = <span class="keyword">self</span>.new_cursor();</span><br><span class="line"> <span class="keyword">unsafe</span> {</span><br><span class="line"> c.copy_into_slice(count, buf[..result_len].as_mut())</span><br><span class="line"> .or(<span class="literal">Err</span>(libc::EFAULT))?;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="literal">Ok</span>(ret)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>proxy 函数的实现是在 <code>internal/shim-sgx/src</code> 中实现的(因为 SGX 和 SEV 进行系统调用转发的实现是不一样的) proxy 是 Handler 中的函数,所以这里的 self 是个 Handler,Handler 有两个成员 HostCall 类型的 hostcall 和大小为 6 的数组(用于存系统调用的参数的数组)。HostCall 又有两个成员,block_index(block编号) 和 block(Block 的 mut 指针),回顾一下上面提到的,Block 其实还有两个成员,msg(用于指示是 request 还是 reply)和 buf (u8数组,用于存一些系统调用要用到的数据)<br>所以这里的 proxy 逻辑是先取出 hostcall 中的 block,向 block 中的 request 成员写入从 read 部分传递过来的 request,然后调用 hostcall</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">unsafe</span> <span class="function"><span class="keyword">fn</span> <span class="title">proxy</span></span>(&<span class="keyword">mut</span> <span class="keyword">self</span>, req: Request) -> sallyport::<span class="built_in">Result</span> {</span><br><span class="line"> <span class="keyword">let</span> block = <span class="keyword">self</span>.hostcall.as_mut_block();</span><br><span class="line"> block.msg.req = req;</span><br><span class="line"> <span class="keyword">self</span>.hostcall.hostcall()</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p> hostcall 函数位于<code>internal/shim-sev/src/hostcall.rs</code>。进入该函数起始点时,还在 VM 中的 kernel 端,VM microkernel 基于 PMIO 的方式向某些端口执行写操作,这个写操作会触发 out,同时写必需的 block。<br>如果没有启动 SNP(也就是 VM 内存没有被加密),创建一个特定端口(KVM_SYSCALL_TRIGGER_PORT, 0xFF),对该端口的写将会触发 VMEXIT ,控制权从 shim-sev(keep/KVM) 转交到 host 部分,接下来向该端口中写入 block_index(这边我猜 Block 内容已经在 proxy 这一部分被写进去了,所以 hostcall 只需要找到 block_index 的 block 就可以读出 Block 信息。</p><p>如果启动了 SNP,其实就是向 GHCB 中写 <code>KVM_SYSCALL_TRIGGER_PORT</code> ,GHCB 是专门用于 VMM 和VM 通信用的内存(区别应该就是加解密的差别)</p><p>现在写入完成之后,就会触发 VMEXIT。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="keyword">unsafe</span> <span class="function"><span class="keyword">fn</span> <span class="title">hostcall</span></span>(&<span class="keyword">mut</span> <span class="keyword">self</span>) -> sallyport::<span class="built_in">Result</span> {</span><br><span class="line"> <span class="keyword">if</span> !snp_active() {</span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">mut</span> port = Port::<<span class="built_in">u16</span>>::new(KVM_SYSCALL_TRIGGER_PORT);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// prevent earlier writes from being moved beyond this point</span></span><br><span class="line"> core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::Release);</span><br><span class="line"></span><br><span class="line"> port.write(<span class="keyword">self</span>.block_index);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// prevent later reads from being moved before this point</span></span><br><span class="line"> core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::Acquire);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// prevent earlier writes from being moved beyond this point</span></span><br><span class="line"> core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::Release);</span><br><span class="line"></span><br><span class="line"> GHCB.do_io_out(KVM_SYSCALL_TRIGGER_PORT, <span class="keyword">self</span>.block_index);</span><br><span class="line"> <span class="comment">// prevent later reads from being moved before this point</span></span><br><span class="line"> core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::Acquire);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">self</span>.block.as_mut().unwrap().msg.rep.into()</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当触发了 VMEXIT 后,程序的控制权又回到了 host 端,回到 <code>src/backend/kvm/thread.rs</code> 函数,当接收到 <code>VcpuExit::Inout(KVM_SYSCALL_TRIGGER_PORT, data)</code> , host 部分对传递进来的 data(也就是 block_index 进行处理,根据它读到 Block 结构体,从而读到 request,根据 request 的系统调用参数选择处理方式,如果不是 SYS_ENARX_BALLON_MEMORY 或 SYS_ENARX_MEM_INFO,就返回 Command::SysCall(block)。<br>此时,再回到 <code>src/main.rs</code> ,keep_exec 根据 thread.enter() 的返回值选择处理方式,如果是 Command::SysCall,就调用 block.msg.req.syscall(),并将返回值赋值给 block.msg.rep。 </p><p>request 的 syscall 调用过程和普通的系统调用基本无差别,返回值存放在 rax 和 rdx 中。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="keyword">unsafe</span> <span class="function"><span class="keyword">fn</span> <span class="title">syscall</span></span>(&<span class="keyword">self</span>) -> Reply {</span><br><span class="line"> <span class="keyword">let</span> rax: <span class="built_in">usize</span>;</span><br><span class="line"> <span class="keyword">let</span> rdx: <span class="built_in">usize</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">i64</span>::from(<span class="keyword">self</span>.num) == libc::SYS_clock_gettime {</span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">mut</span> ret = libc::clock_gettime(<span class="built_in">usize</span>::from(<span class="keyword">self</span>.arg[<span class="number">0</span>]) <span class="keyword">as</span> _, <span class="keyword">self</span>.arg[<span class="number">1</span>].into());</span><br><span class="line"> <span class="keyword">if</span> ret != <span class="number">0</span> {</span><br><span class="line"> ret = *libc::__errno_location();</span><br><span class="line"> }</span><br><span class="line"> Reply {</span><br><span class="line"> ret: [ret.into(), <span class="number">0</span>.into()],</span><br><span class="line"> err: <span class="built_in">Default</span>::<span class="keyword">default</span>(),</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> asm!(</span><br><span class="line"> <span class="string">"syscall"</span>,</span><br><span class="line"> inlateout(<span class="string">"rax"</span>) <span class="built_in">usize</span>::from(<span class="keyword">self</span>.num) => rax,</span><br><span class="line"> <span class="keyword">in</span>(<span class="string">"rdi"</span>) <span class="built_in">usize</span>::from(<span class="keyword">self</span>.arg[<span class="number">0</span>]),</span><br><span class="line"> <span class="keyword">in</span>(<span class="string">"rsi"</span>) <span class="built_in">usize</span>::from(<span class="keyword">self</span>.arg[<span class="number">1</span>]),</span><br><span class="line"> inlateout(<span class="string">"rdx"</span>) <span class="built_in">usize</span>::from(<span class="keyword">self</span>.arg[<span class="number">2</span>]) => rdx,</span><br><span class="line"> <span class="keyword">in</span>(<span class="string">"r10"</span>) <span class="built_in">usize</span>::from(<span class="keyword">self</span>.arg[<span class="number">3</span>]),</span><br><span class="line"> <span class="keyword">in</span>(<span class="string">"r8"</span>) <span class="built_in">usize</span>::from(<span class="keyword">self</span>.arg[<span class="number">4</span>]),</span><br><span class="line"> <span class="keyword">in</span>(<span class="string">"r9"</span>) <span class="built_in">usize</span>::from(<span class="keyword">self</span>.arg[<span class="number">5</span>]),</span><br><span class="line"> lateout(<span class="string">"rcx"</span>) _, <span class="comment">// clobbered</span></span><br><span class="line"> lateout(<span class="string">"r11"</span>) _, <span class="comment">// clobbered</span></span><br><span class="line"> );</span><br><span class="line"></span><br><span class="line"> Reply {</span><br><span class="line"> ret: [rax.into(), rdx.into()],</span><br><span class="line"> err: <span class="built_in">Default</span>::<span class="keyword">default</span>(),</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="SGX-部分代码解析"><a href="#SGX-部分代码解析" class="headerlink" title="SGX 部分代码解析"></a>SGX 部分代码解析</h1><h2 id="SGX-host-端"><a href="#SGX-host-端" class="headerlink" title="SGX host 端"></a>SGX host 端</h2><ul><li>类似于 SEV,首先是生成 Keep,keep函数执行的是 <code>builder::Builder::load(shim, exec)</code> ,该函数的具体实现在 <code>src/backend/binary.rs</code> 中的 load( binary.rs 为 SEV 和 SGX 共用的函数,基本的逻辑移植,但是其中内部函数的具体实现细节不一样,这里我们详细讨论其中不一样的细节部分。<ul><li>将 shim 和 exec 加载并解析后变成 sbin 和 ebin 之后,调用 <code>let mut loader: Self = Self::Config::new(&sbin, &ebin)?.try_into()?;</code> 先对 sbin 进行处理构造一个 builder.<ul><li>该函数首先对 shim-sgx 进行解析,构造了一个 Config 结构体,有 parameters, size, ssap(SSA pointer) 成员</li><li>接着,Config 对象 try_into 成 Builder 类型,在该期间,函数打开 “/dev/sgx_enclave” 设备,调用 ENCLAVE_CREATE 创建一个 enclave,并且返回Builder 对象</li></ul></li></ul></li></ul><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="literal">Ok</span>(Builder {</span><br><span class="line"> hash: Hasher::new(config.size, config.ssap),</span><br><span class="line"> mmap: map.into(), <span class="comment">// Discard typed permissions</span></span><br><span class="line"> perm: <span class="built_in">Vec</span>::new(),</span><br><span class="line"> tcsp: <span class="built_in">Vec</span>::new(),</span><br><span class="line"> cnfg: config,</span><br><span class="line"> file,</span><br><span class="line">})</span><br></pre></td></tr></table></figure><ul><li>将 sbin 和 ebin 的各个 segments 连接起来,放到 map 数组中,调用 <code>loader.map(map, seg.range.start, flags)?;</code> (注意这里是个循环,对于每个 segment 都会执行 map 操作)<ul><li>调用 ENCLAVE_ADD_PAGES 将页面(shim 和 wasmldr 页面)加载到 enclave,并将 TCS push 到 tcsp 数组中(TCS:硬件进入/退出 Enclave 时用于存储/恢复线程相关信息的数据结构)</li></ul></li><li>调用 Builder 的 try_from,将 builder 对象转化为 Keep 对象<ul><li>调用 ENCLAVE_INIT 初始化 enclave</li><li>根据页面类型设置页面的权限,返回 Keep 对象</li></ul></li></ul><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="literal">Ok</span>(Arc::new(super::Keep {</span><br><span class="line"> _mem: builder.mmap,</span><br><span class="line"> tcs: RwLock::new(builder.tcsp),</span><br><span class="line">}))</span><br></pre></td></tr></table></figure><ul><li>重新回到 <code>src/main.rs</code> 中,第二步是调用 <code>let mut thread = keep.clone().spawn()?.unwrap();</code> 这一步是创建了一个线程<ul><li>定位 vDSO 地址</li><li>返回一个 Thread 对象(这边的 vdso 到底代表着什么?)</li></ul></li></ul><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="literal">Ok</span>(<span class="literal">Some</span>(<span class="built_in">Box</span>::new(Thread {</span><br><span class="line"> enclave: <span class="keyword">self</span>,</span><br><span class="line"> vdso,</span><br><span class="line"> tcs,</span><br><span class="line"> block: Block::<span class="keyword">default</span>(),</span><br><span class="line"> cssa: <span class="built_in">usize</span>::<span class="keyword">default</span>(),</span><br><span class="line"> how: EENTER,</span><br><span class="line">})))</span><br></pre></td></tr></table></figure><ul><li><p>调用 thread.enter(),这时候程序走到 <code>src/backend/sgx/thread.rs</code> 中。</p><ul><li>SGX 指令包括 ENCLU(用户指令) 和 ENCLS(特权指令)两种,参数和返回值通过 EAX/EBX/ECX/RDX 寄存器指定。</li><li>下面的函数有一个 <code>push r10</code> ,也就是 push run address,该操作存入的是 Run 结构体的地址,Run 结构体的原型对应的是 linux kernel 中的 sgx_enclave_run,是 <strong>vdso_sgx_enter_enclave() 的 execution context(其中的 function 是 EENTER/ERESUME/EEXIT),接下来的 call vDSO function 其实就是调用 `</strong>vdso_sgx_enter_enclave()` ,其中的 function 只能为 EENTER 或者 ERESUME。(EENTER 是用户态指令,vDSO(Linux 虚拟动态共享库)技术是将部分内核代码映射到用户空间中,用户程序可以在无需进入到内核状态下直接调用。)(官方 vDSO 解释:vDSO call that wraps EENTER/ERESUME cycle and returns the CPU exception back to the caller exactly when it happens.)</li><li>在 call vDSO function 后,程序就进入到了 enclave 部分中。</li><li>程序是怎么知道要跳转到 enclave 的地址中呢,要跳转的地址存在 run 结构体中的 TCS 中的 OENTRY 中,(layout.ld 中写明, <code>QUAD(_start) /* OENTRY*/</code> )</li></ul></li></ul><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fn</span> <span class="title">enter</span></span>(&<span class="keyword">mut</span> <span class="keyword">self</span>) -> <span class="built_in">Result</span><Command> {</span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">mut</span> run: Run = <span class="keyword">unsafe</span> { MaybeUninit::zeroed().assume_init() };</span><br><span class="line"> run.tcs = <span class="keyword">self</span>.tcs <span class="keyword">as</span> <span class="built_in">u64</span>;</span><br><span class="line"> <span class="keyword">let</span> how = <span class="keyword">self</span>.how;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// The `enclu` instruction consumes `rax`, `rbx` and `rcx`. However,</span></span><br><span class="line"> <span class="comment">// the vDSO function preserves `rbx` AND sets `rax` as the return</span></span><br><span class="line"> <span class="comment">// value. All other registers are passed to and from the enclave</span></span><br><span class="line"> <span class="comment">// unmodified.</span></span><br><span class="line"> <span class="keyword">unsafe</span> {</span><br><span class="line"> asm!(</span><br><span class="line"> <span class="string">"push rbx"</span>, <span class="comment">// save rbx</span></span><br><span class="line"> <span class="string">"push rbp"</span>, <span class="comment">// save rbp</span></span><br><span class="line"> <span class="string">"mov rbp, rsp"</span>, <span class="comment">// save rsp</span></span><br><span class="line"> <span class="string">"and rsp, ~0xf"</span>, <span class="comment">// align to 16+0</span></span><br><span class="line"></span><br><span class="line"> <span class="string">"push 0"</span>, <span class="comment">// align to 16+8</span></span><br><span class="line"> <span class="string">"push r10"</span>, <span class="comment">// push run address</span></span><br><span class="line"> <span class="string">"call r11"</span>, <span class="comment">// call vDSO function</span></span><br><span class="line"></span><br><span class="line"> <span class="string">"mov rsp, rbp"</span>, <span class="comment">// restore rsp</span></span><br><span class="line"> <span class="string">"pop rbp"</span>, <span class="comment">// restore rbp</span></span><br><span class="line"> <span class="string">"pop rbx"</span>, <span class="comment">// restore rbx</span></span><br><span class="line"></span><br><span class="line"> inout(<span class="string">"rdi"</span>) &<span class="keyword">self</span>.block => _,</span><br><span class="line"> lateout(<span class="string">"rsi"</span>) _,</span><br><span class="line"> lateout(<span class="string">"rdx"</span>) _,</span><br><span class="line"> inout(<span class="string">"rcx"</span>) how => _,</span><br><span class="line"> lateout(<span class="string">"r8"</span>) _,</span><br><span class="line"> lateout(<span class="string">"r9"</span>) _,</span><br><span class="line"> inout(<span class="string">"r10"</span>) &<span class="keyword">mut</span> run => _,</span><br><span class="line"> inout(<span class="string">"r11"</span>) <span class="keyword">self</span>.vdso => _,</span><br><span class="line"> lateout(<span class="string">"r12"</span>) _,</span><br><span class="line"> lateout(<span class="string">"r13"</span>) _,</span><br><span class="line"> lateout(<span class="string">"r14"</span>) _,</span><br><span class="line"> lateout(<span class="string">"r15"</span>) _,</span><br><span class="line"> lateout(<span class="string">"rax"</span>) _,</span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="SGX-enclave-端"><a href="#SGX-enclave-端" class="headerlink" title="SGX enclave 端"></a>SGX enclave 端</h2><p>当调用 EENTER 时,函数会跳转到 <code>internal/shim-sgx/src/main.rs</code> 中的 _start 函数。该函数的隐式参数是 rax(current SSA index),rbx(TCS 地址),rcx(执行完 EENTER 之后的下一条指令)进入 _start 函数,首先会判断 CSSA ,如果 CSSA 等于 0,则进行普通的执行,但如果 CSSA 不等于0,将先处理中断。SSA 中存储着 enclave 因为中断被打断而存储的运行状态信息。如果 CSSA 大于 0,直接跳转到 EENTRY (也就是 main),并传递了 rdi(&mut sallyport::Block,host 和 Keep 共享地址,用于系统调用参数和返回结果传递的内存空间),rsi(&mut [StateSaveArea; N],SSA 数组),rdx(CSSA)。如果 CSSA 等于 0,先执行 relocate(relocate symbols)再跳转到 EENTRY。</p><p>进入到 main 函数中,开启 exception,申请一块 128 M 的ENARX HEAP,根据 CSSA 的大小判断执行。</p><ul><li>如果 CSSA = 0,表示没有 enclave 程序被中断,所以直接通过 ENARX_EXEC_START 进入到 enarx 程序的入口(是 wasmldr 的加载地址)</li><li>CSSA = 1, 由 handle 进行处理系统调用(Keep 处理)</li><li>CSSA > 1,触发 finish 函数,该函数会直接跳过当前的中断,直接执行下一条指令 <figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">unsafe</span> <span class="keyword">extern</span> <span class="string">"C"</span> <span class="function"><span class="keyword">fn</span> <span class="title">main</span></span>(port: &<span class="keyword">mut</span> sallyport::Block, ssas: &<span class="keyword">mut</span> [StateSaveArea; <span class="number">3</span>], cssa: <span class="built_in">usize</span>) {</span><br><span class="line"> ssas[cssa].extra[<span class="number">0</span>] = <span class="number">1</span>; <span class="comment">// Enable exceptions</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">let</span> heap = lset::Line::new(</span><br><span class="line"> &ENARX_HEAP_START <span class="keyword">as</span> *<span class="keyword">const</span> _ <span class="keyword">as</span> <span class="built_in">usize</span>,</span><br><span class="line"> &ENARX_HEAP_END <span class="keyword">as</span> *<span class="keyword">const</span> _ <span class="keyword">as</span> <span class="built_in">usize</span>,</span><br><span class="line"> );</span><br><span class="line"></span><br><span class="line"> <span class="keyword">match</span> cssa {</span><br><span class="line"> <span class="number">0</span> => entry::entry(&ENARX_EXEC_START <span class="keyword">as</span> *<span class="keyword">const</span> <span class="built_in">u8</span> <span class="keyword">as</span> _),</span><br><span class="line"> <span class="number">1</span> => handler::Handler::handle(&<span class="keyword">mut</span> ssas[<span class="number">0</span>], port, heap),</span><br><span class="line"> n => handler::Handler::finish(&<span class="keyword">mut</span> ssas[n - <span class="number">1</span>]),</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ssas[cssa].extra[<span class="number">0</span>] = <span class="number">0</span>; <span class="comment">// Disable exceptions</span></span><br><span class="line"> …………</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><p>对于中断的处理,根据存储在 SSA 中的 rip 值选择处理</p><ul><li>h.ssa.gpr.rip = OP_SYSCALL => h.handle_syscall();</li><li>h.ssa.gpr.rip = OP_CPUID => h.handle_cpuid();<br>进入到 handle_syscall() 中,程序将系统调用所需要的寄存器和参数传递到 sallyport(keep) 实现的 syscall 中,等到 keep 中执行完毕后,将返回值 [rax, rdx] 写入到 ssa 的通用寄存器中。</li></ul><h2 id="SGX-backend-shim-执行程序-包括syscall-转发)"><a href="#SGX-backend-shim-执行程序-包括syscall-转发)" class="headerlink" title="SGX(backend + shim) 执行程序 (包括syscall 转发)"></a>SGX(backend + shim) 执行程序 (包括syscall 转发)</h2><p>(0)在 backend-sgx 的 thread.rs 中 call r11 (r11 就是 __vdso_sgx_enter_enclave,相当于ENCLU的一种快速调用方式)的时候就进入到了 enclave 中。</p><p>(1)第一次EENTER进入到 enclave(shim-sgx) 中的时候,执行完一些初始化操作后(会进入到TCS.OENTRY,就是shim-sgx 里的 main.rs 里的 _start 函数),会跳到相同文件中的main函数,根据CSSA的值选择当前要执行的操作</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">match</span> cssa {</span><br><span class="line"> <span class="number">0</span> => entry::entry(&ENARX_EXEC_START <span class="keyword">as</span> *<span class="keyword">const</span> <span class="built_in">u8</span> <span class="keyword">as</span> _),</span><br><span class="line"> <span class="number">1</span> => handler::Handler::handle(&<span class="keyword">mut</span> ssas[<span class="number">0</span>], port, heap),</span><br><span class="line"> n => handler::Handler::finish(&<span class="keyword">mut</span> ssas[n - <span class="number">1</span>]),</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>当前CSSA = 0,就会跳到 ENARX_EXEC_START,就是wasmldr的位置,开始执行wasmldr。<br>(2)wasmldr 执行过程中,若发生系统调用,则会触发一个AEX异常,进入Kernel的异常处理函数,然后Kernel中会判断是否设置了user handler,我们是没设置的(如果设置的话需要在Run结构体里面设置,设置了的话,就会跳到User Handler),就只会设置一下Run结构体中的一些异常原因之类的字段,比如run.vector,然后Ret,即返回到backend-sgx中Call r11的下一句指令。</p><p>(3.1)此时会判断当前的run.function,就是上一次见到的ENCLU指令,上次就是EENTER到enclave的时候,所以就是run.function 就是 EENETR,然后run.vector 是 InvalidOpcode(被Kernel里的异常处理程序设置的,更准确的说,其实应该是Kernel返回到被vdso函数的时候被设置的,可以在 __vdso_sgx_enter_enclave 的源码里看到)。所以现在self.how = EENTER;(其实就是下一轮call r11的时候,会用EENTER)</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">self</span>.how = <span class="keyword">match</span> run.function <span class="keyword">as</span> <span class="built_in">usize</span> {</span><br><span class="line"> EENTER | ERESUME <span class="keyword">if</span> run.vector == Vector::InvalidOpcode => EENTER,</span><br><span class="line"></span><br><span class="line"> <span class="meta">#[cfg(feature = <span class="meta-string">"gdb"</span>)]</span></span><br><span class="line"> EENTER | ERESUME <span class="keyword">if</span> run.vector == Vector::Page => EENTER,</span><br><span class="line"></span><br><span class="line"> EEXIT => ERESUME,</span><br><span class="line"></span><br><span class="line"> _ => <span class="built_in">panic!</span>(<span class="string">"Unexpected AEX: {:?}"</span>, run.vector),</span><br><span class="line"> };</span><br></pre></td></tr></table></figure><p>(3.2)所以后面会执行 self.cssa += 1,使 cssa = 1;</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">match</span> <span class="keyword">self</span>.how {</span><br><span class="line"> EENTER => <span class="keyword">self</span>.cssa += <span class="number">1</span>,</span><br><span class="line"> ERESUME => <span class="keyword">match</span> <span class="keyword">self</span>.cssa {</span><br><span class="line"> <span class="number">0</span> => <span class="built_in">unreachable!</span>(),</span><br><span class="line"> _ => <span class="keyword">self</span>.cssa -= <span class="number">1</span>,</span><br><span class="line"> },</span><br><span class="line"> _ => <span class="built_in">unreachable!</span>(),</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>(3.3)此时,虽然cssa > 0,但how = EENTER,self.how = EENTER,不满足条件,所以此时不会返回 OK(Command::Syscall),而是 Ok(Command::Continue)。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">if</span> <span class="keyword">self</span>.cssa > <span class="number">0</span> {</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">let</span> (EENTER, ERESUME) = (how, <span class="keyword">self</span>.how) {</span><br><span class="line"> <span class="keyword">match</span> <span class="keyword">unsafe</span> { <span class="keyword">self</span>.block.msg.req }.num.into() {</span><br><span class="line"> SYS_ENARX_CPUID => <span class="keyword">return</span> <span class="literal">Ok</span>(Command::CpuId(&<span class="keyword">mut</span> <span class="keyword">self</span>.block)),</span><br><span class="line"></span><br><span class="line"> <span class="meta">#[cfg(feature = <span class="meta-string">"gdb"</span>)]</span></span><br><span class="line"> sallyport::syscall::SYS_ENARX_GDB_START</span><br><span class="line"> | sallyport::syscall::SYS_ENARX_GDB_PEEK</span><br><span class="line"> | sallyport::syscall::SYS_ENARX_GDB_READ</span><br><span class="line"> | sallyport::syscall::SYS_ENARX_GDB_WRITE => {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">Ok</span>(Command::Gdb(&<span class="keyword">mut</span> <span class="keyword">self</span>.block, &<span class="keyword">mut</span> <span class="keyword">self</span>.gdb_fd))</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> _ => <span class="keyword">return</span> <span class="literal">Ok</span>(Command::SysCall(&<span class="keyword">mut</span> <span class="keyword">self</span>.block)),</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="literal">Ok</span>(Command::Continue)</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>(3.4)这个时候继续执行最外面的main.rs,就会进行loop,进行下一轮 thread.run。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">loop</span> {</span><br><span class="line"> <span class="keyword">match</span> thread.enter()? {</span><br><span class="line"> Command::SysCall(block) => <span class="keyword">unsafe</span> {</span><br><span class="line"> block.msg.rep = block.msg.req.syscall();</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> Command::CpuId(block) => <span class="keyword">unsafe</span> {</span><br><span class="line"> <span class="keyword">let</span> cpuid = core::arch::x86_64::__cpuid_count(</span><br><span class="line"> block.msg.req.arg[<span class="number">0</span>].try_into().unwrap(),</span><br><span class="line"> block.msg.req.arg[<span class="number">1</span>].try_into().unwrap(),</span><br><span class="line"> );</span><br><span class="line"></span><br><span class="line"> block.msg.req.arg[<span class="number">0</span>] = cpuid.eax.into();</span><br><span class="line"> block.msg.req.arg[<span class="number">1</span>] = cpuid.ebx.into();</span><br><span class="line"> block.msg.req.arg[<span class="number">2</span>] = cpuid.ecx.into();</span><br><span class="line"> block.msg.req.arg[<span class="number">3</span>] = cpuid.edx.into();</span><br><span class="line"> },</span><br><span class="line"></span><br><span class="line"> <span class="meta">#[cfg(feature = <span class="meta-string">"gdb"</span>)]</span></span><br><span class="line"> Command::Gdb(block, gdb_fd) => {</span><br><span class="line"> backend::handle_gdb(block, gdb_fd, _gdblisten.as_ref().unwrap());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> Command::Continue => (),</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>(4)此时,how=self.how=EENTER,同(1)一样,会从backend-sgx中使用EENTER再一次进入shim-sgx,唯一不同的是,现在 cssa = 1,不会执行 entry了,会执行handle,开始在shim-sgx里初步处理异常,这个函数会先走到SallyPort的Syscall Handler,然后根据系统调用的具体编号,走到shim-sgx里对应的处理函数,这些处理函数做一些初步的处理之后,最终回调用shim-sgx的Proxy函数,准备将系统调用交由Host处理,这个porxy函数的有效代码就是一个syscall,这下可好了,又会触发一次AEX,然后又会返回到backend-sgx。<br>(5.1)同3.1 一样,判断run.function,此时还是EENTER,然后run.vector 是 InvalidOpcode,所以现在self.how = EENTER;</p><p>(5.2)所以后面会执行 self.cssa += 1,使 cssa =2;</p><p>(5.3)同之前一样,虽然cssa > 0,但how = EENTER,self.how = EENTER,不满足条件,所以此时不会返回 OK(Command::Syscall),而是 Ok(Command::Continue)。</p><p>(5.4)这个时候继续执行最外面的main.rs,就会进行loop,进行下一轮 thread.run。</p><p>(6)此时,how=self.how=EENTER,同(1)一样,会从backend-sgx中使用EENTER再一次进入shim-sgx,唯一不同的是,现在 cssa = 2,会执行 finish,看这个名字,终于要完成异常处理了,他干了什么呢?他主要是让之前AEX退出时候保存的寄存器组中的 rip += 2,即跳过了Syscall指令,然后就ret了,所以,这次终于不会AEX了,而是正常的用<strong>EEXIT</strong>来退出了enclave,从shim-sgx回到了backend-sgx里call r11的下一句指令。</p><p>(7.1)此时,同5.1一样,判断run.function,此时可不是EENTER了,而是刚刚的EEXIT,所以现在self.how = ERESUME,这就意味着下一轮循环的时候,会用ERESUME进入enclave,日子终于有了盼头。</p><p>(7.2)所以这次,终于轮到 self.cssa -= 1,使得cssa=1。</p><p>(7.3)这次终于与之前不一样,cssa > 0,而且,how = EENTER,self.how = ERESUME,所以这次会返回OK(Command::Syscall)。</p><p>(7.4)这个时候继续执行最外面的main.rs,就会在Host的环境执行系统调用,并且把结果写到Block了。</p><p>(8)然后进行loop,进行下一轮 thread.run。</p><p>(9)这次,how=self.how=ERESUME,会从backend-sgx中使用ERESUME再一次进入shim-sgx,用ERESUME进入,与用EENTER进入不一样,会使得cssa-=1,此时cssa重新等于0,并且不会从TCS.OENTRY进入,而是通过之前AEX时候保存的寄存器返回,就是6中处理后的rip,就是syscall的下一句指令,至此系统调用完成。一切恢复正常。</p><h3 id="summary"><a href="#summary" class="headerlink" title="summary"></a>summary</h3><p>【cssa=0】wasm 中 第一次 syscall -> AEX,回到 backend中,self.how = EENTER,cssa+=1</p><p>【cssa=1】backend-sgx 中 EENTER </p><p>【cssa=1】shim-sgx 中 handle来初步处理Syscall,然后 Proxy 再次 syscall -> AEX,回到backend中,self.how = EENTER,cssa+=1</p><p>【cssa=2】backend-sgx 中 EENTER</p><p>【cssa=2】shim-sgx 中 finish来 ssa.gpr.rip += 2,跳过syscall指令,之后EEXIT ,回到 backend中,self.how = ERESUME,cssa-=1</p><p>【cssa=1】Host 真实的执行了 syscall</p><p>【cssa=1】backend-sgx 中 ERESUME,cssa-=1</p><p>【cssa=0】恢复wasm执行</p><p>(本轮执行 了 r11 返回之后设置的 self.how 就是下一轮 call r11的时候要执行的操作)</p><h1 id="一些问题"><a href="#一些问题" class="headerlink" title="一些问题"></a>一些问题</h1><ol><li>shim-sgx 中的内存页的权限是什么?enclave v1 中的内存被 push 进来之后,只能执行,不具有可执行的权限。<br>在 layout.ld 中声明了 tcs0, ssa0, exec, heap 等一堆段,但注意这些都是 NOLOAD 的,所以这些段的声明是为了在 shim-sgx 中划分出一些特定的段。(虽然exec 段的 flags 被设置为 0,也就是不可读不可写不可执行;heap 段的 flags 被设置为 7,也就是可读可写可执行。)</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">tcs0 PT_LOAD FLAGS(1 << 20); /* sallyport::elf::pf::sgx::TCS */</span><br><span class="line"> ssa0 PT_LOAD;</span><br><span class="line"></span><br><span class="line"> exec 0x63400000 FLAGS(0); /* sallyport::elf::pt::EXEC */</span><br><span class="line"> heap PT_LOAD FLAGS(7);</span><br></pre></td></tr></table></figure><p>但是当给 sbin 和 ebin 分配物理内存的时候,会将分配的 page 设置为 ReadWrite ,也就是可读可写,所以 tcs0, ssa0, exec, heap 这些段的权限都会变成 R-W,其中只有 text 段是 R-X 的。</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> seg <span class="keyword">in</span> ssegs.iter().chain(esegs.iter()) {</span><br><span class="line"> <span class="comment">// Create the mapping and copy the bytes.</span></span><br><span class="line"> <span class="keyword">let</span> <span class="keyword">mut</span> map = Map::map(seg.range.end - seg.range.start)</span><br><span class="line"> .anywhere()</span><br><span class="line"> .anonymously()</span><br><span class="line"> .known::<perms::ReadWrite>(Kind::Private)?;</span><br><span class="line"> map[seg.skipb..][..seg.bytes.len()].copy_from_slice(seg.bytes);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Pass the region to the builder.</span></span><br><span class="line"> <span class="keyword">let</span> flags = Self::Config::flags(seg.flags);</span><br><span class="line"> loader.map(map, seg.range.start, flags)?;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>wasmldr 会被加载到 exec 段(在 shim-sgx/layout.ld 中是 NOLOAD的,是给wasmldr 预留的)中,由于 wasmldr 中的 text 段是可执行的(没有 layout.ld,但这是默认的)。所以当 wasmldr 加载到 EXEC 段时,wasmldr 对应的 text 段是 R-X 的。</p><p>在 ENCLAVE_CREATE 时创建 SECS,这部分的页面权限是 private,不可读不可写</p><ol start="2"><li>workldr 怎么加载 hello-world.wasm?<br>enarx 直接用 文件描述符 3 表示打开的 wasm 文件</li></ol><blockquote><p>the .wasm module is open on fd3 and gets no arguments or env vars<br>从 fd3 读入编译好的 hello-world.wasm,以 u8 数组的 bytes 格式作为 module 传递到wasmtime::Module 中,运行 hello-world.wasm</p></blockquote><p>问题 2.1 workldr 具体是什么呢?</p><p>workload 的文件中其实是调用的一堆 wasmtime 的 API。</p><p>(1)首先创建 Config 并配置为 WebAssembly 创建一个 全局的编译环境;</p><p>(2)初始化 wasmtime linker,并将 WASI 添加到 linker 中;</p><p>(3)创建 WASI context(WasiCtxBuilder)并放在 Store (a collection of WebAssembly instances and host-defined items)中,从传递进来的 bytes(读到的 hello-world.wasm 文件)初始化 wasmtime::Module,将 module 加入 store;</p><p>(4)call module function</p><p>写在最后</p><p>感谢 <a href="https://aryb1n.github.io/" target="_blank" rel="noopener">aryb1n</a> 的耐心 qwq.(没有小企鹅我就是个废物)</p><p>感谢组长 <a href="https://mssun.me/" target="_blank" rel="noopener">mingshen</a> 和 导师 <a href="https://heartever.github.io/" target="_blank" rel="noopener">wenhao</a> 的指正和讲解</p><p>感谢师兄 <a href="https://ya0guang.com/" target="_blank" rel="noopener">hongbo</a> 的内推和鼓励 </p><p>感谢在 2021 年遇到的可爱的人~</p>]]></content>
<summary type="html">
<p><a href="https://github.com/enarx/enarx" target="_blank" rel="noopener">enarx</a> 的阅读理解。基于 <a href>enarx</a></p>
</summary>
<category term="TEE" scheme="http://yoursite.com/categories/TEE/"/>
<category term="TEE" scheme="http://yoursite.com/tags/TEE/"/>
<category term="Rust" scheme="http://yoursite.com/tags/Rust/"/>
</entry>
<entry>
<title>hexo d出错后的解决</title>
<link href="http://yoursite.com/2021/07/16/2021-07-16-hexo-d%E5%87%BA%E9%94%99%E5%90%8E%E7%9A%84%E8%A7%A3%E5%86%B3/"/>
<id>http://yoursite.com/2021/07/16/2021-07-16-hexo-d出错后的解决/</id>
<published>2021-07-16T13:23:00.000Z</published>
<updated>2021-07-17T02:49:16.053Z</updated>
<content type="html"><![CDATA[<p>可恶的<code>cmder</code>,还是改换成<code>wsl</code>了。</p><a id="more"></a><p>这篇文章只能算上一次问题解决的记录。。。</p><p>又到了一周更新一次blog的时候,结果就是又一次出现了这个错误</p><blockquote><p>Error: Spawn failed<br> at ChildProcess.<anonymous> (D:\git\blog\node_modules\hexo-util\lib\spawn.js:52:19)<br> at ChildProcess.emit (events.js:203:13)<br> at ChildProcess.cp.emit (D:\git\blog\node_modules\cross-spawn\lib\enoent.js:40:29)<br> at Process.ChildProcess._handle.onexit (internal/child_process.js:272:12</anonymous></p></blockquote><p>开始得到的错误是<code>[Unknown SSL protocol error in connection]</code>,AA说这是被墙的错误,虽然我恨不得一天25小时挂着梯子,但我的梯子貌似并无法使得<code>cmder</code>成功翻出去。</p><p>然后随着我不停地google和bing和baidu的结果,一通胡乱操作之后,成功获得一个</p><blockquote><p>fatal: Failed to connect to github.com port 443: Timed out</p></blockquote><p>ping不通github的错误XD</p><p><del>然后根据<a href="https://blog.csdn.net/qq_34817440/article/details/106420689" target="_blank" rel="noopener">这篇</a>,我在windows的hosts文件下加入了<code>github.com</code>和<code>gist.github.com</code>的IP地址后,终于成攻<code>hexo d</code>了</del>。</p><p>比较搞笑的是,我打算把这篇blog推送上去的时候,又出现了上面的错误,怒,遂拥抱<code>wsl</code>。</p><p>注意网上都是直接切换<code>wsl</code>版本为2,但是我直接切换会遇到<code>Error 0x80370102</code>的错误,而<code>wsl2</code>相对于<code>wsl1</code>可能应用了底层硬件的虚拟化功能,需要开启<code>hyper-V</code>,但是虚拟机中使用嵌套虚拟化的话需要关闭<code>hyper-V</code>。。。所以这里我直接使用<code>wsl1</code>,一切顺利。</p>]]></content>
<summary type="html">
<p>可恶的<code>cmder</code>,还是改换成<code>wsl</code>了。</p>
</summary>
<category term="git" scheme="http://yoursite.com/categories/git/"/>
<category term="git" scheme="http://yoursite.com/tags/git/"/>
<category term="杂七杂八的配置" scheme="http://yoursite.com/tags/%E6%9D%82%E4%B8%83%E6%9D%82%E5%85%AB%E7%9A%84%E9%85%8D%E7%BD%AE/"/>
</entry>
<entry>
<title>碎碎念碎碎念</title>
<link href="http://yoursite.com/2021/07/09/2021-07-09-%E7%A2%8E%E7%A2%8E%E5%BF%B5%E7%A2%8E%E7%A2%8E%E5%BF%B5/"/>
<id>http://yoursite.com/2021/07/09/2021-07-09-碎碎念碎碎念/</id>
<published>2021-07-09T10:48:00.000Z</published>
<updated>2021-07-16T13:01:06.080Z</updated>
<content type="html"><![CDATA[<p>没有那么烦了,果然干活让人充实</p><a id="more"></a><p>最近两周因为个人原因过的其实真的很浑浑噩噩(</p><p><del>珍爱生命,远离伪情感大师的情感辅导,被aa坑了的十天,记仇.jpg</del></p><p>原谅aa了,跟自己和命握手言和</p><p>感谢ya0guang</p><p>打算不充知乎盐选了,每天看一集小排球(谁能拒绝月月和影山呢</p><p>我有8个纸片人老婆.jpg</p>]]></content>
<summary type="html">
<p>没有那么烦了,果然干活让人充实</p>
</summary>
<category term="日记" scheme="http://yoursite.com/categories/%E6%97%A5%E8%AE%B0/"/>
<category term="日记" scheme="http://yoursite.com/tags/%E6%97%A5%E8%AE%B0/"/>
</entry>
<entry>
<title>git push时如何忽略大文件</title>
<link href="http://yoursite.com/2021/07/08/2021-07-08-git-push%E6%97%B6%E5%A6%82%E4%BD%95%E5%BF%BD%E7%95%A5%E5%A4%A7%E6%96%87%E4%BB%B6/"/>
<id>http://yoursite.com/2021/07/08/2021-07-08-git-push时如何忽略大文件/</id>
<published>2021-07-08T03:00:00.000Z</published>
<updated>2021-07-08T03:01:17.404Z</updated>
<content type="html"><![CDATA[<p>git push时如何忽略大文件</p><a id="more"></a><p>如果已经进行了</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git add .</span><br><span class="line">git commit -m "xxx"</span><br><span class="line">git push</span><br></pre></td></tr></table></figure><p>这一系列操作之后,得到下述的错误信息</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">remote: error: GH001: Large files detected. You may want to try Git Large File Storage - https:/</span><br><span class="line">remote: error: Trace: 61dafcef71f2e27157398cc7ed0ba9a2bd82716a9bfd1c519a4fbbe97d3b072b</span><br><span class="line">remote: error: See http://git.io/iEPt8g for more information.</span><br><span class="line">remote: error: File xxx is 611.35 MB; this exceeds GitHub's file size limit of</span><br><span class="line">remote: error: File xxx is 611.35 MB; this exceeds GitHub's file size limit of 10</span><br></pre></td></tr></table></figure><p>此时大文件已经被<code>commit</code>了,即使这时候添加<code>.gitignore</code>文件也无法起到应有的作用。</p><p>所以此时应该线回退到没有大文件的版本</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git reflog //查看git commit的各个版本信息</span><br><span class="line">git reset --soft xxx版本号 //回退到希望回退的版本号</span><br></pre></td></tr></table></figure><p>这时候,再添加<code>.gitinore</code>文件,添加自己想要忽略的文件夹和文件信息,就可以再进行<code>git push</code>操作。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://www.jianshu.com/p/699ed86028c2" target="_blank" rel="noopener">.gitignore</a></li><li><a href="https://blog.csdn.net/guozhaohui628/article/details/78922946" target="_blank" rel="noopener">git push提交成功后如何撤销回退</a></li></ul>]]></content>
<summary type="html">
<p>git push时如何忽略大文件</p>
</summary>
<category term="git" scheme="http://yoursite.com/categories/git/"/>
<category term="git" scheme="http://yoursite.com/tags/git/"/>
<category term="杂七杂八的配置" scheme="http://yoursite.com/tags/%E6%9D%82%E4%B8%83%E6%9D%82%E5%85%AB%E7%9A%84%E9%85%8D%E7%BD%AE/"/>
</entry>
<entry>
<title>kvm-unit-tests init_vmx_caps()</title>
<link href="http://yoursite.com/2021/07/02/2021-07-02-kvm-unit-tests-init_vmx_caps()/"/>
<id>http://yoursite.com/2021/07/02/2021-07-02-kvm-unit-tests-init_vmx_caps()/</id>
<published>2021-07-02T08:54:00.000Z</published>
<updated>2021-07-02T09:42:29.220Z</updated>
<content type="html"><![CDATA[<p>vmx.c中的<code>init_vmx_cap()</code>函数解读。</p><a id="more"></a><p>关于<code>init_vmx_cap()</code>函数,该函数密切相关的两个结构体是<code>vmx_ctrl_msr</code>和<code>vmx_basic</code></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">union</span> vmx_ctrl_msr {</span><br><span class="line"> u64 val;</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> {</span></span><br><span class="line"> u32 <span class="built_in">set</span>, clr;</span><br><span class="line"> };</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">union</span> vmx_basic {</span><br><span class="line">u64 val;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> {</span></span><br><span class="line">u32 revision;</span><br><span class="line">u32size:<span class="number">13</span>,</span><br><span class="line">reserved1: <span class="number">3</span>,</span><br><span class="line">width:<span class="number">1</span>,</span><br><span class="line">dual:<span class="number">1</span>,</span><br><span class="line">type:<span class="number">4</span>,</span><br><span class="line">insouts:<span class="number">1</span>,</span><br><span class="line">ctrl:<span class="number">1</span>, <span class="comment">//如果VMX controls默认为1但可以被清除为0,则该bit位为1</span></span><br><span class="line">reserved2:<span class="number">8</span>;</span><br><span class="line">};</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">init_vmx_caps</span><span class="params">(<span class="keyword">void</span>)</span> </span>{</span><br><span class="line"> basic.val = rmdsr(MSR_IA32_VMX_BASIC);</span><br><span class="line"> ctrl_pin_rev.val = rdmsr(basic.ctrl ? MSR_IA32_VMX_TRUE_PIN</span><br><span class="line"> : MSR_IA32_VMX_PINBASED_CTLS);</span><br><span class="line"> ....</span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>MSR_IA32_VMX_BASIC</code>寄存器中保存的值即为<code>vmx_basic</code>,该寄存器bit 55如果为1,通过读取<code>IA32_VMX_TRUE_PINBASED_CTLS MSR</code>报告在VM entry中默认1的类中有哪些pin-based VM-execution controls可以为0(reports which of the pin-based VM-execution controls in the default1 class can be 0 on VM entry)</p><blockquote><p>bits 31:0 indicates the allowed 0-settings of these controls. VM entry allows control X to be 0 if bit X in the MSR is cleared to 0; if bit X in the MSR is set to 1, VM entry fails if control X is 0.</p><p>bits 63:32 indicate the allowed 1-settings of these controls. VM entry allows control X to be 1 if bit 32+X in the MSR is set to 1; if bit 32+X in the MSR is cleared to 0, VM entry fails if control X is 1.</p></blockquote><p>当<code>MSR_IA32_VMX_BASIC</code>寄存器的bit55为0时,所有关于pin-based VM-execution controls信息都保存在<code>IA32_VMX_PINBASED_CTLS MSR</code>。</p><p>(更详细的解释参考intel sdm Volume3 A.3)</p><p>事实证明,当看不懂代码的时候,intel手册永远是最好的小帮手(</p>]]></content>
<summary type="html">
<p>vmx.c中的<code>init_vmx_cap()</code>函数解读。</p>
</summary>
<category term="虚拟化" scheme="http://yoursite.com/categories/%E8%99%9A%E6%8B%9F%E5%8C%96/"/>
<category term="虚拟化" scheme="http://yoursite.com/tags/%E8%99%9A%E6%8B%9F%E5%8C%96/"/>
<category term="KVM" scheme="http://yoursite.com/tags/KVM/"/>
<category term="VT-x" scheme="http://yoursite.com/tags/VT-x/"/>
</entry>
<entry>
<title>CPUID</title>
<link href="http://yoursite.com/2021/07/02/2021-07-02-CPUID/"/>
<id>http://yoursite.com/2021/07/02/2021-07-02-CPUID/</id>
<published>2021-07-02T08:54:00.000Z</published>
<updated>2021-07-02T09:16:18.267Z</updated>
<content type="html"><![CDATA[<p><code>kvm-unit-test x86/processor.h</code>中的<code>CPUID</code></p><a id="more"></a><p>首先以一个函数作为切入口,<code>raw_cpuid</code>通过修改第一个参数和第三个参数的值来输出不同的硬件信息。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">inline</span> struct cpuid <span class="title">raw_cpuid</span><span class="params">(u32 function, u32 index)</span> </span>{</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">cpuid</span> <span class="title">r</span>;</span></span><br><span class="line"> <span class="function"><span class="keyword">asm</span> <span class="title">volatile</span> <span class="params">(<span class="string">"cpuid"</span></span></span></span><br><span class="line"><span class="function"><span class="params"> : <span class="string">"=a"</span>(r.a), <span class="string">"=b"</span>(r.b), <span class="string">"=c"</span>(r.c), <span class="string">"=d"</span>(r.d)</span></span></span><br><span class="line"><span class="function"><span class="params"> : <span class="string">"0"</span>(function), <span class="string">"2"</span>(index))</span></span>;</span><br><span class="line"> <span class="keyword">return</span> r;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>CPUID</code>指令的输出位于<code>EAX</code>, <code>EBX</code>, <code>ECX</code>, <code>EDX</code>中</p><blockquote><p><code>CPUID</code>隐式使用<code>EAX</code>寄存器来确定返回信息的主类别。在intel的最新术语中,这被称之为CPUID leaf。</p></blockquote><p>同时,四个寄存器也作为输入存在,在上述内联汇编中的<code>"0"(function)</code>表示的是将<code>EAX</code>作为第一个输入参数,而<code>"2"(index)</code>是将<code>ECX</code>作为第二个输入参数。所以,如果输入换成<code>: "a"(function), "c"(index)</code>也会得到一样的结果。</p><p>继续向下,理清楚代码如何使用<code>cpuid</code>命令判断机器的各种特征</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> X86_FEATURE_VMX (CPUID(0x1, 0, ECX, 5)) <span class="comment">//00000001 0000 0002</span></span></span><br><span class="line">....</span><br><span class="line">c = cpuid_indexed(<span class="number">0x1</span>, <span class="number">2</span>); <span class="comment">//c = {eax, ebx, ecx, edx}</span></span><br><span class="line">tmp = (u32 *)&c; <span class="comment">//tmp指向c的地址</span></span><br><span class="line"><span class="keyword">return</span> (*(tmp+(output_reg%<span class="number">32</span>))) & (<span class="number">1</span><<bit) <span class="comment">// ecx值 & 100000,如相与不为0,则返回true</span></span><br></pre></td></tr></table></figure><p>给出一个代码在本地显示一些信息。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"stdio.h"</span></span></span><br><span class="line"><span class="keyword">typedef</span> <span class="keyword">unsigned</span> <span class="keyword">int</span> u32;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> u32 val_eax, val_ebx, val_ecx, val_edx;</span><br><span class="line"> <span class="keyword">asm</span>(<span class="string">"cpuid"</span></span><br><span class="line"> : <span class="string">"=a"</span>(val_eax), <span class="string">"=b"</span>(val_ebx), <span class="string">"=c"</span>(val_ecx), <span class="string">"=d"</span>(val_edx)</span><br><span class="line"> : <span class="string">"a"</span>(<span class="number">2</span>));</span><br><span class="line"></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"eax: 0x%08X\n"</span>, val_eax);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"ebx: 0x%08X\n"</span>, val_ebx);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"ecx: 0x%08X\n"</span>, val_ecx);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"edx: 0x%08X\n"</span>, val_edx);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在服务器上进行测试得到的结果是</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">eax: 0x76036301</span><br><span class="line">ebx: 0x00F0B6FF</span><br><span class="line">ecx: 0x00000000</span><br><span class="line">edx: 0x00C30000</span><br></pre></td></tr></table></figure><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://www.cnblogs.com/long123king/p/3522717.html" target="_blank" rel="noopener">CPUID读取有关Cache的信息</a></li><li><a href="https://en.wikipedia.org/wiki/CPUID" target="_blank" rel="noopener">CPUID wiki</a></li></ul>]]></content>
<summary type="html">
<p><code>kvm-unit-test x86/processor.h</code>中的<code>CPUID</code></p>
</summary>
<category term="虚拟化" scheme="http://yoursite.com/categories/%E8%99%9A%E6%8B%9F%E5%8C%96/"/>
<category term="虚拟化" scheme="http://yoursite.com/tags/%E8%99%9A%E6%8B%9F%E5%8C%96/"/>
<category term="KVM" scheme="http://yoursite.com/tags/KVM/"/>
<category term="VT-x" scheme="http://yoursite.com/tags/VT-x/"/>
</entry>
<entry>
<title>KVM SVM</title>
<link href="http://yoursite.com/2021/06/25/2021-06-25-AMD-SEV/"/>
<id>http://yoursite.com/2021/06/25/2021-06-25-AMD-SEV/</id>
<published>2021-06-25T09:22:17.000Z</published>
<updated>2021-06-25T09:26:08.329Z</updated>
<content type="html"><![CDATA[<p>如果去谷歌上搜VT-x,相关的技术blog特别特别多,但如果要是找SVM,基本就是没有,所以本文大部分的内容来自于《AMD programmable manual》以及KVM <code>svm.c</code>代码。</p><a id="more"></a><h1 id="VMRUN"><a href="#VMRUN" class="headerlink" title="VMRUN"></a><code>VMRUN</code></h1><p>SVM中<code>VMRUN</code>, <code>VMSAVE</code>, <code>VMLOAD</code>, <code>VMEXIT</code></p><p>VMCB(virtual machine control block): 用于表示即将运行的虚拟机</p><ul><li>guest中的需要拦截的一系列指令或者行为(比如,向CR3中写数据)</li><li>定义guest的执行环境的control bits或进入guest code之前的将进行的特殊动作</li><li>guest处理器状态(控制寄存器)</li></ul><p>VMRUN</p><p><strong>保存主机状态(Host State)</strong>。<code>VMRUN</code>保存主机状态到<code>VM_HSAVE_PA</code>寄存器指定的物理地址</p><ul><li><p><code>CS.SEL</code>, <code>NEXT_RIP</code>: <code>VMRUN</code>紧接着的CS选择符和RIP,当发起<code>#VMEXIT</code>操作后,主机从该地址重新执行;</p></li><li><p><code>RFLAGS</code>, <code>RAX</code>: 主机处理器以及<code>VMRUN</code>用于寻址VMCB的寄存器</p></li><li><p><code>SS.SEL</code>, <code>RSP</code>: 主机的栈指针(stack pointer)</p></li><li><p><code>CR0</code>, <code>CR3</code>, <code>CR4</code>, <code>EFER</code>: 主机的分页/操作模式</p><ul><li><code>CR0</code>: 系统内的控制寄存器,控制CPU中的一些重要特征</li><li><code>CR3</code>: 页目录基址寄存器,保存页目录表的物理地址</li><li><code>CR4</code>: 是否进入实模式等控制位</li><li><code>EFER</code>(Extended Feature Enable Register): 管理一些扩展bit位(比如System-Call Extension(SCE) Bit)(svm.c <code>smv_set_efer</code>函数)</li></ul></li><li><p><code>IDTR</code>, <code>GDTR</code>: 伪描述符,<code>VMRUN</code>并不会保存或者恢复主机的<code>LDTR</code></p></li><li><p><code>ES.SEL</code>, <code>DS.SEL</code></p></li></ul><p><strong>加载客户机状态(guest state)</strong></p><ul><li><code>CS</code>, <code>rIP</code>: 客户机从该地址处开始执行(从VMCB中加载)</li><li><code>RFLAGS</code>, <code>RAX</code></li><li><code>SS</code>, <code>RSP</code>: <code>SS</code>段寄存器的状态</li><li><code>CR0</code>, <code>CR2</code>, <code>CR3</code>, <code>CR4</code>, <code>EFER</code>: 客户机分页模式。</li><li><code>INTERRUPT_SHADOW</code>: 标记客户机是否位于<em>interrupt lockout shadow</em></li><li><code>IDTR</code>, <code>GDTR</code></li><li><code>ES</code>, <code>DS</code>: 段寄存器中的隐藏状态</li><li><code>DR6</code>, <code>DR7</code>: 客户机的断点状态</li><li><code>V_TPR</code>: 客户机的虚拟TPR</li><li><code>V_IRQ</code>: 指示是否在客户机中使用虚拟中断的标志</li><li><code>CPL</code>: 如果客户机位于实模式,则CPL为0;如果客户机在v86模式,则CPL为3</li></ul><p>当EFLAGS.RF=1时,VMM阻止第一条guest命令中的任何可能的指令断点;</p><p>当EFLGAS.TF=1时,该指令在客户机的第一条指令后会产生trace trap</p><h2 id="svm-c"><a href="#svm-c" class="headerlink" title="svm.c"></a><code>svm.c</code></h2><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//设置指定v</span></span><br><span class="line">cpu的efer</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">svm_set_efer</span><span class="params">(struct kvm_vcpu *vcpu, u64 efer)</span></span></span><br><span class="line"><span class="function"><span class="comment">/*</span></span></span><br><span class="line"><span class="function"><span class="comment">1. 获取vcpu对应的svm结构体</span></span></span><br><span class="line"><span class="function"><span class="comment">2. 读取vcpu中已有的efer,记为old_efer, 并将vcpu对应的efer设置为efer</span></span></span><br><span class="line"><span class="function"><span class="comment">3. 如果没有开启NPT</span></span></span><br><span class="line"><span class="function"><span class="comment">3.1 设置efer中的EFER_NX位</span></span></span><br><span class="line"><span class="function"><span class="comment">3.2 判断LMA位(Long Mode Active),如果不是长模式,则设置LME(Long Mode Enable)为0, 使用legacy mode。</span></span></span><br><span class="line"><span class="function"><span class="comment">4. 如果old_efer和efer中的SVME(Secure Virtual Machine Enable)位不一致</span></span></span><br><span class="line"><span class="function"><span class="comment">4.1 如果efer设置中的EFER_SVME为0(也就是没有开启)</span></span></span><br><span class="line"><span class="function"><span class="comment">4.1.1 svm_leave_nested(svm)</span></span></span><br><span class="line"><span class="function"><span class="comment">4.1.2 svm_set_git</span></span></span><br><span class="line"><span class="function"><span class="comment">4.1.3 如果开启了vmware_backdoor, 则清理exception和intercept</span></span></span><br><span class="line"><span class="function"><span class="comment">4.1.4 如果不位于VMM状态,则清楚nested guest state</span></span></span><br><span class="line"><span class="function"><span class="comment">4.2 如果不为0.也就是开了SVM</span></span></span><br><span class="line"><span class="function"><span class="comment">4.2.1 svm_allocate_nested(svm)</span></span></span><br><span class="line"><span class="function"><span class="comment">4.2.2 设置成功,则设置vcpu中的arch.efer为old_efer</span></span></span><br><span class="line"><span class="function"><span class="comment">4.2.3 设置exception, intercept</span></span></span><br><span class="line"><span class="function"><span class="comment">5. 如果一致,则设置svm vmcb save.efer中的EFER_SVME位,并将svm vmcb结构体中的clean位保留最后五位</span></span></span><br><span class="line"><span class="function"><span class="comment">*/</span></span></span><br></pre></td></tr></table></figure><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//当用户通过KVM_CREATE_VCPU系统调用请求创建vCPU之后,KVM子模块将创建kvm_vcpu结构体并进行初始化操作,最后返回对应的vcpu_fd描述符</span></span><br><span class="line"><span class="comment">//内核版本为4.8</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">kvm_vcpu</span> {</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">kvm</span> *<span class="title">kvm</span>;</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> CONFIG_PREEMPT_NOTIFIERS</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">preempt_notifier</span> <span class="title">preempt_notifier</span>;</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"><span class="keyword">int</span> cpu; <span class="comment">//运行当前vCPU的物理编号</span></span><br><span class="line"><span class="keyword">int</span> vcpu_id;</span><br><span class="line"><span class="keyword">int</span> srcu_idx;</span><br><span class="line"><span class="keyword">int</span> mode;</span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">long</span> requests;</span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">long</span> guest_debug;</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> pre_pcpu;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">list_head</span> <span class="title">blocked_vcpu_list</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">mutex</span> <span class="title">mutex</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">kvm_run</span> *<span class="title">run</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> fpu_active;</span><br><span class="line"><span class="keyword">int</span> guest_fpu_loaded, guest_xcr0_loaded;</span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">char</span> fpu_counter;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">swait_queue_head</span> <span class="title">wq</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">pid</span> *<span class="title">pid</span>;</span></span><br><span class="line"><span class="keyword">int</span> sigset_active;</span><br><span class="line"><span class="keyword">sigset_t</span> sigset;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">kvm_vcpu_stat</span> <span class="title">stat</span>;</span></span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">int</span> halt_poll_ns;</span><br><span class="line"><span class="keyword">bool</span> valid_wakeup;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> CONFIG_HAS_IOMEM</span></span><br><span class="line"><span class="keyword">int</span> mmio_needed;</span><br><span class="line"><span class="keyword">int</span> mmio_read_completed;</span><br><span class="line"><span class="keyword">int</span> mmio_is_write;</span><br><span class="line"><span class="keyword">int</span> mmio_cur_fragment;</span><br><span class="line"><span class="keyword">int</span> mmio_nr_fragments;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">kvm_mmio_fragment</span> <span class="title">mmio_fragments</span>[<span class="title">KVM_MAX_MMIO_FRAGMENTS</span>];</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> CONFIG_KVM_ASYNC_PF</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> {</span></span><br><span class="line">u32 queued;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">list_head</span> <span class="title">queue</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">list_head</span> <span class="title">done</span>;</span></span><br><span class="line"><span class="keyword">spinlock_t</span> lock;</span><br><span class="line">} async_pf;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> CONFIG_HAVE_KVM_CPU_RELAX_INTERCEPT</span></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Cpu relax intercept or pause loop exit optimization</span></span><br><span class="line"><span class="comment"> * in_spin_loop: set when a vcpu does a pause loop exit</span></span><br><span class="line"><span class="comment"> * or cpu relax intercepted.</span></span><br><span class="line"><span class="comment"> * dy_eligible: indicates whether vcpu is eligible for directed yield.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> {</span></span><br><span class="line"><span class="keyword">bool</span> in_spin_loop;</span><br><span class="line"><span class="keyword">bool</span> dy_eligible;</span><br><span class="line">} spin_loop;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"><span class="keyword">bool</span> preempted;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">kvm_vcpu_arch</span> <span class="title">arch</span>;</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure><ul><li><code>int vcpu_id;</code> //对应的vcpu的id</li><li><code>struct kvm_run *run;</code> //vCPU的运行时参数,其中保存了寄存器信息、内存信息、虚拟机状态等各种动态信息</li><li><code>struct kvm_vcpu_arch arch;</code> //KVM虚拟机的运行时参数,比如定时器、中断、内存槽等信息</li></ul><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">vcpu_svm</span></span></span><br></pre></td></tr></table></figure><p>深坑。。。未完待续</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li>AMD手册</li></ul>]]></content>
<summary type="html">
<p>如果去谷歌上搜VT-x,相关的技术blog特别特别多,但如果要是找SVM,基本就是没有,所以本文大部分的内容来自于《AMD programmable manual》以及KVM <code>svm.c</code>代码。</p>
</summary>
<category term="虚拟化" scheme="http://yoursite.com/categories/%E8%99%9A%E6%8B%9F%E5%8C%96/"/>
<category term="虚拟化" scheme="http://yoursite.com/tags/%E8%99%9A%E6%8B%9F%E5%8C%96/"/>
<category term="SVM" scheme="http://yoursite.com/tags/SVM/"/>
<category term="KVM" scheme="http://yoursite.com/tags/KVM/"/>
<category term="手册工程师" scheme="http://yoursite.com/tags/%E6%89%8B%E5%86%8C%E5%B7%A5%E7%A8%8B%E5%B8%88/"/>
</entry>
<entry>
<title>VT-x, KVM, QEMU如何一起愉快地工作</title>
<link href="http://yoursite.com/2021/06/18/2021-06-18-VT-x-KVM-QEMU/"/>
<id>http://yoursite.com/2021/06/18/2021-06-18-VT-x-KVM-QEMU/</id>
<published>2021-06-18T13:19:10.000Z</published>
<updated>2021-06-18T13:22:59.217Z</updated>
<content type="html"><![CDATA[<p>Intel Virtualization: VT-x, KVM, QEMU如何一起愉快地工作</p><a id="more"></a><blockquote><p>When we talk about virtualization we mean hardware assisted virtualization where the VM’s processor matches host computer’s processor. </p></blockquote><blockquote><p>Containerization is mostly a software concept and it builds on top of operating system abstractions like process identifiers, file system and memory consumption limits.</p></blockquote><blockquote><p>In case of KVM, this is actually Linux kernel which has KVM modules loaded into it. In other words, KVM is a set of kernel modules that when loaded into Linux kernel turn the kernel into hypervisor. </p></blockquote><p>Full virtualization: when OS that is running inside a VM is exactly the same as would be running on real hardware.</p><p>Paravirtualization: when OS inside VM is aware that it is being virtualized and thus runs in a slightly modified way than it would on real hardware.</p><p><img src="/2021/06/18/2021-06-18-VT-x-KVM-QEMU/image-20210612163024109.png" alt="image-20210612163024109"></p><h2 id="VT-x"><a href="#VT-x" class="headerlink" title="VT-x"></a>VT-x</h2><p>VT-x是用于Intel64和IA-32架构的CPU虚拟化。VT-x中的CPU操作有两种模式:root和non-root。Hypervisor运行在root模式,而VMs运行在non-root模式。non-root模式可以模拟在root模式下的操作,但需要注意的是”global state-changing”指令,这些指令能够影响CPU状态,这些指令是non-root模式下无法执行的。</p><blockquote><p>Examples are those instructions which modify clock or interrupt registers, or write to control registers in a way that will change the operation of root mode.</p></blockquote><h3 id="VMX-Virtual-Machine-Extension"><a href="#VMX-Virtual-Machine-Extension" class="headerlink" title="VMX(Virtual Machine Extension)"></a>VMX(Virtual Machine Extension)</h3><p>VMX是一系列用于支持VT-x的指令。</p><p><code>VMXON</code>: 用于进入虚拟化的指令,执行完毕后,CPU在root模式下;</p><p><code>VMXOFF</code>:执行完毕后,退出虚拟化;</p><p><code>VMLAUNCH</code>:构造一个VM实例并进入non-root模式;</p><p><code>VMRESUME</code>:进入non-root模式和一个已经存在的VM实例。</p><p><img src="/2021/06/18/2021-06-18-VT-x-KVM-QEMU/image-20210607112952018.png" alt="image-20210607112952018"></p><p>当一个VM尝试执行一条不被运行在non-root模式下执行的指令时,CPU以trap-like的方式切换到root模式,这就是VM exit。</p><ul><li>CPU首先运行在普通模式下,执行<code>VMXON</code>开始了root模式下的虚拟化</li><li>接着,执行<code>VMLAUNCH</code>进入non-root模式并构造了一个VM实例</li><li>当VM实例运行自己的代码,并尝试执行不被运行的命令时,会触发<code>VM exit</code>,并切换到root模式</li><li>切换到root模式时,控制权已经转移到了hypervisor上,hypervisor检查引起<code>VM exit</code>的原因,而后执行<code>VMRESUME</code>重新回到non-root模式下的该VM实例</li></ul><p>Q1:hypervisor如何知道VM exit的原因?不同的VM实例之间如何区分?</p><p>A1:从VMCS结构中得知。</p><p>每个vCPU都有一个VMCS,VMCS以CPU级别存储信息。</p><ul><li><code>VMREAD</code>:读取特定VMCS</li><li><code>VMWRITE</code>:写特定VMCS</li></ul><p>这两条指令都需要root模式,并且只有hypervisor能够修改VMCS。Non-root模式下的VM虽然可以执行VMWRITE,但写入的并不是真正的VMCS,而是”shadow” VMCS.</p><p>有一些指令是作用在所有的VMCS实例中,而不是单独的VMCS。</p><ul><li><code>VMPTRLD</code>: load the address of a VMCS</li><li><code>VMPTRST</code>: store the address to a specified memory address</li></ul><p>虽然可以有多个VMCS实例,但只能有一个时刻只能有一个active的VMCS。<code>VMPTRLD</code>标记该特定的VMCS为active。当执行<code>VMRESUME</code>时,non-root模式下的VM通过active VMCS实例就可以知道正在运行的VM和vCPU。</p><p>VMCS主要保存两种类型的信息:</p><ul><li>在进行root和non-root上下文切换时,CPU寄存器需要保存和恢复的信息</li><li>non-root下,决定VM行为的控制信息。(比如设置了特定的比特位,就可以允许VM尝试执行<code>RDTSC</code>时不会引起<code>VM exit</code></li></ul><p>VMCS可以被划分为6个区域:</p><ul><li>GUEST-STATE域:虚拟机从根操作模式进入非根操作模式时,处理器所处于的状态保存在这里</li><li>HOST-STATE域:虚拟机从非根模式退出到根操作模式时,处理器所处于的状态保存在这里</li><li>VM execution control fields:虚拟机在非根操作模式运行时,处理器所处于的状态</li><li>VM exit control fields:虚拟机从非根操作模式下退出时,需要保存的信息</li><li>VM entry control fields:虚拟机从跟模式进入非根模式时,需要读取的信息</li><li>VM exit information fields:虚拟机从非根操作模式退出到根操作模式,将退出的原因保存到该域中</li></ul><p>Guest software指令:</p><ul><li>INVEPT: GPA->HPA相关缓存刷新</li><li>INVVPID: 用于GVA->GPA的刷新</li></ul><h2 id="KVM-Kernel-based-Virtual-Machine"><a href="#KVM-Kernel-based-Virtual-Machine" class="headerlink" title="KVM(Kernel-based Virtual Machine)"></a>KVM(Kernel-based Virtual Machine)</h2><p><strong>KVM: a set of Linux kernel modules that when loaded, turn Linux kernel into hypervisor.</strong></p><p>KVM模块可以被分成两部分:</p><ul><li>core module: kvm.ko</li><li>machine specific modules: 取决于host machine CPU,比如VT-x需要的就是kvm-intel.ko</li></ul><p>KVM与用户空间进行交互,在本文的语境下,就是QEMU通过两种方式:</p><ul><li>device file <code>/dev/kvm</code><ul><li>system level: </li><li>vm level: </li><li>vCPU level: </li></ul></li><li>memory mapped pages: 用于QEMU和KVM之间批量数据传递</li></ul><h2 id="QEMU-Quick-Emulator"><a href="#QEMU-Quick-Emulator" class="headerlink" title="QEMU(Quick Emulator)"></a>QEMU(Quick Emulator)</h2><blockquote><p>QEMU creates one process for every VM. For each vCPU, QEMU creates a thread. These are regular threads and they get scheduled by the OS like any other thread. </p><p>As these threads get run time, QEMU creates impression of multiple CPUs for the software running inside its VM. </p></blockquote><p>QEMU能够进行模拟,它能够模拟一些KVM并不支持的I/O操作。举个例子,当VM中的某个软件希望执行I/O操作:</p><ul><li>VM返回KVM</li><li>KVM查找VM exit的原因,将控制权以及I/O请求信息的指针转交给QEMU</li><li>QEMU为该请求模拟I/O设备</li><li>当VM中的软件执行完毕后,KVM重新获得控制权,并且执行VMRESUME重新执行VM。</li></ul><p><img src="/2021/06/18/2021-06-18-VT-x-KVM-QEMU/image-20210612222222989.png" alt="image-20210612222222989"></p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://binarydebt.wordpress.com/2018/10/14/intel-virtualisation-how-vt-x-kvm-and-qemu-work-together/" target="_blank" rel="noopener">Intel Virtualization: How VT-x, kVM and QEMU Work Together</a></li><li><a href="https://blog.csdn.net/wanthelping/article/details/47068745" target="_blank" rel="noopener">内核VMX基本数据结构与操作</a></li></ul>]]></content>
<summary type="html">
<p>Intel Virtualization: VT-x, KVM, QEMU如何一起愉快地工作</p>
</summary>
<category term="虚拟化" scheme="http://yoursite.com/categories/%E8%99%9A%E6%8B%9F%E5%8C%96/"/>
<category term="虚拟化" scheme="http://yoursite.com/tags/%E8%99%9A%E6%8B%9F%E5%8C%96/"/>
<category term="KVM" scheme="http://yoursite.com/tags/KVM/"/>
<category term="VT-x" scheme="http://yoursite.com/tags/VT-x/"/>
<category term="QEMU" scheme="http://yoursite.com/tags/QEMU/"/>
</entry>
<entry>
<title>kvm environment in vmware</title>
<link href="http://yoursite.com/2021/06/11/2021-06-11-kvm-environment-in-vmware/"/>
<id>http://yoursite.com/2021/06/11/2021-06-11-kvm-environment-in-vmware/</id>
<published>2021-06-11T04:11:10.000Z</published>
<updated>2021-06-11T12:01:04.612Z</updated>
<content type="html"><![CDATA[<p>记录一下昨天晚上苦逼地搭建KVM unit test环境的过程。</p><a id="more"></a><p>首先梳理一下和<a href="http://liujunming.top/" target="_blank" rel="noopener">liujunming</a>学长讨论得到一些结论:</p><p>Q1:在腾讯云服务器上搭建kvm unit test环境,一直显示:</p><blockquote><p>qemu-system-x86_64: failed to initialize KVM: No such file or directory</p></blockquote><p>尝试了<code>sudo modprobe kvm-intel</code>指令无果,是不是意味着腾讯云服务器上不能跑kvm unit test?</p><p>A1:是的,腾讯云服务器上不能跑,因为腾讯云服务器本身就是kvm,在kvm上再跑qemu,相当于是嵌套虚拟化,而再在qemu上跑kvm unit test(相当于hypervisor),这就是第三层虚拟化了,一般只能嵌套两层,所以不能在腾讯云服务器上跑。</p><h2 id="vmware配置KVM实验环境"><a href="#vmware配置KVM实验环境" class="headerlink" title="vmware配置KVM实验环境"></a>vmware配置KVM实验环境</h2><p>本来以为虚拟机上不能做kvm unit tests的实验,基本快放弃的时候看到了这篇文章<a href="https://www.jianshu.com/p/d0e4ed80b8a1" target="_blank" rel="noopener">KVM虚拟机安装搭建及基本使用</a>,发现在vmware=>硬件=>处理器=>虚拟化引擎选项中有<strong>虚拟化Intel VT-x/EPT或AMD-V/RVI(V)</strong>选项,但是这时候直接开虚拟机会显示<strong>此平台不支持虚拟化的Intel VT-x/EPT</strong>.</p><p>但是要启动VT-x就需要关闭Hyper-V,Hyper-V反正也用不上,控制面板=>程序=>启动或关闭Windows功能,找到Hyper-V的文件夹,将父项以及子项去掉勾选,接下来重新启动。</p><p>由于我的windows10是从家庭版破解出来的专业版,我还按照参考1照着windows10家庭版的关闭流程走了一遍。</p><ul><li>WIN+R打开运行,输入services.msc回车</li><li>在服务中找到HV主机服务,双击打开设置为停用</li><li>以管理员权限打开Windows PowerShell</li><li>运行命令:<code>bcdedit /etc hypervisorlaunchtype off</code></li></ul><p>重启再关一遍HV主机服务,然后打开vmware成功启动虚拟机。</p><p>在命令行输入命令:<code>grep -E "vmx|svm" /proc/cpuinfo</code>,如果能输出一大堆信息,就表明可以成功在虚拟机中开启了虚拟化VT-x。</p><p>kvm unit test的编译和运行实验参考<a href="http://liujunming.top/2019/11/03/Introduction-to-kvm-unit-test/" target="_blank" rel="noopener">Introduction to kvm-unit-test</a>.在执行到最后一步的时候出现错误:</p><blockquote><p>qemu-system-x86_64: failed to initialize kvm: Permission denied</p></blockquote><p>这时候,尝试<code>sudo chmod 666 /dev/kvm</code>,成功运行。</p><h2 id="Hyper-V和VT-x的关系"><a href="#Hyper-V和VT-x的关系" class="headerlink" title="Hyper-V和VT-x的关系"></a>Hyper-V和VT-x的关系</h2><p>Hyper-V:虚拟机化硬件,能够在Windows上跑不同的系统</p><p>VT-x:硬件支持的虚拟化扩展</p><p>在vmware上开启虚拟化VT-x的时候就要关闭Hyper-V,我猜这俩作用相似,一个的开启可能会占用另一个的部分资源。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://www.cnblogs.com/jaycethanks/p/14087318.html" target="_blank" rel="noopener">此平台不支持虚拟化的Intel VT-x/EPT</a></li><li><a href="http://liujunming.top/2019/11/03/Introduction-to-kvm-unit-test/" target="_blank" rel="noopener">Introduction to kvm-unit-test</a></li><li><a href="https://github.com/sickcodes/Docker-OSX/issues/55" target="_blank" rel="noopener">qemu-system-x86_64: failed to initialize kvm: Permission denied</a></li></ul>]]></content>
<summary type="html">
<p>记录一下昨天晚上苦逼地搭建KVM unit test环境的过程。</p>
</summary>
<category term="虚拟化" scheme="http://yoursite.com/categories/%E8%99%9A%E6%8B%9F%E5%8C%96/"/>
<category term="OS" scheme="http://yoursite.com/tags/OS/"/>
<category term="Linux" scheme="http://yoursite.com/tags/Linux/"/>
<category term="KVM" scheme="http://yoursite.com/tags/KVM/"/>
</entry>
<entry>
<title>new to system call</title>
<link href="http://yoursite.com/2021/06/11/2021-06-11-new-to-system-call/"/>
<id>http://yoursite.com/2021/06/11/2021-06-11-new-to-system-call/</id>
<published>2021-06-11T03:30:53.000Z</published>
<updated>2021-06-11T12:00:57.425Z</updated>
<content type="html"><![CDATA[<p>从入门到入土的system call</p><a id="more"></a><h2 id="Arguemnt-Passing-in-Linux"><a href="#Arguemnt-Passing-in-Linux" class="headerlink" title="Arguemnt Passing in Linux"></a>Arguemnt Passing in Linux</h2><table><thead><tr><th>Register</th><th>Argument User Space</th><th>Argument Kernel Space</th></tr></thead><tbody><tr><td>%rax</td><td>Not Used</td><td>System Call Number</td></tr><tr><td>%rdi</td><td>Argument1</td><td>Argument</td></tr><tr><td>%rsi</td><td>Argument2</td><td>Argument</td></tr><tr><td>%rdx</td><td>Argument3</td><td>Argument</td></tr><tr><td>%r10</td><td>Not Used</td><td>Argument</td></tr><tr><td>%r8</td><td>Argument5</td><td>Argument</td></tr><tr><td>%r9</td><td>Argument6</td><td>Argument</td></tr><tr><td>%rcx</td><td>Argument4</td><td>Destroyed</td></tr><tr><td>%r11</td><td>Not Used</td><td>Destroyed</td></tr></tbody></table><p>syscall的返回值保存在<code>%rax</code>中,该值的范围在-4095到-1之间时,表示错误值。</p><p>能够传递给kernel的值只有两种类型:</p><ul><li><code>INTEGER</code>: 能够存储在通用寄存器中的整数类型;</li><li>``MEMORY<code>: 能够通过堆栈传递并返回内存的数据,大多为</code>strings<code>或</code>memory buffer<code>。举个例子,</code>write()<code>系统调用,第一个参数</code>fd<code>是</code>INTEGER<code>类型,第二个参数</code>buffer<code>是即将被写入文件的数据,它的类型是</code>MEMORY<code>,第三个参数是count,其类型也是</code>INTEGER`</li></ul><blockquote><p>Class INTEGER This class consists of integral types that fit into one of the general purpose registers.</p><p>Class MEMORY This class consists of types that will be passed and returned in memory via the stack. These will mostly be strings or memory buffer.</p></blockquote><h2 id="simple-example"><a href="#simple-example" class="headerlink" title="simple example"></a>simple example</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"> .global _start</span><br><span class="line"> .text</span><br><span class="line"></span><br><span class="line">_start:</span><br><span class="line"> # write(1, message, 13)</span><br><span class="line"> mov $1, %rax # system call number of write is 1</span><br><span class="line"> mov $1, %rdi # first argument: file handle 1 is stdout</span><br><span class="line"> mov $message, %rsi # second argument: address of string to output</span><br><span class="line"> mov $13, %rdx # third argument: count(number of bytes)</span><br><span class="line"> syscall # invoke OS to do the write</span><br><span class="line"></span><br><span class="line"> mov $60, %rax # system call number of exit is 60</span><br><span class="line"> xor %rdi, %rdi # return code is 0</span><br><span class="line"> syscall</span><br><span class="line">message:</span><br><span class="line"> .ascii "hello, world\n"</span><br></pre></td></tr></table></figure><p>执行<code>gcc -c hello.s && ld hello.o && ./a.out</code>在命令行中得到<code>hello, world</code>的输出。</p><h2 id="question"><a href="#question" class="headerlink" title="question"></a>question</h2><blockquote><p>The system call destroys rcx and r11 but others registers are saved across the system call.</p></blockquote><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://github.com/rishiba/doc_syscalls/blob/master/doc/05_calling_system_calls.rst" target="_blank" rel="noopener">calling_system_calls</a></li><li><a href="https://cs.lmu.edu/~ray/notes/syscalls/" target="_blank" rel="noopener">System Calls</a></li><li><a href="http://arthurchiao.art/blog/system-call-definitive-guide-zh/" target="_blank" rel="noopener">Linux系统调用权威指南</a></li></ul>]]></content>
<summary type="html">
<p>从入门到入土的system call</p>
</summary>
<category term="OS" scheme="http://yoursite.com/categories/OS/"/>
<category term="OS" scheme="http://yoursite.com/tags/OS/"/>
<category term="Linux" scheme="http://yoursite.com/tags/Linux/"/>
</entry>
<entry>
<title>关于我对可信计算的理解</title>
<link href="http://yoursite.com/2021/06/01/2021-06-01-trusting%20computing/"/>
<id>http://yoursite.com/2021/06/01/2021-06-01-trusting computing/</id>
<published>2021-06-01T13:31:53.000Z</published>
<updated>2021-06-01T13:42:57.518Z</updated>
<content type="html"><![CDATA[<p>可信计算课程的期末报告</p><a id="more"></a><h2 id="1-可信计算和密码学的关系"><a href="#1-可信计算和密码学的关系" class="headerlink" title="1 可信计算和密码学的关系"></a>1 可信计算和密码学的关系</h2><p>可信计算是安全科学中的一个分支,所以先从我对安全科学的理解来谈。众所周知,密码学是所有安全学科的基础。密码学可以说是安全中最接近于科学的小方向,平时更受工业界追捧的工程安全可能处于安全科学领域鄙视链的最底端。Shamir在2002年图灵奖获奖致辞时就直言非密码学的安全为”a mess”。Schell在2001年描述安全领域为”pseudo-science and flying pigs”(伪科学并且充斥着谎话)[1][3]。</p><p>密码学证明安全的过程,最安全的是形式化证明,形式化证明虽然不能保证100%的安全性,但可以保证相对来说非常高的安全性;其次是规约性证明,用理论来证明密码算法的安全性;最不能保证安全的是用攻击来证明安全性。一个密码学算法的最终应用大多要经过严格的理论证明和形式化证明,最终才能走向应用接受现实的攻击实验。</p><p>但在系统安全领域内,大部分论文的安全性分析都是尝试用攻击来证明安全性,这也是硬件隔离机制和密码学的安全性证明很大的不同。可信并不等于安全,可信技术是通过构造一个信任链来让用户信任安全机制的技术。事实上,工业界中的硬件隔离机制也都是先用攻击来证明安全性,当实践已经能证明具有比较高的安全性后,再去考虑形式化验证,这相对密码学来说是个相反的过程。这样相反的论证过程也在一定程度上验证了当前很多可信机制如同漏水大锅一样层出不穷的漏洞。</p><h2 id="2-从SANCTUARY看TEE技术"><a href="#2-从SANCTUARY看TEE技术" class="headerlink" title="2 从SANCTUARY看TEE技术"></a>2 从SANCTUARY看TEE技术</h2><p>体系结构课程上沈海华老师经常喜欢说的一句话就是:体系结构架构师前三十年铆足劲提升性能,分支预测流水线,无所不用其极,近十年的meltdown, spectre攻击无一不是当初强调性能给自己挖的坑。安全是一个negative的因素,强调安全必然会带来性能的下降,结合应用实现,我们需要不断地做安全和性能之间的tradeoff,TEE(Trusted Execution Environment, 可信执行环境)就是tradeoff的产物。值得注意的是很多TEE,比如ARM TrustZone, Intel SGX, AMD SEV等公开的层出不穷的漏洞缺陷大部分都是利用TEE越权。TEE进程一般拥有比普通应用程序更高的权限,通过利用TEE上的漏洞能够以更高的权限获得系统的机密数据和代码。当TEE概念被提出并实现出来后,很多厂家都知道了TEE这么一个好东西后,都希望把自己的机密数据存入TEE中,这带来的后果就是TA(Ttrusted Application, 可信应用程序)数量的不断增大,直接导致了TCB(Trusted Computing Base, 可信计算基)的不断增大。可信计算基的增大意味着攻击面的增大,假设一个TA存在着缺陷,是否会影响到其他TA?这是需要考虑的不同TA之间的隔离性,遗憾的是,很多生产厂家的TEE中不同TAs之间的隔离是非常弱的。</p><p>在可信计算课程中由我为大家介绍的这篇论文《SANCTUARY: ARMing TrustZone with User-space Enclaves》[5]就是基于上述的TrustZone的设计缺陷构造了一个在性能,安全,开发难度之间进行tradeoff的TEE。</p><p>在进行分析后,作者认为目前各大主流TEE不能满足功能丰富的安全移动服务。针对上面提到的现有TEE的问题,SANCTUARY中通过提出了以下三点进行缓解:</p><ul><li>将安全敏感的应用程序移植到IEE(Isolated Execution Environment, 隔离执行环境)中,从而减少TCB的大小;</li><li>移植到IEE之间的应用程序通过动态划分和重分配资源来达到SA(Sanctuary Application)之间的隔离;</li><li>利用TZASC(TrustZone Address Space Controller,TrustZone地址空间控制器)等硬件来保证系统组件之间的隔离。</li></ul><p>TrustZone具有很高的权限,SANCTUARY相当于构造了一个普通权限的TEE,可以访问安全接口但不能直接运行在高权限上。这当然不是凭空产生的想法,SGX是最负盛名的用户态TEE,SANCTUARY的目标实现的就是在ARM架构上的类SGX的TEE。有趣的是,同年有类似想法的还有Keystone,阅读Keystone论文的话就会发现SANCTUARY和Keystone的架构惊人的相似,但Keystone运行在RISC-V架构上。</p><p>一个完整的SANCTUARY实例包括了处于EL0的Sanctuary App(SA)和处于EL1的Sanctuary Lib(SL), SL主要是为SA初始化运行环境并提供服务接口。SANCTUARY的架构图如下图所示。</p><img src="/2021/06/01/2021-06-01-trusting computing/image-20200610153017985.png" alt="image-20200610153017985" style="zoom:50%;"><p>SANCTUARY从4个方面尝试去达成自己的安全目标。下面的篇幅我将打破文章结构介绍,从这四个方面介绍SANCTUARY为实现自己的安全目标所完成的一系列努力。</p><h3 id="2-1-如何实现严格的隔离?"><a href="#2-1-如何实现严格的隔离?" class="headerlink" title="2.1 如何实现严格的隔离?"></a>2.1 如何实现严格的隔离?</h3><p>TEE是基于硬件机制的隔离,首要任务就是保证严格的独立性。SANCTUARY从两个方面来对独立性进行保证:空间和时间。</p><p>空间独立性上主要是为SANCTUARY静态分配物理资源考虑,每个Sanctuary实例都被分配了一个独立的CPU core,利用TZC-400根据CPU core ID为每个Sanctuary实例分配物理内存。每个CPU core的地址范围的上下界都是通过四个地址范围寄存器来保存记录,而每个CPU core对这些地址范围的访问权限也是通过一个访问许可寄存器记录的。另外,为了抵御缓存侧信道攻击,不允许Sanctuary实例映射到L3缓存中(也就是将Sanctuary实例的物理内存都设置为outer uncacheable)</p><p>时间独立性主要是从SANCTUARY的动态加载来考虑,Sanctuary实例是从可信固件中启动CPU core,并且在退出使用之前将内存中的敏感信息全部清空。</p><h3 id="2-2-如何动态分配内核?"><a href="#2-2-如何动态分配内核?" class="headerlink" title="2.2 如何动态分配内核?"></a>2.2 如何动态分配内核?</h3><p>本部分简单介绍SANCTUARY建立和结束运行的过程。</p><h4 id="2-2-1-Sanctuary建立"><a href="#2-2-1-Sanctuary建立" class="headerlink" title="2.2.1 Sanctuary建立"></a>2.2.1 Sanctuary建立</h4><ul><li>当需要执行SA中的敏感代码时,由LA(Legacy Application,普通世界中的应用程序)触发并加载SL和SA的二进制文件后移交给普通世界中位于EL2的KM(kernel module);</li><li>KM选择一个CPU core来运行Sanctuary实例并采用热拔插技术来关闭这个被选择的CPU core;</li><li>KM请求STA的服务(Static Trusted App)的服务,从而进入monitor模式,在模式切换的过程中,可信固件检查所选择的CPU core是否被合理地关闭而后将控制权转交给STA,后者对SANCTUARY内存进行配置。</li></ul><h4 id="2-2-2-Sanctuary退出"><a href="#2-2-2-Sanctuary退出" class="headerlink" title="2.2.2 Sanctuary退出"></a>2.2.2 Sanctuary退出</h4><p>当LA对SA发出请求表明自己不需要SANCTUARY的服务时,SA首先清除上下文,通过sealing保存自己的状态并将L1缓存等设备中关于自己的进程信息清除。可信固件在检查SANCTUARY core是否被真正关闭,STA将SANCTUARY内存以及其他相关的数据全部清零,防止信息泄露。最后,恢复之前的配置,释放SANCTUARY内存区域和CPU core。</p><img src="/2021/06/01/2021-06-01-trusting computing/image-20200611152941103.png" alt="image-20200611152941103" style="zoom:50%;"><h3 id="2-3-如何提供和SGX类似的安全服务?"><a href="#2-3-如何提供和SGX类似的安全服务?" class="headerlink" title="2.3 如何提供和SGX类似的安全服务?"></a>2.3 如何提供和SGX类似的安全服务?</h3><p>类似的,SANCTUARY需要提供远程认证和密封服务。为了提供远程认证服务,SA和安全世界之间有共享内存,Sanctuary将自己的完整性测量结果与通向代理TA(trusted application, 可信应用程序)的信道相链接,该共享内存就是一个安全信道。而代理TA负责建立一个SA到远程服务器连接的安全信道,所有通过代理TA传输的数据都用平台密钥认证过,并与SA的身份绑定。</p><p>类似于SGX中的sealing服务,用从SA二进制文件计算得到的散列值排除出的唯一加密密钥对机密信息进行加密后存到普通世界的内存中,利用该技术,能够在安全地永久存储SANCTUARY数据信息。</p><h3 id="2-4-是否能够在现实场景中提供足够的性能?"><a href="#2-4-是否能够在现实场景中提供足够的性能?" class="headerlink" title="2.4 是否能够在现实场景中提供足够的性能?"></a>2.4 是否能够在现实场景中提供足够的性能?</h3><p>论文中作者通过构造了一个OTP双因素认证的例子,通过展现该实验构造Provision Key和产生OTP的时间来证明SANCTUARY使用普通世界和安全世界的组件所需要的时间是不会影响用户体验的。</p><h2 id="3-对SANCTUARY的讨论"><a href="#3-对SANCTUARY的讨论" class="headerlink" title="3 对SANCTUARY的讨论"></a>3 对SANCTUARY的讨论</h2><p>计算机领域中有一句很著名的话:“计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决“。SANCTUARY其实就是用了这个思想,TrustZone中只有安全世界和非安全世界,SANCTUARY就构造了一个中间层——隔离执行环境,既能保证安全敏感的应用程序的安全执行,也能保证安全世界中的TCB尽可能小。</p><p>SANCTUARY巧妙地用了已有的多核机制和内存地址控制器来进行硬件资源的隔离,但却忽略了cache coherence的影响。所谓cache coherence是在保证了不同cache之间如果有共享数据,在这些共享数据被修改时该如何维护其他CPU core读到该数据时的正确性。在CITM[6]这篇论文中构造了一个方法,利用CPU的shareability属性来操纵Sanctuary实例中的机密数据泄露或者被篡改。</p><p>cache coherence一直是体系结构中研究非常多的一个方向,在体系结构中考虑的更多是如何保证数据的正确性,但在安全领域的研究人员,看这个问题却是如何利用该机制获取机密信息,不同领域看相同的问题会有不同的角度,因为有不同的借鉴。对于我们来说,我觉得做科研可以了解不同方向的论文,不必让自己的知识栈局限在某个领域内,换个角度看问题也许就能有很多很新颖的发现。</p><p>在Sok[2]这篇文章中提到了有相当一部分的TEE相关的安全漏洞和滥用安全世界的接口有关。SANCTUARY同样是通过安全世界给普通世界提供的安全接口来提供服务,但论文中并没有提到对这些安全接口的保护。</p><p>最后谈一下与SANCTUARY架构非常接近的Keystone[4],虽然架构相似,但Keystone的出发点和SANCTUARY并不一样,Keystone着眼于构造一个用于配置、建立以及实例化可定制TEEs的框架,实现上就是通过模块化来定制TEE。在内存隔离上,SANCTUARY是利用TZC-400,而Keystone利用RISC-V机器模式的PMP(physical memory protection)。另外,Keystone着重强调将资源管理和安全检查解耦,最高权限的SM(security monitor)用最少的代码实施安全策略,而在S(Supervisor)-mode的RT(runtime)负责安全区中执行的用户代码的生命周期、内存管理、系统调用、与SM进行通信等操作。我认为Keystone和SANCTUARY虽然出发点不同,但最后实现的架构有很多近似的地方。</p><img src="/2021/06/01/2021-06-01-trusting computing/image-20200914110304034.png" alt="image-20200914110304034" style="zoom:80%;"><h2 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h2><p>[1] Schell R R. Information security: science, pseudoscience, and flying pigs[C]//Seventeenth Annual Computer Security Applications Conference. IEEE, 2001: 205-216.</p><p>[2] Cerdeira D, Santos N, Fonseca P, et al. Sok: Understanding the prevailing security vulnerabilities in trustzone-assisted tee systems[C]//2020 IEEE Symposium on Security and Privacy (SP). IEEE, 2020: 1416-1432.</p><p>[3] Herley C, Van Oorschot P C. Sok: Science, security and the elusive goal of security as a scientific pursuit[C]//2017 IEEE symposium on security and privacy (SP). IEEE, 2017: 99-120.</p><p>[4] Lee D, Kohlbrenner D, Shinde S, et al. Keystone: An open framework for architecting trusted execution environments[C]//Proceedings of the Fifteenth European Conference on Computer Systems. 2020: 1-16.</p><p>[5] Brasser F, Gens D, Jauernig P, et al. SANCTUARY: ARMing TrustZone with User-space Enclaves[C]//NDSS. 2019.</p><p>[6] Wang J, Sun K, Lei L, et al. Cache-in-the-Middle (CITM) Attacks: Manipulating Sensitive Data in Isolated Execution Environments[C]//Proceedings of the 2020 ACM SIGSAC Conference on Computer and Communications Security. 2020: 1001-1015.</p>]]></content>
<summary type="html">
<p>可信计算课程的期末报告</p>
</summary>
<category term="可信计算" scheme="http://yoursite.com/categories/%E5%8F%AF%E4%BF%A1%E8%AE%A1%E7%AE%97/"/>
<category term="TEE" scheme="http://yoursite.com/tags/TEE/"/>
<category term="可信计算" scheme="http://yoursite.com/tags/%E5%8F%AF%E4%BF%A1%E8%AE%A1%E7%AE%97/"/>
</entry>
<entry>
<title>vmx.c部分代码解读(一)</title>
<link href="http://yoursite.com/2021/06/01/2021-06-04-KVM-vmx/"/>
<id>http://yoursite.com/2021/06/01/2021-06-04-KVM-vmx/</id>
<published>2021-06-01T13:31:53.000Z</published>
<updated>2021-06-11T12:02:50.028Z</updated>
<content type="html"><![CDATA[<p>vmx.c中部分代码的理解</p><a id="more"></a><h2 id="根据MSR调整CPU中部分功能的开启"><a href="#根据MSR调整CPU中部分功能的开启" class="headerlink" title="根据MSR调整CPU中部分功能的开启"></a>根据MSR调整CPU中部分功能的开启</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> __<span class="function">init <span class="keyword">int</span> <span class="title">adjust_vmx_controls</span><span class="params">(u32 ctl_min, u32 ctl_opt,</span></span></span><br><span class="line"><span class="function"><span class="params"> u32 msr, u32 *result)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">u32 vmx_msr_low, vmx_msr_high;</span><br><span class="line">u32 ctl = ctl_min | ctl_opt;</span><br><span class="line"></span><br><span class="line">rdmsr(msr, vmx_msr_low, vmx_msr_high);</span><br><span class="line"></span><br><span class="line">ctl &= vmx_msr_high; <span class="comment">/* bit == 0 in high word ==> must be zero */</span></span><br><span class="line">ctl |= vmx_msr_low; <span class="comment">/* bit == 1 in low word ==> must be one */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* Ensure minimum (required) set of control bits are supported. */</span></span><br><span class="line"><span class="keyword">if</span> (ctl_min & ~ctl)</span><br><span class="line"><span class="keyword">return</span> -EIO;</span><br><span class="line"></span><br><span class="line">*result = ctl;</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>该函数用于根据<code>MSR</code>寄存器的实际值,校准控制字符(<strong>c</strong>on<strong>t</strong>ro<strong>l</strong>),其中特定的若干位设置为1,表明开启CPU的某些功能。</p><ul><li><code>ctl_min</code>: 必须开启的设置(minimal required)</li><li><code>ctl_opt</code>: 可选设置</li><li><code>msr</code>: <code>MSR</code>寄存器的地址,表示读取该<code>MSR</code>的控制字符</li><li><code>result</code>: 输出值的地址,用于保存校准值</li></ul><p><code>u32 ctl = ctl_min | ctl_opt</code>临时变量对<code>ctl_min</code>和<code>ctl_opt</code>取或,保留了两个参数中的所有置一位。</p><p>接着<code>rdmsr</code>读取指定<code>MSR</code>寄存器的控制字符。该字符用64位表示特定设置的要求:低32位保存在<code>vmx_msr_low</code>,保证低32位为0的位必须为0:<code>ctl |= vmx_msr_low</code>;高32位保存在<code>vmx_msr_high</code>, 保证高32位为1的为必须为1:<code>ctl &= vmx_msr_high</code></p><p>最后一个判断语句<code>if (ctl_min & ~ctl)</code>是判断开始设置的必须开启的设置是否还是开启的。如果满足,则写入最终结果。</p><h3 id="APIC控制访问"><a href="#APIC控制访问" class="headerlink" title="APIC控制访问"></a>APIC控制访问</h3><p>其实暂时对APIC不是很理解(</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> CONFIG_X86_64</span></span><br><span class="line"><span class="comment">//如果开启了CPU_BASED_TPR_SHADOW,就可以关闭CPU_BASED_CR8_LOAD_EXITING, CPU_BASED_CR8_STORE_EXITING,因为CR8被映射到TPR(读写TPR就等于读写CR8),这样设置的话,当CPU试图访问CR8就不会引起VM exit</span></span><br><span class="line"><span class="keyword">if</span> ((_cpu_based_exec_control & CPU_BASED_TPR_SHADOW)) <span class="comment">//0x00200000</span></span><br><span class="line">_cpu_based_exec_control &= ~CPU_BASED_CR8_LOAD_EXITING &</span><br><span class="line"> ~CPU_BASED_CR8_STORE_EXITING;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br></pre></td></tr></table></figure><h2 id="vmx-get-cpu"><a href="#vmx-get-cpu" class="headerlink" title="vmx_get_cpu"></a><code>vmx_get_cpu</code></h2><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//called before using a cpu</span></span><br><span class="line"><span class="comment">//@vcpu: VCPU that will be loaded</span></span><br><span class="line"><span class="comment">//disable preemption. Call vmx_put_cpu() when finished</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">vmx_get_cpu</span><span class="params">(struct vmx_vcpu *vcpu)</span></span></span><br><span class="line"><span class="function"> <span class="comment">/*</span></span></span><br><span class="line"><span class="function"><span class="comment">1. 获取当前处理器,禁止内核抢占,`int cur_cpu = get_cpu()`</span></span></span><br><span class="line"><span class="function"><span class="comment">2. 设置当前local_vcpu为vcpu(我的理解:相当于上下文切换到vcpu)</span></span></span><br><span class="line"><span class="function"><span class="comment">3. 如果vcpu的cpu不是cur_cpu,执行下面的操作。否则,则直接执行`vmcs_load`指令。</span></span></span><br><span class="line"><span class="function"><span class="comment"> 3.1 如vcpu已有cpu,则运行函数`__vmx_get_cpu_hepler(vcpu)`: 清空vcpu的vmcs,并且将local_vcpu置为NULL;</span></span></span><br><span class="line"><span class="function"><span class="comment"> 3.2. vpid_sync_context: 首先明确一点,VPID用于区分TLB中不同进程的TLB。`vpid_sync_context`调用`INVVPID`使旧vcpu中的vpid失效</span></span></span><br><span class="line"><span class="function"><span class="comment"> 3.3. ept_sync_context:刷新指定的vcpu的ept,`INVEPT`是使EPT中的TLB项失效,当EPT页表有更新时,CPU执行`INVEPT`使旧TLB失效。</span></span></span><br><span class="line"><span class="function"><span class="comment">4. 设置vcpu->launched为0</span></span></span><br><span class="line"><span class="function"><span class="comment">5. 执行`vmcs_load`指令</span></span></span><br><span class="line"><span class="function"><span class="comment">6. `__vmx_set_cpu`,获得该CPU的host_gdt (set per-cpu TSS and GDT when switch processors)</span></span></span><br><span class="line"><span class="function"><span class="comment">7. 设置vcpu的值为cur_cpu.</span></span></span><br><span class="line"><span class="function"><span class="comment"></span></span></span><br><span class="line"><span class="function"><span class="comment">在执行完毕该函数之后,要调用`put_cpu()`恢复内核抢占。</span></span></span><br><span class="line"><span class="function"><span class="comment"> */</span></span></span><br></pre></td></tr></table></figure><h2 id="刷新EPT"><a href="#刷新EPT" class="headerlink" title="刷新EPT"></a>刷新EPT</h2><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">vmx_ept_sync_vcpu</span><span class="params">(struct vmx_vcpu *vcpu)</span> </span>{</span><br><span class="line"> smp_call_function_single(vcpu->cpu, __vmx_sync_helper, (<span class="keyword">void</span> *)vcpu, <span class="number">1</span>);</span><br><span class="line">}</span><br><span class="line"><span class="comment">//smp_call_function_single该函数表示在cpu核上运行__vmx_sync_helper(void* vcpu)函数</span></span><br><span class="line"><span class="comment">//__vmx_sync_helper调用了ept_sync_context(vcpu->eptp);</span></span><br><span class="line"><span class="comment">//ept_sync_context():如果设置了vmx_invept_context, 则只对某个进程的EPT(invalidate all mappings associated with bits 51:12 of the EPT pointer(EPTP)specified in the INVEPT descriptor.)进行无效化更新(__invept);否则对所有EPT表中的所有项更新.</span></span><br></pre></td></tr></table></figure><h2 id="一些杂七杂八的零碎函数"><a href="#一些杂七杂八的零碎函数" class="headerlink" title="一些杂七杂八的零碎函数"></a>一些杂七杂八的零碎函数</h2><p>//GFP_kernel</p><p>用来标记分配内核空间内存的方式。如果内存不够的时候,会等待内核释放内存,直到可以分配相应大小的内存,GFP_kernel用在可以睡眠的场合</p><p>gfp是get free page</p><p>CPU通过<code>GDTR</code>寄存器知道<code>GDT</code>表的位置,通过<code>IDTR</code>寄存器知道<code>IDT</code>表的位置,通过<code>TR</code>寄存器确定<code>TSS</code>的位置</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://blog.csdn.net/basicthinker/article/details/6603541" target="_blank" rel="noopener">Linux/Xen源代码片段解读</a></li><li><a href="https://blog.csdn.net/q1007729991/article/details/52650822" target="_blank" rel="noopener">任务状态段(TSS)</a></li><li><a href="https://github.com/xzffwy/dune_des" target="_blank" rel="noopener">dune_des</a></li><li><a href="https://xem.github.io/minix86/manual/intel-x86-and-64-manual-vol3/o_fe12b1e2a880e0ce.html" target="_blank" rel="noopener">INVEPT</a></li></ul>]]></content>
<summary type="html">
<p>vmx.c中部分代码的理解</p>
</summary>
<category term="虚拟化" scheme="http://yoursite.com/categories/%E8%99%9A%E6%8B%9F%E5%8C%96/"/>
<category term="OS" scheme="http://yoursite.com/tags/OS/"/>
<category term="虚拟化" scheme="http://yoursite.com/tags/%E8%99%9A%E6%8B%9F%E5%8C%96/"/>
<category term="linux" scheme="http://yoursite.com/tags/linux/"/>
</entry>
<entry>
<title>Unikraft-Eurosys21</title>
<link href="http://yoursite.com/2021/05/28/2021-05-28-Unikraft-Eurosys21/"/>
<id>http://yoursite.com/2021/05/28/2021-05-28-Unikraft-Eurosys21/</id>
<published>2021-05-28T08:48:53.000Z</published>
<updated>2021-05-28T12:53:24.073Z</updated>
<content type="html"><![CDATA[<p>Unikraft: Fast, Specialized Unikernels the Easy Way.</p><a id="more"></a><h1 id="Unikraft"><a href="#Unikraft" class="headerlink" title="Unikraft"></a>Unikraft</h1><h2 id="Abstract"><a href="#Abstract" class="headerlink" title="Abstract"></a>Abstract</h2><ul><li>fully modularizes OS primitives so that it is easy to customize the unikernel and include only relevant components</li><li>exposes a set of composable, performance-oriented APIs in order to make it easy for developers to obtain high performance</li></ul><h2 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h2><p><strong>relying on two key priciples</strong>:</p><ul><li>The kernel should be fully modular(fully and easily customizable) : Unikraft provides a series of OS primitives</li><li>The kernel should provide performance-minded, well-defined APIs that can be easily selected and composed: in Unikraft, such APIs are micro-libraries themselves</li></ul><p>=> define a small set of APIs for core OS components that makes it easy to replace-out a component when is not needed, and pick-and-choose from multiple implementation of the same component when performance dictate(随插随用)</p><h2 id="2-Design-Principles-and-Solution-Space"><a href="#2-Design-Principles-and-Solution-Space" class="headerlink" title="2 Design Principles and Solution Space"></a>2 Design Principles and Solution Space</h2><p>The goal that author want to achieve is <strong>enabling developers to create a specialized OS for every single application to ensure the best performance possible, while bounding OS-related development effort and enabling easy porting of existing applications</strong>. (为每个app定制OS的同时保证能够轻松移植现有的app,这个想法真的斯巴拉西)</p><p>So, key design decisions:</p><ul><li>Single address space</li><li>Fully modular system</li><li>Single protection level: no user/kernel-space separation</li><li>Static linking: enable compiler features</li><li>POSIX support: to support existing applications</li><li>Platform abstraction</li></ul><p>=>how to implement? </p><ul><li>minimize an existing general-purpose OS</li><li>start from an existing unikernel project</li><li>from scratch</li></ul><p>=>existing work</p><p><strong>taking existing OSes and adds or remove functionality</strong></p><p>Existing monolithic OSes do have APIs for each components, but most components depend on each other, which indicates that removing or replacing any single component in the Linux kernel is very difficult.</p><p>So, try to modular certain parts of a monolithic kernel.</p><img src="/2021/05/28/2021-05-28-Unikraft-Eurosys21/image-20210523213724734.png" alt="image-20210523213724734" style="zoom: 67%;"><p><strong>bypass the OS altogether</strong></p><p>apps must be coded against the new network, such as DPDK, netmap…</p><p><strong>add the required OS functionality from scratch for each target app</strong></p><h2 id="3-Unikraft-Architecture-and-APIs"><a href="#3-Unikraft-Architecture-and-APIs" class="headerlink" title="3 Unikraft Architecture and APIs"></a>3 Unikraft Architecture and APIs</h2><p>Unikraft obtain performance via <strong>careful API design and static linking</strong>.</p><p>To achieve the modularity: Unikraft consists of two main components:</p><ul><li>Micro-libraries: software components which implement one of the core Unikraft APIs; they have minimal dependencies and can be arbitrarily small;</li><li>Build system: provides a Kconfig-based menu for users to select which micro-libraries to use in an application build.</li></ul><p><img src="/2021/05/28/2021-05-28-Unikraft-Eurosys21/image-20210524152648074.png" alt="image-20210524152648074"></p><p>接下来作者介绍了四个Unikraft API: uknetdev(network), ukalloc(allocation), uksched(scheduling) and uklock(lock)</p><h2 id="4-Application-Support-and-Porting"><a href="#4-Application-Support-and-Porting" class="headerlink" title="4 Application Support and Porting"></a>4 Application Support and Porting</h2><p>Unikraft rely on the <strong>target application’s native build system</strong>, and use the <strong>statically-compiled object files to link them into Unikraft’s final linking step</strong>.(将Unikraft的boot过程与特定的application绑定?前面确实提到了希望一个application一个OS,所以这边的Unikraft相当于就是一个微型OS,只为选定的app服务?)=>用了<code>musl</code></p><p>To support musl, which depends on Linux syscalls, Unikraft created a micro-library called syscall shim: each library that implements a system call handler register it, via a macro, with this micro-library. The shim layer then generates a system call interface at libc-level.(这里写一下我的理解:<em>每个库用宏向micro-library注册了system call处理函数,从而在libc-level生成system call接口</em>)</p><h3 id="4-1-Application-Compatibility"><a href="#4-1-Application-Compatibility" class="headerlink" title="4.1 Application Compatibility"></a>4.1 Application Compatibility</h3><p>Unikraft是通过将<code>musl</code>中未实现的syscall自己实现来实现Application compatibility,当然,对于源代码未公开的,考虑的是binary compatibility和binary rewriting。</p><h2 id="6-Specializing-Applications"><a href="#6-Specializing-Applications" class="headerlink" title="6 Specializing Applications"></a>6 Specializing Applications</h2><p>这部分确实也点明了<code>Unikraft</code>的目标是尽可能提供<strong>更好的性能</strong>,鲁棒性,正确性和安全性都是未来工作。</p><h3 id="6-1-Specialized-Boot-Code"><a href="#6-1-Specialized-Boot-Code" class="headerlink" title="6.1 Specialized Boot Code"></a>6.1 Specialized Boot Code</h3><p><code>Unikraft</code>为了提高boot的速度,提供了一种静态分配物理页表的方法:</p><blockquote><p>Unikraft binary contains an already initialized page-table structure which is loaded in memory by the VMM</p></blockquote><p>在boot过程中,Unikraft只需要修改page-table register的地址即可。</p><p>这种静态分配的方法能够满足大部分的Application,<code>Unikraft</code>也提供了动态加载的方法:此时所有的page-table将会在boot的时候填充。</p><h2 id="讨论"><a href="#讨论" class="headerlink" title="讨论"></a>讨论</h2><p>Unikraft中表示不需要虚拟地址到物理地址的转换,那么如果用虚拟化技术,VM中的app直接是物理地址,那么gp->vp->hp,会不会快一点(虽然原来就有gp->hp的快表)</p>]]></content>
<summary type="html">
<p>Unikraft: Fast, Specialized Unikernels the Easy Way.</p>
</summary>
<category term="OS" scheme="http://yoursite.com/categories/OS/"/>
<category term="OS" scheme="http://yoursite.com/tags/OS/"/>
</entry>
<entry>
<title>eglibc-syscall</title>
<link href="http://yoursite.com/2021/05/21/2021-05-21-eglibc-syscall/"/>
<id>http://yoursite.com/2021/05/21/2021-05-21-eglibc-syscall/</id>
<published>2021-05-21T10:53:22.000Z</published>
<updated>2021-05-21T11:10:15.786Z</updated>
<content type="html"><![CDATA[<p>本篇文章是阅读<code>eglibc-2.14</code>中与<code>syscall</code>相关部分的代码解析,由于本人的GCC汇编很烂,所以解释的比较详细。</p><a id="more"></a><p>先明确<code>mov 源地址,目的地址</code>(永远分不清.jpg</p><h1 id="lowlevellock-h"><a href="#lowlevellock-h" class="headerlink" title="lowlevellock.h"></a><code>lowlevellock.h</code></h1><p><code>Dune</code>项目中的eglibc-2.14.diff是在eglibc-2.14源代码基础上所打的补丁,整个文件就是对6处的<code>syscall</code>将其修改为带条件的<code>vmcall</code>.下面看一下<code>Dune</code>修改的代码。</p><p>对这部分的代码我的理解是:<code>%%cs</code>与<code>$3</code>执行test操作,也就是看<code>%%cs</code>最后两位是否都为0,都为0的话跳转执行<code>vmcall</code>,否则执行<code>syscall</code>。(问题:<em>这边的<code>$3</code>去直接理解成了普通的十进制数,另外我也不是很理解为什么要通过检查<code>%%cs</code>的最后两位来判断是执行<code>syscall</code>还是<code>vmcall</code>?</em>)</p><p>(接下来不再介绍<code>diff</code>文件中的修改,因为都一样啊,不一样的话请告诉我(</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">+#define DUNE_SYSCALL \</span><br><span class="line">+ "push %%rax\n\t" \</span><br><span class="line">+ "mov %%cs, %%rax\n\t" \</span><br><span class="line">+ "test $3, %%rax\n\t" \</span><br><span class="line">+ "pop %%rax\n\t" \</span><br><span class="line">+ "jz 69f\n\t" \</span><br><span class="line">+ "syscall\n\t" \</span><br><span class="line">+ "jmp 70f\n\t" \</span><br><span class="line">+ "69:" \</span><br><span class="line">+ "vmcall\n\t" \</span><br><span class="line">+ "70:"</span><br></pre></td></tr></table></figure><p>接下来看插入修改的位置的代码</p><p>先将<code>%%r10</code>置零,然后把<code>%2</code>(根据<a href="https://www.cnblogs.com/taek/archive/2012/02/05/2338838.html#:~:text=GCC%E5%85%81%E8%AE%B8%E4%BD%A0%E9%80%9A%E8%BF%87C%2FC%2B%2B%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%8C%87%E5%AE%9A%E5%86%85%E8%81%94%E6%B1%87%E7%BC%96%E4%B8%AD%22instruction,list%22%E4%B8%AD%E7%9A%84%E6%8C%87%E4%BB%A4%E7%9A%84%E8%BE%93%E5%85%A5%E5%92%8C%E8%BE%93%E5%87%BA%2C%E4%BD%A0%E7%94%9A%E8%87%B3%E5%8F%AF%E4%BB%A5%E4%B8%8D%E5%85%B3%E5%BF%83%E5%88%B0%E5%BA%95%E4%BD%BF%E7%94%A8%E5%93%AA%E4%BA%9B%E5%AF%84%E5%AD%98%E5%99%A8%2C%E5%AE%8C%E5%85%A8%E4%BE%9D%E9%9D%A0GCC%E6%9D%A5%E5%AE%89%E6%8E%92%E5%92%8C%E6%8C%87%E5%AE%9A%3B%E8%BF%99%E4%B8%80%E7%82%B9%E5%8F%AF%E4%BB%A5%E8%AE%A9%E7%A8%8B%E5%BA%8F%E5%91%98%E5%85%8D%E5%8E%BB%E8%80%83%E8%99%91%E6%9C%89%E9%99%90%E7%9A%84%E5%AF%84%E5%AD%98%E5%99%A8%E7%9A%84%E4%BD%BF%E7%94%A8%2C%E4%B9%9F%E5%8F%AF%E4%BB%A5%E6%8F%90%E9%AB%98%E7%9B%AE%E6%A0%87%E4%BB%A3%E7%A0%81%E7%9A%84%E6%95%88%E7%8E%87%3B%201.%E5%B8%A6%E6%9C%89C%2FC%2B%2B%E8%A1%A8%E8%BE%BE%E5%BC%8F%E7%9A%84%E5%86%85%E8%81%94%E6%B1%87%E7%BC%96%E8%AF%AD%E5%8F%A5%E7%9A%84%E6%A0%BC%E5%BC%8F%3A" target="_blank" rel="noopener">这篇文章</a>中的描述,<code>%2</code>其实就是<code>”i" (SYS_futex)</code>,也就是这边的 立即数<code>202</code>)中的值赋值给<code>%%rax</code>,然后调用<code>syscall</code>,执行完毕后比较<code>%%rdi</code>的值是否为<code>$0</code>,不等的话回头重复执行<code>movq</code>以及接下来的操作。</p><p>Q1: <code>register __typeof (tid) _tid asm ("edx") = (tid);</code>所以这句话是什么意思?</p><p>A1:经过和Aryb1n的讨论(其实是学长单方面教我(,猜测这句话想表达的意思就是用<code>edx</code>寄存器存<code>tid</code></p><p>Q2: <code>lll_wait_tid</code>这个表达式为了完成什么工作?</p><p>A2: 当<code>tid</code>不为0的时候,进行系统调用,唤醒编号为<code>tid</code>的线程(调用的编号为202,202调用的是<code>sys_futex</code>,参考自<a href="https://blog.csdn.net/sinat_26227857/article/details/44244433" target="_blank" rel="noopener">linux系统调用表(system call table)</a>)(嗯,我瞎猜的)</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//eglibc-2.14.orig/libc/nptl/sysdeps/unix/sysv/linux/x86_64/lowlevellock.h</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> lll_wait_tid(tid) \</span></span><br><span class="line"> <span class="keyword">do</span> { \</span><br><span class="line"> <span class="keyword">int</span> __ignore; \</span><br><span class="line"> <span class="keyword">register</span> __typeof (tid) _<span class="function">tid <span class="title">asm</span> <span class="params">(<span class="string">"edx"</span>)</span> </span>= (tid); \</span><br><span class="line"> <span class="keyword">if</span> (_tid != <span class="number">0</span>) \</span><br><span class="line"> __asm __volatile (<span class="string">"xorq %%r10, %%r10\n\t"</span> \</span><br><span class="line"> <span class="string">"1:\tmovq %2, %%rax\n\t"</span> \</span><br><span class="line"><span class="string">"syscall\n\t"</span> \</span><br><span class="line"><span class="string">"cmpl $0, (%%rdi)\n\t"</span> \</span><br><span class="line"><span class="string">"jne 1b"</span> \</span><br><span class="line">: <span class="string">"=&a"</span> (__ignore) \</span><br><span class="line">: <span class="string">"S"</span> (FUTEX_WAIT), <span class="string">"i"</span> (SYS_futex), <span class="string">"D"</span> (&tid), \</span><br><span class="line"> <span class="string">"d"</span> (_tid) \</span><br><span class="line">: <span class="string">"memory"</span>, <span class="string">"cc"</span>, <span class="string">"r10"</span>, <span class="string">"r11"</span>, <span class="string">"cx"</span>); \</span><br><span class="line"> } <span class="keyword">while</span> (<span class="number">0</span>)</span><br></pre></td></tr></table></figure><h2 id="clone-S"><a href="#clone-S" class="headerlink" title="clone.S"></a><code>clone.S</code></h2><p>前面四行的<code>movq</code>操作都是传递相应的参数到指定的寄存器中,需要注意的是user mode下的寄存器中的参数和kernel mode中期望的不一样,所以需要传递不同的参数</p><p><code>movl $SYS_ify(clone),%eax</code>的意思是将<code>clone</code>替换成系统调用,存入<code>eax</code>寄存器中</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">/* eglibc-2.14/libc/sysdeps/unix/sysv/linux/x86_64/clone.S */</span><br><span class="line">/* Do the system call. */</span><br><span class="line">movq%rdx, %rdi</span><br><span class="line">movq%r8, %rdx</span><br><span class="line">movq%r9, %r8</span><br><span class="line">movq8(%rsp), %r10</span><br><span class="line">movl$SYS_ify(clone),%eax</span><br><span class="line"></span><br><span class="line">/* End FDE now, because in the child the unwind info will be</span><br><span class="line"> wrong. */</span><br><span class="line">cfi_endproc;</span><br><span class="line">syscall</span><br></pre></td></tr></table></figure><p>来看<code>clone.S</code>中的第二个打补丁的地方的原函数</p><p>这个函数表达的意思: 判断<code>%rdi</code>中存放的是<code>flags</code>,<code>testq</code>依次判断标记位,判断是否设置了<code>CLONE_THREAD</code>,当没有设置就意味着将子进程设置为新的线程组,继续判断是否设置<code>CLONE_VM</code>,没有设置就意味着父子进程不会共享同一个虚拟内存页,然后执行<code>syscall</code>,获得子进程的<code>pid</code>.(RESET_PID看名字就知道为子进程获得一个PID,如果父子进程共用一个PID的话,虚拟内存等资源应该还是共享的。另外,看程序,testq执行<code>CLONE_THREAD</code>和<code>%rdi</code>的与操作,如果设置了<code>CLONE_THREAD</code>位的话,也就是<code>ZF=0</code>(zero标志位不等于0,跳到程序端末尾)</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">#ifdef RESET_PID</span><br><span class="line">testq$CLONE_THREAD, %rdi</span><br><span class="line">jne1f</span><br><span class="line">testq$CLONE_VM, %rdi</span><br><span class="line">movl$-1, %eax</span><br><span class="line">jne2f</span><br><span class="line">movl$SYS_ify(getpid), %eax</span><br><span class="line">syscall</span><br><span class="line">2:movl%eax, %fs:PID</span><br><span class="line">movl%eax, %fs:TID</span><br><span class="line">1:</span><br><span class="line">#endif</span><br></pre></td></tr></table></figure><h1 id="信号量堵塞处理"><a href="#信号量堵塞处理" class="headerlink" title="信号量堵塞处理"></a>信号量堵塞处理</h1><h2 id="getcontent-S"><a href="#getcontent-S" class="headerlink" title="getcontent.S"></a><code>getcontent.S</code></h2><p>原函数表达的含义: <code>sigprocmask</code>函数用于设定屏蔽信号集中的信号的处理方式(对于某个信号并不希望立即执行该信号,同时也不希望忽略该信号,而是延迟一段时间执行该信号,这就是通过堵塞信号来完成的)</p><p>注:系统调用传递次序为</p><table><thead><tr><th>arch</th><th>arg1</th><th>arg2</th><th>arg3</th><th>arg4</th><th>arg5</th><th>arg6</th></tr></thead><tbody><tr><td>x86_64</td><td>rdi</td><td>rsi</td><td>rdx</td><td>r10</td><td>r8</td><td>r9</td></tr></tbody></table><ul><li><code>SIG_BLOCK</code>:将<code>set</code>所指向的信号集中包含的信号加到当前的信号掩码中(执行<strong>与</strong>操作)(存在<code>%rdi</code>中</li><li><code>set</code>:指向信号集的指针,下面的程序中将其设置为NULL,表示仅想读取现在的屏蔽值</li><li><code>oldset</code>:指向信号集的指针,存放原来的信号集,用来检测信号掩码中存在什么信号(<code>%rdx</code>)</li><li><code>_NSIG/8</code>:信号集的大小(<code>%r10d</code>)</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">/* eglibc-2.14/libc/sysdeps/unix/sysv/linux/x86_64/getcontent.S */</span><br><span class="line"> /* Save the current signal mask with</span><br><span class="line"> rt_sigprocmask (SIG_BLOCK, NULL, set,_NSIG/8). */</span><br><span class="line">leaqoSIGMASK(%rdi), %rdx</span><br><span class="line">xorl%esi,%esi</span><br><span class="line">#if SIG_BLOCK == 0</span><br><span class="line">xorl%edi, %edi</span><br><span class="line">#else</span><br><span class="line">movl$SIG_BLOCK, %edi</span><br><span class="line">#endif</span><br><span class="line">movl$_NSIG8,%r10d</span><br><span class="line">movl$__NR_rt_sigprocmask, %eax</span><br><span class="line">syscall</span><br><span class="line">cmpq$-4095, %rax/* Check %rax for error. */</span><br><span class="line">jaeSYSCALL_ERROR_LABEL/* Jump to error handler if error. */</span><br></pre></td></tr></table></figure><h2 id="setcontent-S"><a href="#setcontent-S" class="headerlink" title="setcontent.S"></a><code>setcontent.S</code></h2><p>跟上面的<code>getcontent.S</code>中的<code>ENTRY</code>函数类似,同样调用了<code>rt_sigprocmask</code>函数,但第一个参数为<code>SIG_SETMASK</code>,表示将<code>set</code>值(<code>leaq OSIGMASK(%rdi), %rsi</code>) 设定为新的进程信号掩码</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">ENTRY(__setcontext)</span><br><span class="line">/* Save argument since syscall will destroy it. */</span><br><span class="line">pushq%rdi</span><br><span class="line">cfi_adjust_cfa_offset(8)</span><br><span class="line"></span><br><span class="line">/* Set the signal mask with</span><br><span class="line"> rt_sigprocmask (SIG_SETMASK, mask, NULL, _NSIG/8). */</span><br><span class="line">leaqoSIGMASK(%rdi), %rsi</span><br><span class="line">xorl%edx, %edx</span><br><span class="line">movl$SIG_SETMASK, %edi</span><br><span class="line">movl$_NSIG8,%r10d</span><br><span class="line">movl$__NR_rt_sigprocmask, %eax</span><br><span class="line">syscall</span><br><span class="line">popq%rdi/* Reload %rdi, adjust stack. */</span><br><span class="line">cfi_adjust_cfa_offset(-8)</span><br><span class="line">cmpq$-4095, %rax/* Check %rax for error. */</span><br><span class="line">jaeSYSCALL_ERROR_LABEL/* Jump to error handler if error. */</span><br></pre></td></tr></table></figure><p>补丁文件也是修改<code>syscall</code></p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">--- eglibc-2.14.orig/libc/sysdeps/unix/sysv/linux/x86_64/setcontext.S2012-06-29 15:42:39.000000000 -0700</span></span><br><span class="line"><span class="comment">+++ eglibc-2.14/libc/sysdeps/unix/sysv/linux/x86_64/setcontext.S2012-06-18 14:49:49.000000000 -0700</span></span><br><span class="line">@@ -44,7 +44,16 @@ ENTRY(__setcontext)</span><br><span class="line"> movl$SIG_SETMASK, %edi</span><br><span class="line"> movl$_NSIG8,%r10d</span><br><span class="line"> movl$__NR_rt_sigprocmask, %eax</span><br><span class="line"><span class="deletion">-syscall</span></span><br><span class="line"><span class="addition">+push %rax</span></span><br><span class="line"><span class="addition">+mov %cs, %rax</span></span><br><span class="line"><span class="addition">+test $3, %rax</span></span><br><span class="line"><span class="addition">+pop %rax</span></span><br><span class="line"><span class="addition">+jnz 1f</span></span><br><span class="line"><span class="addition">+vmcall</span></span><br><span class="line"><span class="addition">+jmp 2f</span></span><br><span class="line"><span class="addition">+1:</span></span><br><span class="line"><span class="addition">+ syscall</span></span><br><span class="line"><span class="addition">+2:</span></span><br><span class="line"> popq%rdi/* Reload %rdi, adjust stack. */</span><br><span class="line"> cfi_adjust_cfa_offset(-8)</span><br><span class="line"> cmpq$-4095, %rax/* Check %rax for error. */</span><br></pre></td></tr></table></figure><p><code>eglibc-2.14/libc/sysdeps/unix/sysv/linux/x86_64/swapcontext.S</code>主要完成的工作参考函数<code>rt_sigprocmask (SIG_BLOCK, newset, oldset, _NSIG/8)</code>,就是用<code>newset</code>替换原来的<code>oldset</code>,补丁也是直接替换的<code>syscall</code>.</p><h1 id="DO-CALL"><a href="#DO-CALL" class="headerlink" title="DO_CALL"></a><code>DO_CALL</code></h1><p><code>eglibc-2.14/libc/sysdeps/unix/sysv/linux/x86_64/sysdep.h</code>文件</p><p>所有的系统调用都会通过<code>DO_CALL</code>发起</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"># <span class="meta-keyword">undef</span>DO_CALL</span></span><br><span class="line"><span class="meta"># <span class="meta-keyword">define</span> DO_CALL(syscall_name, args)\</span></span><br><span class="line"> DOARGS_#<span class="meta">#args\</span></span><br><span class="line"> movl $SYS_ify (syscall_name), %eax;\</span><br><span class="line"> syscall;</span><br></pre></td></tr></table></figure><p>再往下,<code>INTERNAL_SYSCALL_NCS</code>也调用了<code>syscall</code></p><p><code>LOAD_ARGS_##nr</code>负责把参数<code>args</code>展开,<code>LOAD_REGS_##nr</code>则把对应的参数设置到对应的寄存器中,然后调用<code>syscall</code>。(Q:这个<code>INTERNAL_SYSCALL_NCS</code>和上面的<code>DO_CALL</code>区别在哪里?</p><figure class="highlight hpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"># <span class="meta-keyword">define</span> INTERNAL_SYSCALL_NCS(name, err, nr, args...) \</span></span><br><span class="line"> ({ \</span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">long</span> <span class="keyword">int</span> resultvar; \</span><br><span class="line"> LOAD_ARGS_##nr (args) \</span><br><span class="line"> LOAD_REGS_##nr \</span><br><span class="line"> <span class="keyword">asm</span> <span class="keyword">volatile</span> ( \</span><br><span class="line"> <span class="string">"syscall\n\t"</span> \</span><br><span class="line"> : <span class="string">"=a"</span> (resultvar) \</span><br><span class="line"> : <span class="string">"0"</span> (name) ASM_ARGS_##nr : <span class="string">"memory"</span>, <span class="string">"cc"</span>, <span class="string">"r11"</span>, <span class="string">"cx"</span>); \</span><br><span class="line"> (<span class="keyword">long</span> <span class="keyword">int</span>) resultvar; })</span><br><span class="line"><span class="meta"># <span class="meta-keyword">undef</span> INTERNAL_SYSCALL</span></span><br><span class="line"><span class="meta"># <span class="meta-keyword">define</span> INTERNAL_SYSCALL(name, err, nr, args...) \</span></span><br><span class="line"> INTERNAL_SYSCALL_NCS (__NR_##name, err, nr, ##args)</span><br></pre></td></tr></table></figure><h1 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h1><ul><li><a href="https://blog.csdn.net/weixin_42250655/article/details/102533048" target="_blank" rel="noopener">clone()函数及其与Linux线程实现的关系</a></li><li><a href="https://man7.org/linux/man-pages/man2/rt_sigprocmask.2.html" target="_blank" rel="noopener">rt_sigprocmask</a></li><li><a href="https://cloud.tencent.com/developer/article/1492374" target="_blank" rel="noopener">syscall分析</a></li></ul>]]></content>
<summary type="html">
<p>本篇文章是阅读<code>eglibc-2.14</code>中与<code>syscall</code>相关部分的代码解析,由于本人的GCC汇编很烂,所以解释的比较详细。</p>
</summary>
<category term="Linux" scheme="http://yoursite.com/categories/Linux/"/>
<category term="Linux" scheme="http://yoursite.com/tags/Linux/"/>
</entry>
<entry>
<title>五月的生活</title>
<link href="http://yoursite.com/2021/05/09/2021-05-09-%E4%BA%94%E6%9C%88%E7%9A%84%E7%94%9F%E6%B4%BB/"/>
<id>http://yoursite.com/2021/05/09/2021-05-09-五月的生活/</id>
<published>2021-05-09T10:35:48.000Z</published>
<updated>2021-05-28T08:53:39.534Z</updated>
<content type="html"><![CDATA[<p>胡言乱语</p><a id="more"></a><p>想迁移到Jekyll+github,结果在上传我自己的blog的时候一直上传不上去,我要不就凑合着这个hexo+github好了</p><p>新加的两篇blog图片都显示不出来:我累了,今天先不解决了。</p><p>更新:解决了,出现的问题在于我的乱改名,很多解决方案中说的一定要放图片的文件夹和blog文章的title一致是有原因的,可以通过查看blog源代码发现hexo在解析图片的时候会将图片的链接变成blog名/img名,所以存放的文件夹名和blog名不一致就会解析不成功图片,进而无法显示图片。(虽然花了一点时间,但解决问题的感觉还挺好的hhh)</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git config --global http.proxy</span><br><span class="line">git config --global --unset http.proxy</span><br></pre></td></tr></table></figure><p>接下来就是重复劳动了,今天就先不改了,下次再改~</p><p>一直没更新的原因一方面是我没学到啥,另外一方面是常年挂梯子导致<code>hexo d</code>的时候报错</p><blockquote><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">> <span class="type">Error</span>: <span class="type">Spawn</span> failed</span><br><span class="line">> at <span class="type">ChildProcess</span>.<anonymous> (<span class="type">D</span>:\git\blog\node_modules\hexo-util\lib\spawn.js:<span class="number">52</span>:<span class="number">19</span>)</span><br><span class="line">> at <span class="type">ChildProcess</span>.emit (events.js:<span class="number">203</span>:<span class="number">13</span>)</span><br><span class="line">> at <span class="type">ChildProcess</span>.cp.emit (<span class="type">D</span>:\git\blog\node_modules\cross-spawn\lib\enoent.js:<span class="number">40</span>:<span class="number">29</span>)</span><br><span class="line">> at <span class="type">Process</span>.<span class="type">ChildProcess</span>._handle.onexit (<span class="keyword">internal</span>/child_process.js:<span class="number">272</span>:<span class="number">12</span>)</span><br><span class="line">></span><br></pre></td></tr></table></figure></blockquote><p>通过参考<a href="https://www.jianshu.com/p/f7feda035dc9" target="_blank" rel="noopener">这篇</a>,终于修好了。</p><p>回去看体系结构了!(考完并且写完了报告√)</p><p>我的blog排序很混乱,本地存储的时候考虑加上时间(改好了√)。</p><p>blog争取周更(做梦(感谢周报,连续周更了两周,好耶!)</p><p>要改一下yillia默认的英文字体和中文字体</p><p>这周末看完体系结构安全的论文</p>]]></content>
<summary type="html">
<p>胡言乱语</p>
</summary>
<category term="日记" scheme="http://yoursite.com/categories/%E6%97%A5%E8%AE%B0/"/>
<category term="日记" scheme="http://yoursite.com/tags/%E6%97%A5%E8%AE%B0/"/>
<category term="生活" scheme="http://yoursite.com/tags/%E7%94%9F%E6%B4%BB/"/>
</entry>
<entry>
<title>HypSec-Usenix19</title>
<link href="http://yoursite.com/2021/04/22/2021-05-09-HypSec-Usenix19/"/>
<id>http://yoursite.com/2021/04/22/2021-05-09-HypSec-Usenix19/</id>
<published>2021-04-22T02:27:15.000Z</published>
<updated>2021-05-28T13:20:17.557Z</updated>
<content type="html"><![CDATA[<p>Protecting Cloud Virtual Machines from Hypervisor and Host Operating System Exploits</p><a id="more"></a><h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><ul><li>an untrusted host: perform most complex hypervisor functionality without access to virtual machine data</li><li>a trusted core: provide access control to virtual machine data and perform basic CPU and memory virtualization </li></ul><h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>目前,应用程序和硬件虚拟化的几个设计提供了很好的机会来解决安全问题: </p><ul><li>modern hardware includes virtualization support to protect and run the hypervisor at a higher privilege level than VMs</li><li>applications are increasingly designed to use end-to-end encryption for I/O channels (应用程序可以提供更好的I/O通信加密,所以可以减少hypervisor的工作)</li></ul><p>本文的思路:分成两部分</p><p><strong>corevisor</strong> (a small trusted core):</p><ul><li>has full access to hardware resources(VM data)</li><li>provides basic CPU and memory virtualization</li><li>mediates all exceptions and interrupts</li><li>ensures that only a VM and the corevisor can access the VM’s data in CPU and memory</li></ul><p><strong>hostvisor</strong> (a large untrusted hos):</p><ul><li>I/O and interrupt virtualization</li><li>resource management such as CPU scheduling, memory management, and device management </li><li>can leverage a host OS</li><li>may import or export encrypted VM data from the system to boot VM images or support hypervisor features such as snapshots and migration, but otherwise has no access to VM data</li></ul><h2 id="3-Design"><a href="#3-Design" class="headerlink" title="3 Design"></a>3 Design</h2><h3 id="3-1-Boot-and-Initialization"><a href="#3-1-Boot-and-Initialization" class="headerlink" title="3.1 Boot and Initialization"></a>3.1 Boot and Initialization</h3><p><strong>Corevisor boot</strong></p><p>HypSec relies on the hostvisor bootstrapping code to install the corevisor securely at boot time since the hostvisor is initially benign.</p><p>在安装完毕后,corevisor获得硬件的所有控制权,并且deprivileges the hostvisors</p><p><img src="/2021/04/22/2021-05-09-HypSec-Usenix19/image-20210409113004021.png" alt="image-20210409113004021"></p><p>当新的VM创建的时候:</p><ul><li>hostvisor calls VM CREATE to request the corevisor to allocate VM state in corevisor memory, including an NPT and VCPU state, a per virtual CPU(VCPU) data structure.</li><li>hostvisor calls VM BOOT to request the corevisor to authenticate the loaded VM image.</li><li>if loaded successfully, the hostvisor can then call VM ENTER to execute the VM</li></ul><p>hostvisor存储VM images并且将他们加载到内存中,而corevisor负责的是验证VM images的公钥签名。</p><h3 id="3-2-CPU"><a href="#3-2-CPU" class="headerlink" title="3.2 CPU"></a>3.2 CPU</h3><p>Hypervisors通过执行以下四个主要功能来提供CPU虚拟化:</p><ul><li>handling traps from the VM</li><li>emulating privileged CPU instructions executed by the guest OS to ensure the hypervisor retains control of CPU hardware</li><li>save and restore VM CPU state, including GPRs and system registers such as page table base registers, as needed when switching among VMs and between a VM and the hypervisor</li><li>scheduling VCPUs on physical CPUs</li></ul><p>为了让TCB尽可能的小,限制访问corevisor的VM CPU,corevisor需要干的活:handle all traps from the VM, instruction emulation, and world switches between VMs and the hostvisor;而VCPU调度可以由hostvisor来完成。</p><p>corevisor maintains VCPU execution context in the VCPU state in-memory data structure(该data structure是由VM CREATE分配的)</p><p>corevisor maintains the hostvisor’s CPU context in a similar Host state data structure.</p><p>所有的保存和恢复VM CPU state都是由corevisor来完成的,有且仅有corevisor能够运行VM。</p><p>当VM执行的指令需要hostvisor的sharing values时,这些值可以放在GPR(genral purpose registers)。由corevisor基于正在执行的CPU指令决定是否以及什么时候从GPRs中复制值,以及复制什么值。</p><h3 id="3-3-Memory"><a href="#3-3-Memory" class="headerlink" title="3.3 Memory"></a>3.3 Memory</h3><p>Hypervisor通过执行以下三个功能来提供memory virtualization:</p><ul><li>memory protection to ensure VMs cannot access unauthorized physical memory</li><li>memory allocation to provide physical memory to VMs</li><li>memory reclamation to reclaim physical memory from VMs</li></ul><p>guest OS 通过page tables将gVA映射到gPA,然后由hypervisor管理NPT将gPAs(guest physical memory address)映射到hPA(host physical memory address)</p><p>HypSec保护VM内存免受hostvisor的破坏,而将TCB大小控制的很小,尽可能地分隔corevisor的大小</p><ul><li>corevisor负责内存保护,包括configure NPT hardware</li><li>hostvisor负责memory allocation and reclamation</li></ul><p><strong>Memory protection</strong></p><p>corevisor通过hNPT(host Nested Page Table)将vhPA转化成hPA,每个vhPA都被映射到一个确定的hPA。hostvisor负责的是hVAs到vhPAs的转换(也就是虚拟机中的虚拟地址到物理地址的转换),hostvisor是没有权限来访问NPT,一旦尝试非法映射,corevisor就会中断映射。hostvisor中的地址转换是动态的,而hostvisor和corevisor之间的地址分配是静态的。 </p><p><img src="/2021/04/22/2021-05-09-HypSec-Usenix19/image-20210410103736933.png" alt="image-20210410103736933"></p><p><strong>Memory allocation</strong></p><ul><li>step1: 当guest OS尝试将gVA映射到一个unmapped gPA,就会发生一个nested page fault,陷入到corevisor中;</li><li>step2: faulted gPA如果是一个合法的VM内存地址,将NPT Base Register指向HNPT</li><li>step3: 切换到hostvisor,为gPA分配physical page</li><li>step4: 切换到corevisor,验证分配的vhPA没有被分配给其他的VM(通过VMID),并经更新更新到sNPT</li><li>corevisor更新NPT Base Register指向sNPT</li><li>corevisor返回VM</li></ul><p>(文章中提到<em>The corevisor does not shadow guest OS updates in its page table; it only shadows hostvisor updates to the vNPT.</em>这部分是指shadow的内容比较少?shadow page table的作用其实就是记录GVA->HPA的映射关系)</p><p><strong>Memory Reclamation</strong></p><p>corevisor负责将回收到的内存清楚然后分配给需要的VM。</p><p><strong>Advanced VM Memory Management</strong></p><p>当VM希望和hostvisor分享内存时,可以调用GRANT_MEM和REVOKE_MEM并传递相关的参数,corevisor会通过控制hNPT中的memory region’s mapping来实施访问控制策略。</p><p>merging similar memory pages (KSM)</p><h3 id="3-4-Interrupts"><a href="#3-4-Interrupts" class="headerlink" title="3.4 Interrupts"></a>3.4 Interrupts</h3><p>corevisor configures the hardware to route all physical interrupts and trap all accesses to the interrupt controller to the corevisor.</p><p>hostvisor处理大部分的中断功能:handle physical interrupts and provide the virtual interrupt controller interface(在hostvisor处理中断后,corevisor保护所有的VM CPU以及内存状态)</p><p>在一次MMIO write中</p><ul><li>VM passes the value to be stored in a GPR</li><li>the write traps to the corevisor, which identifies the instruction and memory address</li><li>corevisor copies the value to be written from the GPR to the intermediate VM state to make the value valuable to the hostvisor</li></ul><h3 id="3-5-Input-Output"><a href="#3-5-Input-Output" class="headerlink" title="3.5 Input/Output"></a>3.5 Input/Output</h3><p>modern hypervisors通常依赖于OS kernel以及对应的设备驱动来支持I/O virtualization,这增加了hypervisor的TCB。</p><p>HypSec中介绍了一种end-to-end I/O安全方法,依靠VMs来进行I/O保护(TLS/SSL来保护网络通信,全磁盘加密用于存储)。</p><p>passthrough I/O:corevisor控制IOMMU进行inter-device isolation,并保证passthrough device无法访问到VM自己的I/O buffer.</p><h2 id="4-Implementation"><a href="#4-Implementation" class="headerlink" title="4. Implementation"></a>4. Implementation</h2><p>secure DMA: corevisor uses trap-and-emulate on hostvisor accesses to the SMMU</p><p>virtualize interrupts: 利用hardware features from the VGIC and KVM/ARM’s existing support (support emulated device via MMIO, paravirtualized device via virtio, and passthrough devices)</p><p>secure boot:ARM TrustZone-based framework such as OP-TEE</p><p>Ed25519来验证boot images</p><p>AES:support encrypted VM migration and snapshort </p><p><img src="/2021/04/22/2021-05-09-HypSec-Usenix19/image-20210412165136608.png" alt="image-20210412165136608"></p><p>关于Dune论文</p><p>用硬件特性<code>VT-x</code>来实现普通进程。<code>VT-x</code>是X86下的虚拟扩展,管理的进程有自己的<code>CR3</code>寄存器(guest root mode),让不可信的进程通过<code>VT-x</code>扩展跑在guest root mode,该进程也无法从分配的内存中逃逸。</p><p>SGX中存在的问题,在于使用的page table是和非enclave共用的,如果SGX跑在<code>VT-x</code>模式下,是guest non-root下的page table是不是能减少一些侧信道攻击的问题?</p>]]></content>
<summary type="html">
<p>Protecting Cloud Virtual Machines from Hypervisor and Host Operating System Exploits</p>
</summary>
<category term="OS" scheme="http://yoursite.com/categories/OS/"/>
<category term="OS" scheme="http://yoursite.com/tags/OS/"/>
<category term="virtualization" scheme="http://yoursite.com/tags/virtualization/"/>
</entry>
<entry>
<title>Browser-NDSS17</title>
<link href="http://yoursite.com/2021/03/30/2021-05-09-Browser-NDSS17/"/>
<id>http://yoursite.com/2021/03/30/2021-05-09-Browser-NDSS17/</id>
<published>2021-03-30T07:03:10.000Z</published>
<updated>2021-05-21T12:07:23.550Z</updated>
<content type="html"><![CDATA[<p>(Cross-) Browser Fingerprinting via OS and Hardware Level Features</p><p>春季学期web追踪前沿分享的论文,由于本人的web知识浅薄,整理的笔记难免有偏颇的地方。</p><p>基于操作系统和硬件特征来进行单浏览器和跨浏览器指纹追踪。</p><a id="more"></a><p>首先从摘要部分,我们从整体层面看一下这篇论文所完成的工作。</p><h2 id="Abstract"><a href="#Abstract" class="headerlink" title="Abstract"></a>Abstract</h2><p>提出了一种browser fingerprinting technique——不仅能够在单个浏览器中追踪用户信息,并且能够在同一个机器上的不同浏览器中。</p><p>本文利用了多个novel OS 和硬件级别的特征(比如graphics cards, CPU, installed writing scripts) 。作者通过让浏览器执行依赖于这些OS和硬件级别的功能来提取出对应的特征。</p><p>评估结果表明本文方法能够成功鉴别99.24%的用户,而现有的single-browser fingerprinting技术在相同的数据集上只有90.84%成功率。</p><p>并且,与文献中有相似稳定性的cross-browser approach相比,本文的方法能够获得更高的uniqueness(更高的唯一性)</p><h2 id="1-Introduction"><a href="#1-Introduction" class="headerlink" title="1 Introduction"></a>1 Introduction</h2><p>web tracking:用于记录past website visitors。</p><p>一方面,该技术能够认证用户(多种不同的技术结合能够用于multi-factor authentication来增强安全性);另一方面,该技术能够用于实现personalized service(如果该服务是targeted ads,那么这种web tracking技术就是一种侵犯隐私的行为)</p><p>不管我们是否喜欢web tracking或者该技术是否被合法地使用,Alex Top 500 websites中的多余90%的网站采用该技术。</p><p>(第一段,简单地介绍了该技术)</p><p><strong>“浏览器指纹”是一种通过浏览器对网站可见的配置和设置信息来跟踪Web浏览器的方法</strong>,浏览器指纹就像我们人手上的指纹一样,具有个体辨识度,只不过现阶段浏览器指纹辨别的是浏览器。</p><p>人手上的指纹之所以具有唯一性,是因为每个指纹具有独特的纹路、这个纹路由凹凸的皮肤所形成。每个人指纹纹路的差异造就了其独一无二的特征。</p><p>那么浏览器指纹也是同理,获取浏览器具有辨识度的信息,进行一些计算得出一个值,那么这个值就是浏览器指纹。辨识度的信息可以是UA、时区、地理位置或者是你使用的语言等等,你所选取的信息决定了浏览器指纹的准确性。</p><p><strong>web tracking的发展</strong></p><p>first-generation: 采用stateful, server-set identifiers,比如cookies和evercookies(需要用户登陆才可以得到有效的信息)</p><p>second-generation:利用stateless identifiers,比如plug-in versions和已经存在了browsers中的user agent。(有了浏览器指纹的概念,通过不断增加浏览器的特征值从而让用户更具有区分度,例如(UA、浏览器插件信息)</p><p>前两代tracking技术都被限制在a single browser中,本篇论文提出来的2.5-generation technique能在同一台机器上的相同乃至不同浏览器中追踪用户。经过调查,70%的被调查用户都有在一台电脑上安装和频繁使用至少两个浏览器的习惯。</p><p>本文所提出的2.5-generation technique</p><ul><li>被用作part of stronger multi-factor user authentications even across browsers(更有力的(跨浏览器)多因素用户认证的一部分);</li><li>improve existing privacy-preserving works(保护隐私)</li></ul><p><strong>技术方法</strong></p><p>如果想要在相同的机器上fingerprint不同 的浏览器——利用existing features。但是很多existing features都是browser specific(特定于浏览器的),cross-browser能够让特征稳定但是得到的fingerprint不够unique。</p><p>这也是唯一一篇cross-browser的工作采用了IP地址作为main feature,但是采用IP地址只是一个network-level feature(已经被排除在了现在流行的browser printing中),如果IP地址是被动态分配的,那么还会经常变,也无法获得位于匿名网络或者代理之后的数据。</p><p><strong>本文方法</strong></p><p>本文提出的方法基于很多novel OS以及硬件级别的特征(from graphics card, CPU, audio stack以及installed writing scripts)。具体来说,当请求浏览器通过浏览器APIs执行特定任务时,这些OS和硬件级别的功能都会被这些APIs公开给JavaScript,我们就可以提取这些特征(这些特征能够被用于single/cross-browser fingerprinting)。</p><p>WebGL:在browser canvas object中实现的3D部分。虽然该特征一致被认为“too brittle and unreliable”(因为AmIUnique选择了一个任意的WebGL task,并没有约束变量),但本文展示了WebGL不仅能用于single也可以用于cross-browser fingerprinting。具体来说,让browser用精心挑选的computer graphics parameters渲染了20多个任务,比如纹理,反锯齿,光线,透明度,然后从这些渲染任务的输出中提取出特征。</p><p><strong>本文贡献</strong></p><ul><li>第一个在single-和cross-browser fingerprinting中使用多个novel OS以及hardware features。本篇论文中的方法能够成功fingerprint(定位?)99.24%的用户(而AmIUnique只有90.84%);另外,本方法能够在91.44%的cross-browser stability上达到83.24%的uniqueness。</li><li>本文在single-和cross-browser fingerprinting上进行了一些有趣的观察。以往工作中的screen resolution(屏幕分辨率)是不稳定的,因为当用户缩小放大网页时,屏幕分辨率会变化。所以作者将缩放比例纳入考虑范围,并且标准化屏幕分辨率的长宽。另外,DataURL和JPEG格式在不同浏览器中是不稳定的,这是因为这些格式有损且在不同的浏览器和服务端都有不同的实现。因为在cross-browser fingerprinting中,需要使用无损格式进行server-client通信。</li></ul><p>第二部分:介绍所有的features,包括从AmIUnique中采用的以及修改的老方法和本文采用的新方法;</p><p>第三部分:介绍本文browser fingerprinting的设计,包括总体架构,渲染任务以及掩膜生成(mask generation)</p><p>第四部分:讨论完成方法</p><p>第五部分:数据收集</p><p>第六部分:评估方法以及展示结果</p><p>第七部分:讨论针对本文的fingerprinting的抵御方法</p><p>其余:伦理问题,相关工作以及结论</p><h2 id="2-fingerprintable-features"><a href="#2-fingerprintable-features" class="headerlink" title="2 fingerprintable features"></a>2 fingerprintable features</h2><p>single-browser fingerprinting的features没有限制,但cross-browser features需要反映OS和硬件级别的信息和操作。OS和硬件级别的这些特征要相对来说更稳定,毕竟所有的浏览器都是跑在相同的OS和硬件上。(比如,顶点着色器和片段着色器都暴露了GPU和OS中的GPU driver的特征)</p><p>若某个操作的输出由浏览器和底层(OS和硬件)共同贡献(contributed),那么也可以利用该特征用于single-browser fingerprinting,但在cross-browser fingerprinting中需要剔除browser factor。</p><h3 id="A-prior-fingerprintable-features"><a href="#A-prior-fingerprintable-features" class="headerlink" title="A. prior fingerprintable features"></a>A. prior fingerprintable features</h3><p>源自<em>AmIUnique</em>论文中</p><p>4种特征:screen resolution, color depth, list of fonts, platform.</p><h3 id="B-old-features-with-major-modifications"><a href="#B-old-features-with-major-modifications" class="headerlink" title="B. old features with major modifications"></a>B. old features with major modifications</h3><p><strong>Screen Resolution</strong></p><p>目前针对屏幕分辨率的测量是通过JavaScript中的”screen”对象。但是部分浏览器会根据缩放比例变化屏幕分辨率。</p><p>根据已有的工作()根据div tag的大小和设备pixel ratio,然后相应地调整屏幕分辨率。另外,为了更好地保证可靠性,增加了一个特征——屏幕宽度和高度之间的比率,该特征不会随缩放级别而改变。</p><p>一些其他的属性,可用于single- and cross-browser fingerprinting.</p><p>availHeight, availWidth, availLeft, availTop:代表除浏览器菜单和MacOSSierra的工具栏以外等系统区域以外的可用屏幕。</p><p>screenOrientation:用于表示屏幕的位置,是横屏还是竖屏以及该屏幕是否上下颠倒。</p><p><strong>number of CPU virtual Cores</strong></p><p>内核数目可以通过a new browser feature——hardwareConcurrency来获得,该信息能够提供Web Workers的性能信息。不支持该功能的浏览器可以通过侧信道来获得该数据。</p><p>具体来说,我们可以在增加web workers数目的同时监视payload的结束时间。当增加web workers到一定数目时,finishing time增加显著,表明这时候已经达到了hardware concurrency的极限,根据该信息就可以获得内核数目。(部分浏览器比如Safari可能只会给web workers分一半的内核,这时候只需要将内核数目乘2即可)</p><p>web workers:使得一个web应用程序可以在与主执行线程分离的后台线程中运行一个脚本操作。(可以在独立的线程中执行费时的处理任务,从而允许主线程运行时不被堵塞)</p><p>payload:一段代码</p><p>(HardwareConcurrency的inventor清楚该功能是fingerprintable的,所以并没有给他取名叫cores,但在之前的相关研究中,该特征从未被使用)</p><p><strong>AudioContext</strong></p><p>在OS中的音频堆栈(audio stack)和声卡的帮助下,AudioContext提供了包括从信号生成到信号过滤的一系列的音频信号处理功能。</p><p>已有的fingerprinting工作(Online tracking: A 1-million-site measurement and analysis) 使用振荡节点(OscillatorNode)来产生一个三角波(triangle wave),然后将波注入DynamicsCompressNode,这是一个信号处理模块,能够降低响亮的声音并放大安静的声音(也就是产生一个压缩效果)。然后将处理后的音频信号通过AnalyserNode转化到频域。</p><p>频域中的波在同样的机器上不同的浏览器是不一样的。但作者观察发现peak values(峰值)以及对应的频率在不同的浏览器中都是相对稳定的。</p><p>于是,作者在频率轴和值轴上构造了一个具有small steps(小步进)的容器列表,并且将峰值频率和峰值值映射到对应的容器中。如果一个bin中包含一个频率或者值,就将该容器标记为1,否则为0.这样的bin列表能够作为cross-browser特征。(<em>Therefore, we create a list of bins with small steps on both the frequency and value axes, and map the peak frequencies and values to the corresponding bins. If one bin contains a frequency or value, we mark the bin as one and otherwise zero: such list of bins serve as our cross-browser feature.</em>)(它虽然讲了这么多,但我对它的理解就是把峰值的频率和值拿出来作为一个特征啊。。。那为啥要有这么多容器呢?)</p><p>除了上述的波处理,作者从目标音频设备中获取以下信息:sample rate(采样率), max channel count(最大通道计数), number of inputs(输入数), number of outputs(输出数), channel count(通道计数), channel count mode(通道计数模式), channel interpretation(通道翻译).(已有的工作都没有用过这些音频特征作为浏览器指纹)</p><p><strong>List of Fonts</strong></p><p>采用(Cookieless monster: Exploring the ecosystem of web-based device fingerprinting)这篇文献中提到的侧信道方法:通过测量某个字符串的宽高来确定字体类型。</p><p>并不是所有的字体都是cross-browser fingerprintable,因为一些字体是特定于web的,并由浏览器提供,所以我们需要应用第三章C部分中的掩码来选择合适的子集。</p><p>(为什么不用flash,因为现在flash逐渐退出历史舞台,很多字体也不需要flash支持;文献20提供了43种字体的子集来fingerprinting,但由于他们的工作是基于single-browser fingerprinting的,所以不考虑)</p><h3 id="C-Newly-proposed-Atomic-Fingerprintable-Features"><a href="#C-Newly-proposed-Atomic-Fingerprintable-Features" class="headerlink" title="C. Newly-proposed Atomic Fingerprintable Features"></a>C. Newly-proposed Atomic Fingerprintable Features</h3><p>atomic: 意味着browser公开一个API或一个component给JavaScript。</p><p><strong>直线,曲线,反锯齿</strong></p><p>直线和曲线都是Canvas(2D部分)和WebGL。反锯齿是一种在单线/曲线对象或者是计算机图形模型边缘中的通过平滑锯齿来减少锯齿(锯齿就是锯齿或者阶梯状的线)。</p><p>现有的用于反锯齿的方法包括first-principles, approach, signal processing approach, mipmapping,这些方法能够让反锯齿变得fingerprintable。</p><p><strong>顶点着色器</strong></p><p>顶点着色引擎(vertex shader) 由GPU和驱动渲染,将一个3D模型中的每个顶点转化成2D裁剪空间中的坐标。顶点着色器能够以三种方式接收数据:</p><ul><li>attributes from buffers (来自缓冲区中的属性)</li><li>uniforms that always stay the same(始终保持一致的数据)</li><li>texture from fragment shader(片段着色器中的纹理)</li></ul><p>当渲染计算机图形任务时,顶点着色器通常与片段着色器相结合。</p><p><strong>片段着色器</strong></p><p>一个片段着色器,由GPU和驱动程序渲染,处理一个片段,比如将一个三角形栅格化输出到一组颜色和一个单独的深度值。</p><p>(片元着色器的作用是处理由光栅化阶段生成的每个片元,最终计算出每个像素的最终颜色。归根结底,实际上就是数据的集合。这个数据集合包含每一个像素的各个颜色分量和像素透明度的值。)</p><p>WebGL通过如下方式获取数据:</p><ul><li>Uniforms:在一次draw call中,一个片段中的每个像素的uniform value都保持一致。因为,uniforms是不可追踪(non-fingerprintable)的特征,在这儿列举出来仅仅是为了完整性</li><li>Varying:varying从顶点着色器传递值到片段着色器,片段着色器在这些值中进行插值并栅格化片段,比如在fragment中绘制每个像素。插入的值在不同的计算机显卡中是不同的,因此,varying也是fingerprintable的。</li><li>Textures:给定顶点和纹理之间的映射,片段着色器可以计算出基于该纹理的每个像素值。由于纹理的分辨率有限,片段着色器需要基于目标周围的纹理像素值为目标像素插入值。在不同的显卡上,纹理插入算法有所差异,这使得texture是fingerprintable的。texture可以被进一步分为:(1)normal texture(上述提到的);(2)depth texture(一种包含了每个像素的深度值的texture);(3)animating texture(动画纹理,一种包含了视频帧而不是静态图像的纹理);(4)压缩纹理(一种接收压缩格式的纹理)</li></ul><p><strong>transparency via alpha channel</strong></p><p>transparency: 一种由GPU和driver提供的特征,允许背景和前景混合。具体来说,alpha channel中的值在0到1之间,使用合成代数将背景和前景图像合成一个单一的,最终图像。</p><p>alpha channel中的两个指纹点(fingerprinting point):</p><ul><li>使用single alpha value来fingerprint背景和前景的合成算法</li><li>当alpha value的值从0增加到1时,fingerprint透明度效果的变化。(因为一些显卡采用离散的alpha values,当变化透明度效果时,可能能够观察到some jumps)</li></ul><p><strong>图像编码和解码</strong></p><p>PNG:无损格式;JPEG:压缩有损格式。</p><p>对压缩图像的解压是一种fingerprintable feature,因为不同的算法在解压过程中会泄露不同的信息。在本文中,这是一种single-browser feature,并不能被cross-browser使用。</p><p><strong>installed writing scripts(languages)</strong></p><p>writing scripts就是可写语言,比如中文,韩文或者阿拉伯文需要安装一些特殊的lib,虽然浏览器不能提供访问安装语言的API,但是一些信息能够通过侧信道获得。</p><p>具体来说,安装了特定语言的浏览器会正确地显示语言,否则就会显示several boxes。也就是boxes的存在能够被用于fingerprint该语言的存在。</p><h3 id="D-Newly-proposed-Composite-Fingerprintable-Features"><a href="#D-Newly-proposed-Composite-Fingerprintable-Features" class="headerlink" title="D. Newly-proposed Composite Fingerprintable Features"></a>D. Newly-proposed Composite Fingerprintable Features</h3><p>composite:也就意味着公开一个或多个API给JavaScript,有时需要使用基于browser APIs的额外算法构造。</p><p><strong>Modeling and multiple models</strong></p><p>建模也就是本文中的3D建模,是一种通过三维曲面对物体进行数学描述的计算机图形过程。模型的顶点由顶点着色器处理,表面由片段着色器处理。不同对象由不同模型表示,并且在下述技术的作用下(比如光)可能会相互作用。</p><p><strong>Lighting and shadow mapping</strong></p><p>light: 计算机图形中对灯光效果的模拟</p><p>shadow mapping:测试一个像素在一定光照下是否可见,并增加相应的阴影。</p><p>光有很多种类,其区别在于光的来源。另外,很多效果都是和光共同作用产生的,当光和一个或多个计算机图形模型共同作用时,会产生比如反射,半透明,光跟踪,间接照明。</p><p>WebGL没有给lights和shadow提供直接的APIs,但是一些WebGL lib提供了构建在WebGL上的顶点和片段着色器的high-level API。</p><p><strong>Camera</strong></p><p>针孔摄像模型,用于将一个空间中的三维点映射到一个图像中的二维点。在WebGL中,a camera由顶点和片段着色器处理的camera projection matrix(投影矩阵)来表示,并且可以用来放大或缩小一个对象。</p><p><strong>Clipping Planes</strong></p><p>切割面。裁剪将渲染操作限制在感兴趣的范围中。在3D渲染中,裁剪平面距离一定距离并且垂直于camera,这样就可以防止渲染离相机太远的表面。</p><p>WebGL中,裁剪平面是由顶点和片段着色器使用额外提供的算法来执行的。</p><h2 id="3-Design"><a href="#3-Design" class="headerlink" title="3. Design"></a>3. Design</h2><h3 id="A-Overall-Architecture"><a href="#A-Overall-Architecture" class="headerlink" title="A. Overall Architecture"></a>A. Overall Architecture</h3><p>第一步:server端的task manager发送多个渲染任务(比如画曲线或者直线)给客户端(这些渲染任务中包含OS和hardware级别的信息,比如屏幕分辨率和时区);</p><p>第二步:client端通过调用一个或多个API完成这些渲染任务,并生成相应的结果(比如图像和声波);</p><p>第三步:这些结果将会被转化为哈希值,从而被方便地发送给server端;同时,浏览器会收集browser-specific信息,比如是否支持反锯齿和压缩纹理,这些都可以被用于server端的fingerprint composition</p><p>第四步:当server在client端收集到了所有的信息,就开始合成指纹。指纹由list of hashes和mask通过“与操作”得到。</p><p>single-browser的指纹值非常直观,是一连串的1. 而cross-browser的指纹值来自两部分。</p><ul><li>收集的浏览器信息:如果该浏览器不支持反锯齿,mask中涉及反锯齿的相关bit位都为0;</li><li>为每个浏览器对设置不同的掩码 </li></ul><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210323155003664.png" alt="image-20210323155003664"></p><h3 id="B-Rendering-Tasks"><a href="#B-Rendering-Tasks" class="headerlink" title="B. Rendering Tasks"></a>B. Rendering Tasks</h3><p>先介绍了一些basic setting,包括坐标设置,环境光[R: 0.3, G: 0.3, B: 0.3];camera位于[0,0,-7]</p><p>不同于AmIUnique,当当前窗口变化时,本文中的canvas设置依然是可靠的。作者从窗口大小,侧栏,缩放比例三个角度进行变化,窗口中的内容以及哈希值都没有发生变化。(哈希值咋算的?)</p><p><strong>Task(a): Texture</strong></p><p>下图为在片段着色器上测试regular texture feature(Suzanne Monkey Head model被渲染为canvas上的randomly-generated texture)。纹理:是一个大小为256*256的正方形,通过为每个像素随机选择一种颜色来创建。(也就是说,在一个像素上位三种原色生成0-255之间的随机值,然后将该颜色作为该像素的颜色)</p><p>这里选择随机生成的纹理而不是一个定值,是因为这样的纹理具有更好的fingerprintable特征。原因是当片段着色器将纹理映射到一个模型中时,片段着色器需要在纹理中插值使得纹理能够被映射到模型中的每个点。在不同的显卡中插入算法不同,而当颜色中的纹理变化明显时算法差异会增大。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210324100038950.png" alt="image-20210324100038950"></p><p><strong>Task(b): Varying</strong></p><p>用于测试canvas上的片段着色器的varying特征。在立方体模型的6个面上绘制不同的颜色,每个面上的4个点也指定上颜色。</p><p>选择这种varying color来提高每个面上的颜色变化和颜色差异(比如某个面有0.9/1的蓝色,那么另一个顶点的蓝色就会比较少,有更多的绿色和红色)。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210324144152727.png" alt="image-20210324144152727"></p><p><strong>Task(b’): Anti-aliasing+Varying</strong></p><p>测试anti-aliasing特征,比如浏览器如何平滑模型的边缘。具体来说,就是采用了Task(b)中相同的工作,并增加了反锯齿。(对比一下b和b‘,b’的边缘是平滑的)</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210324151720029.png" alt="image-20210324151720029"></p><p><strong>Task(c): Camera</strong></p><p>测试相机功能,比如说投射到片段着色器的投射矩阵(projection matrix)。本部分所有的设置和a中相同,不同的在于camera的位置,a中的位置是[0,0,-5], 而在本部分camera中的位置为[-1,-4,-10].</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210324151735549.png" alt="image-20210324151735549"></p><p><strong>Task(d): Lines and Curves</strong></p><p>测试直线和曲线。在一个canvas上有一根曲线和三个不同角度的直线。曲线遵从这样的算式:</p><p>y = 256 - 100cos(2.0pix/100.0) + 30cos(4.0pix/100.0) + 6cos(6.0pix/100.0)</p><p>[0,0]是cavas的左部和顶部,x轴向右增长,y轴向下增长。三根线的起始点和结束点分别为{[38.4, 115.2], [89.6, 204.8]}, {[89.6,89.6], [153.6, 204.8]}, {[166.4, 89.6], [217.6, 204.8]}.</p><p>通过选择这些特定的曲线和直线来测试不同的梯度和形状。</p><p>Task(d’)就是增加了反锯齿版本的Task(d).</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210324154141105.png" alt="image-20210324154141105"></p><p><strong>Task(e): Multi-models</strong></p><p>测试同一个canvas中的不同模型如何相互影响。平行地放置了两个模型(Suzanne model和sofa model)。sofa model也根据Task(a)中描述的步骤生成一个random texture。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210324164542602.png" alt="image-20210324164542602"></p><p><strong>Task(f): Light</strong></p><p>用diffuse(漫射),点光测试了和Suzanne model的相互作用。测试选择的光是白色的,RGB中的每个值都是2,光源位于[3.0,-4.0,2.0]. 选择白色光源是因为single-color light可能会降低texture上的微小差别。光的强度也是谨慎选择的,因为太弱会让模型看不清,太强会让模型变白,降低fingerprintable特征。经过我们的测试,当光强为2时,机器之间的像素差异最大。光源位置是随便选的,这对fingerprinting的结果没有影响。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210324165208181.png" alt="image-20210324165208181"></p><p><strong>Task(g): Light and Models</strong></p><p>本任务是为了测试single light, 散射光,点光和两个模型之间的作用,因为当被point light照射时,一个模型可能会在另一个模型上映射一块阴影。光的设置和Task (f)一样,模型的的设置和Task(e)一样。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210324170858463.png" alt="image-20210324170858463"></p><p><strong>Task(h): Specular Light</strong></p><p>用diffuse point light(散射点光)和另一种不同颜色的specular point light(镜面点光)在两个模型上测试。两种光都位于[0.8, -0.8, -0.8], diffuse point light的RGB为[0.75, 0.75, 1.0], specular light的RGB为[0.8, 0.8, 0.8].</p><p>选择了更近的位置,因为这样离模型更近并且有更好的效果。沙发模型的背面有被specular point light照亮的部分。虽然diffuse point light看起来照着蓝色,但其中也有很大面积的红色和绿色。虽然希望测试其他颜色,但白色仍然是最适合fingerprinting的颜色。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210324191453598.png" alt="image-20210324191453598"></p><p><strong>Task(h’): Anti-aliasing+Specular Light</strong></p><p>反锯齿版本的Task(h’)</p><p><strong>Task(h’’): Anti-aliasing+Specular Light+Rotation</strong></p><p>Task(h’)版本旋转90度</p><p><strong>Task(i): Two Textures</strong></p><p>本部分测试两个不同的纹理映射到同一个物理上的效果。和Task(h)其他的设置都一致,但将另一层随机生成的texture映射到Suzanne和sofa模型。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210324205735708.png" alt="image-20210324205735708"></p><p><strong>Task(j): Alpha</strong></p><p>将Suzanne模型和sofa模型平行放置,通过8个不同的alpha值: {0.09, 0.1, 0.11, 0.39, 0.4, 0.41, 0.79, 1}来测试产生效果,其中0意味着完全透明,1完全不透明。</p><p>选择这样一个值集来表示不同的alpha值:{0.1, 0.4, 0.8},值的变化在0.01,选择这个数是因为部分GPU不支持更小的steps。另一方面,Suzanne和sofa模型以这样的方位摆放部分由交叠,当Suzanne模型透明的时候,sofa模型部分被遮盖的部分能够变得可见。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210324210351161.png" alt="image-20210324210351161"></p><p><strong>Task(k): Complex Lights</strong></p><p>测试复杂的光线特征,比如反射,移动光源以及多个模型之间的光线跟踪。具体来说,生成5000个金属环,这些金属环以随机的角度堆在地上。为了可靠性,使用一个随机数生成器,每次在不同的浏览器和机器上使用相同的随机数种子进行重复测试。</p><p>照向底部的黄色和红色的点光源,在整个场景的右上方角落部分缠绕。当光照亮底部的圆环时,其他的圆环通过反射被照亮,两种不同的光同时也被混合在一起。(这里选择单色光是因为模型不是多彩的,所以有颜色的光能够照出更多环上的细节,另外,不同的光之间的交互会产生更多的细节)</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210324212814603.png" alt="image-20210324212814603"></p><p><strong>Task(k’): Anti-aliasing+Complex Lights</strong></p><p>反锯齿版本的Task(k)</p><p><strong>Task(l): Clipping Plane</strong></p><p>测试裁剪平面的移动以及FPS。具体来说,将一个静态的正四面体放在地上,用准直光照射它,并移动裁剪平面,让观察者感觉四面体在移动。(当移动下图的位置时,四面体是颠倒的)</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210324212829829.png" alt="image-20210324212829829"></p><p><strong>Task(m): Cubemap Texture+Fresnel Effect(立方图纹理+菲涅尔效应)</strong></p><p>测试光折射中的立方图纹理和菲涅尔效应。</p><p>cubemap texture是一种特殊的纹理,利用一个立方体上的6个面作为map shape(图形映射)</p><p>fresnel effect是基于观察角度时所观察到的反射光数量。</p><p>在本文的实验中,用普通的校园场景构造了一个立方体纹理,并且在该纹理上放置了几个透明泡泡用于菲涅尔效应。所以泡泡随机移动并且相互碰撞。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210324212841814.png" alt="image-20210324212841814"></p><p><strong>Task(n): DDS Textures</strong></p><p>DDS指纹指的是使用了S3 Texture Compression(S3TC)算法的DirectDraw Surface file format(一种特殊的数据压缩格式). S3TC有5种不同的变量:DXT1到DXT5,每种格式都有一个选项支持mipmapping(mipmapping是一种将高分辨率的纹理在纹理文件中缩放成多个texture file的技术)</p><p>下图所示仅测试了DXT1, DXT3, DXT5(因为2和3,4和5比较相似),另外在最右列增加了ARGB格式,第一行是mipmapping,第二行是without mipmapping(第一行的两个gray cube是因为特定机器上不支持DXT3 and DXT5 with mipmapping)</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210325102746780.png" alt="image-20210325102746780"></p><p><strong>Task(o): PVR Textures</strong></p><p>PVR texture是一种被很多移动设备采纳的另一种纹理压缩格式。基于数据块有两种模式:4bit模式和2bit模式;有两种流行版本:v1和v3,然后再基于是否mipmapping一共有8种子任务。其中的灰色方块也表示该格式不支持。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210325102845109.png" alt="image-20210325102845109"></p><p><strong>Task(p): Float Textures</strong></p><p>浮点纹理,用浮点数代替整数来表示颜色值。浮点纹理的一种特殊类型是深度纹理(depth texture),该纹理包含特定场景的深度缓冲的数据。下图来自于一个已存的在线测试,该测试是为了渲染浮点纹理和深度纹理。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210325102856913.png" alt="image-20210325102856913"></p><p><strong>Task(q): Video(Animating Textures)</strong></p><p>测试video的解压。具体来说,用三种不同的压缩格式(WebM, high quality MP4, standard MP4)的PNG文件构造了一个2s的静态场景视频,将视频从动态纹理映射到方块,并从该视频中捕获6个连续的帧。</p><p>(虽然所有的视频都是用一个PNG文件创建的,但由于图片压缩算法是有损的,所以捕获的帧都是不同的。目标是想捕获特定帧数的帧,但由于js没有提供特定帧数的帧的接口,值提供特定时间的API接口,所以这里选择6个连续的帧来保证目标帧在范围内)</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210325103014267.png" alt="image-20210325103014267"></p><p><strong>Task(r): Writing Scripts</strong></p><p>采用了一个侧信道方法来测试每个writing scripts的存在。writing scripts中的语言都会在浏览器中被渲染。如果writing scripts支持某个语言,渲染就会成功;否则就会出现一堆box。下图显示:Javanese, Sundanese, Lontara, Thaana在该测试浏览器中不支持。目前作者的测试列表中有36种语言。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210325103035220.png" alt="image-20210325103035220"></p><h3 id="C-Fingerprints-Composition"><a href="#C-Fingerprints-Composition" class="headerlink" title="C. Fingerprints Composition"></a>C. Fingerprints Composition</h3><p>在本部分主要介绍的是如何基于客户端的渲染任务的哈希值在服务器端形成一个指纹。</p><p>所有任务的哈希列表以及一个掩码(mask)共同执行“与”操作最终生成一个指纹值。</p><p>如果是single-browser fingerprinting,那么mask值是全1;如果是cross-browser fingerprinting,就需要从两个子任务中计算。第一个子任务是该浏览器是否支持B章节中描述的任务;第二个子任务根据不同的浏览器对有所不同。</p><p>为两个浏览器生成掩码是一个基于训练的过程。具体来说,使用较小的子集来获得一个掩码以优化跨浏览器的稳定性和唯一性(stability and the uniqueness)(稳定性和唯一性就像是硬币的两面,一方变大,另一方就会减小;比如说单浏览器的稳定性是0,但是唯一性确实最高的。)</p><p>下面的算法显示了对每个浏览器对的掩码的训练过程,采用了穷举算法,虽然不是最有效率但是结果最好并且最完整。</p><p>首先枚举了所有的浏览器对(Line 1); 接着枚举了所有的mask(line 4)</p><p>对每个掩码,遍历训练数据(line7),保证最终选择出来的掩码值是最大的cross-browser稳定性乘上唯一性(Line 8-11, 14-17)。第8行这边的相等是为了找两个不同浏览器之间的共同特征,FS是为了避免重复。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210325155911260.png" alt="image-20210325155911260"></p><h2 id="4-Implementation"><a href="#4-Implementation" class="headerlink" title="4 Implementation"></a>4 Implementation</h2><p>本开源实现(three.js, a JavaScript 3D library, glMatrix,a JavaScript library for matrix operations)一共包含21K LoC,其中JavaScripts: 14K lines, HTML: 1K lines; Coffeescript: 2.4K lines; C code: 500 line; Python code: 3.7K lines.</p><p>客户端的代码有一个manager,该manager由Coffeescript生成并转化为JavaScript。</p><ul><li>装载所有的渲染任务</li><li>从渲染任务中国收集结果以及浏览器信息</li><li>将结果发送给JavaScript代码,JavaScript代码执行散列操作并与服务端代码进行通信。</li></ul><p>Task(n)和(o)由C编写并通过Emscripten转化为JavaScript。其余的渲染代码由JavaScript编写,其中(k)-(m)的帮助由three.js协助完成,其余的直接使用WebGL或者JavaScript APIs。所有的渲染任务都使用glMatrix用于向量和矩阵操作。</p><p>服务器端部分的实现由Python编写,作为Apache server的一个模块:</p><ul><li>第一部分为1.2KLoC用于和客户端进行通信,并将哈希值存储在数据库中,图片存储在文件夹中</li><li>第二部分为2.5KLoC用于分析,比如生成并在收集到的指纹上应用masks。</li></ul><h2 id="5-Data-Collection"><a href="#5-Data-Collection" class="headerlink" title="5 Data Collection"></a>5 Data Collection</h2><p>本论文是从众包网站上收集数据:Amazon Mechanical Turks, MacroWorkers. 如果众包工人愿意用两个不同的浏览器登录指定的网站,将会得到额外的奖励。</p><p>为了保证获取完全真实的数据,在每个众包员工访问的链接中插入唯一标识符,比如<code>http://oururl.com/?id=ABC</code>。这个特殊标签会保存在客户端浏览器中作为一个cookie,如果该用户重新访问该浏览器,将会得到一样的身份标识符。每个众包员工只能接一次这个工作。</p><p>对于cross-browser fingerprinting,数据集被等分10份:一个用于生成掩码,其余用于验证。</p><h3 id="A-Comparing-Our-Dataset-with-AmIUnique-and-Panopticlick"><a href="#A-Comparing-Our-Dataset-with-AmIUnique-and-Panopticlick" class="headerlink" title="A. Comparing Our Dataset with AmIUnique and Panopticlick"></a>A. Comparing Our Dataset with AmIUnique and Panopticlick</h3><p>本篇论文方法,AmIUnique, Panopticlick三种得到的数据集在normalized Shannon’s entropy(归一化香农熵)上的对比。</p><p>其中,HM意味着最坏的可能性:也就是所有的指纹都有着相同的概率,也就是所有的指纹都一样。(<em>这儿有个问题:为什么熵最大的时候,是最坏的可能性,这不是意味着没有重复吗?还是说一个物理机器上的不同浏览器之间的重复值基本没有?</em>)</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210325192759650.png" alt="image-20210325192759650"></p><p>其中,差异比较大的是List of Fonts,主要原因是当前很多浏览器都淘汰了Flash</p><p>TimeZone差异大是因为众包员工来自世界各地;</p><p>另外,本方法的cookie的归一化熵几乎为0,这是因为作者从众包网站上收集数据时,员工必须在支持cookie收集的情况下才能得到奖励。如果他们关闭了cookie,甚至无法登陆进众包网站。而其余两个网站的用户可能有一小部分关闭了cookie(enable)</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210325192431886.png" alt="image-20210325192431886"></p><h2 id="6-Results"><a href="#6-Results" class="headerlink" title="6. Results"></a>6. Results</h2><p>首先给出结果的概览,然后将结果分成不同的浏览器对和特征,最后给出一些有趣的观察结果。</p><h3 id="A-Overview"><a href="#A-Overview" class="headerlink" title="A. Overview"></a>A. Overview</h3><p>与AmIUnique比较single-browser fingerprinting,与Boda et al.比较cross-browser fingerprinting。</p><p>Boda et al.中的特征具有着最高的cross-browser stability。</p><p>AmIUnique是开源的,代码可以直接从github获得;Boda et al.提供了开放的测试网站,作者直接下载了fingerprinting JavaScript。这样直接使用它们的源代码能够最小化可能的实现偏差。</p><p>Uniqueness意味着唯一指纹数占总指纹数量的比重,而entropy是Shannon entropy。本论文方法的结果为99.24%,相对于AmIUnique有8.4%的增长。而对于熵,最大值为10.96,两种方法都非常接近这个最大值(尤其是本文的方法更接近一点),这也意味着两种方法中的非特殊的指纹都分散在比较小的匿名空间中。</p><p>stability意味着同一台机器上的不同浏览器中稳定的指纹比例。(举个例子,在Boda et al.中,用户如果选择不同的缩放比例,那么屏幕分辨率也可能不同;GPU渲染可能使用不同的方法,比如硬件渲染或者软件渲染)</p><p>unique相对于Boda提高了很多;另外,cross-browser stability提高到91.44%,这是因为本论文中采集的特征在不同的浏览器中更稳定。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210325203512556.png" alt="image-20210325203512556"></p><h3 id="B-Breakdown-by-Browser-Pairs"><a href="#B-Breakdown-by-Browser-Pairs" class="headerlink" title="B. Breakdown by Browser Pairs"></a>B. Breakdown by Browser Pairs</h3><p>其余浏览器包括Maxthon, Coconut, UC浏览器。主对角线代表着single-browser fingerprinting,其余部分表示cross-browser。(有两个N/A是因为Apple放弃了在Windows上支持Safari,而微软从来没有在Mac OS上支持IE和Edge)</p><p>single browser的稳定性为100%是因为只是在和自己比较。其中,火狐拥有最低的唯一性,是因为火狐隐藏了一些信息(比如由于隐私原因隐藏了WebGL渲染和供应商)。IE和Edge的唯一性为100%,说明这两款浏览器都是highly fingerprintable;而Opera,Safari以及其他的浏览器为100%是在我们的数据集中只有比较少的样本。</p><p>接下来观察cross-browser的uniqueness和stability,其中,除了Opera vs IE(因为这对组合的基数非常小) ,其余浏览器对的cross-browser稳定性都很高(>85%)</p><p>IE和Edge对的唯一性很高,这是因为这两者都是由微软设计并且使用了比较少的开源库;而且他们之间的稳定性也很高,说明IE和Edge很有可能共同使用了相当多的代码。</p><p>Edge在和其他浏览器组成浏览器对时,比IE有更高的uniqueness,这是因为Edge引入了更多的功能,比如WebGL遵从标准的完全实现,会有更多的指纹面。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210325210632580.png" alt="image-20210325210632580"></p><h3 id="C-Breakdown-by-Features"><a href="#C-Breakdown-by-Features" class="headerlink" title="C. Breakdown by Features"></a>C. Breakdown by Features</h3><p>表4被分成两个部分,AmIUnique上面的部分显示了AmIUnique采用的特征;而第二部分是本文方法采用的特征。</p><h4 id="1-Screen-Resolution-and-Retio"><a href="#1-Screen-Resolution-and-Retio" class="headerlink" title="1) Screen Resolution and Retio"></a>1) Screen Resolution and Retio</h4><p>single-browser中,屏幕分辨率和屏幕比例的熵为7.41,而宽和高比例的熵仅为1.40.这是因为很多分辨率比如1024x768和1280x960,共享相同的比例。cross-browser的屏幕分辨率的stability非常低(9.13%),因为用户经常放大缩小网页。cross-browser的宽高比稳定性很高(97.57%),但低于100%,这是因为部分用户采用两个屏幕,并将两个浏览器分别投射在两个不同的屏幕上。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210326144654161.png" alt="image-20210326144654161"></p><h4 id="2-List-of-Font"><a href="#2-List-of-Font" class="headerlink" title="2) List of Font"></a>2) List of Font</h4><p>由于Flash技术的逐渐被取代,由Flash得到的字体列表的熵仅为2.40,而相对低,由JavaScript获得的字体列表的熵为10.4,这也意味着list of fonts是一种highly fingerprintable feature</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210326202803918.png" alt="image-20210326145829551"></p><h4 id="3-Anti-aliasing"><a href="#3-Anti-aliasing" class="headerlink" title="3) Anti-aliasing"></a>3) Anti-aliasing</h4><p>(b: varying), (d: lines and curves), (h: specular light), (k: complex lights)</p><p>当增加了反锯齿后,b, d, h的single-browser fingerprinting的熵增加,而k减少了,这是因为b, d, h有更少的边,而反锯齿会增加更多的fingerprintable内容;而k中的每个圆环有很多小边,反锯齿会占据圆环的内容并且降低每个圆环的fingerprintable内容。</p><p>而cross-browser fingerprinting的稳定性特征正相反:b, d, h下降,而k提高。因为一些机器上的所有浏览器并不支持反锯齿,而反锯齿降低了圆环中的一些fingerprintable内容,从而提高了稳定性。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210326151523197.png" alt="image-20210326151523197"></p><h4 id="4-Line-amp-Curves"><a href="#4-Line-amp-Curves" class="headerlink" title="4) Line&Curves"></a>4) Line&Curves</h4><p>任务(d) 这部分的熵很小(1.09),而且cross-browser的稳定性很高(90.77%),因为直线和曲线都是简单的2D操作,不同浏览器和机器之间不会相差太多(手动分析一些案例得到的主要差异在于起点和终点有几个像素点移动了)</p><h4 id="5-Camera"><a href="#5-Camera" class="headerlink" title="5) Camera"></a>5) Camera</h4><p>比较b和c时,可以发现当增加摄像头时,熵值下降了,这是因为增加摄像头的目的在于缩小(画面拉远)立方体,这会减少表面上的微小差异。b和c的cross-browser稳定性很相似,是因为b和c执行了类似的任务。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210326152848288.png" alt="image-20210326152848288"></p><h4 id="6-Texture"><a href="#6-Texture" class="headerlink" title="6) Texture"></a>6) Texture</h4><p>比较DDS, PVR, cubemap, float texture,其中,float和cubemap比其他的纹理的熵高,这是因为这两者其中包含了更多的信息(比如,浮点纹理中的宽以及cubemap纹理中的cube mapping)。PVR纹理的熵值很低,是因为它主要在Apple移动设备上提供支持(众包员工使用Apple移动设备的比较少)。而DDS纹理的cross-browser稳定性很低(68.18%),这是因为DDS是一个微软格式,不在其他浏览器上提供支持。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210326153914549.png" alt="image-20210326153914549"></p><p>比较Task(h)和Task(i),当增加了一层纹理后,single-和cross-browser的熵都减小了,这是因为本文中的纹理是精心挑选的,其中包含了很多fingerprintable特征,当增加了两个纹理之后,一些特征被减小了,从而使two-texture的任务变得less fingerprintable。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210326155403356.png" alt="image-20210326155403356"></p><h4 id="7-Model"><a href="#7-Model" class="headerlink" title="7) Model"></a>7) Model</h4><p>比较Task(a)和(e)以及Task(f)和Task(g)中关于模型的影响。(e)和(g)相对于(a)和(f)增加了沙发模型,但熵值都只增加了0.03,说明Sofa模型带来的fingerprintable feature的增加非常有限。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210326155932072.png" alt="image-20210326155932072"></p><h4 id="8-Light"><a href="#8-Light" class="headerlink" title="8) Light"></a>8) Light</h4><p>(a), (e), (f), (h), (k)都是与光相关的部分。其中,f相对于a增加了diffuse point light(散射点光),熵值相对增加了0.01,说明散射点光在fingerprinting中产生了比较小的作用。而(h)表明镜面光有更显著的作用,因为h在单浏览器和cross-browser中的指纹识别中,熵值都增加了超过0.9(相比较f)。而任务k的熵值也很高,是因为k中有超过5000个模型,并且不同颜色光在这些模型上反射并相互混合。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210326160933816.png" alt="image-20210326160933816"></p><h4 id="9-Alpha"><a href="#9-Alpha" class="headerlink" title="9) Alpha"></a>9) Alpha</h4><p>在0.09到1之间选择了5个不同的值赋给alpha。通过实验结果,可以发现随着alpha值增加,熵值开始也随之增加但慢慢产生了回落。作者比较了修改了alpha值的图像和标准图像,发现回退主要是由于软件渲染引起的,软件渲染近似于Alpha值。作者还观察到了回退中的一些模式,这些模式以0.1的增量步骤发生。</p><p>(<em>瞎翻的,具体为什么会回退看论文没看懂</em>)</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210326162314054.png" alt="image-20210326162314054"></p><h4 id="10-Clipping-Planes"><a href="#10-Clipping-Planes" class="headerlink" title="10) Clipping Planes"></a>10) Clipping Planes</h4><p>single-browser中的熵为3.48,cross-browser的熵为1.93,有着76.61%的稳定性。熵结果和pure texture值接近,因为折叠平面用JavaScript实现,并没有贡献多少指纹。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210326164243396.png" alt="image-20210326164243396"></p><h4 id="11-Rotation"><a href="#11-Rotation" class="headerlink" title="11) Rotation"></a>11) Rotation</h4><p>h’’相对于h’增加了稳定性,但降低了熵,因为Suzanne模型的正面和沙发模型的里面有更多的细节,当旋转到另一个角度时,会让fingerprintable细节减少而相应地稳定性提高。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210326164436353.png" alt="image-20210326164436353"></p><h4 id="12-AudioContext"><a href="#12-AudioContext" class="headerlink" title="12) AudioContext"></a>12) AudioContext</h4><p>本文中测量的AudioContext(目标音频的信息和转换波)是cross-browser稳定的。熵值为1.87,相比较测量Englehardt[18]测量整个wave得到的熵值(5.4)要小很多。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210326164713108.png" alt="image-20210326164713108"></p><h4 id="13-Video"><a href="#13-Video" class="headerlink" title="13) Video"></a>13) Video</h4><p>video的熵值是所有任务中最高的(7.29),因为解码视频需要浏览器,驱动和硬件的共同参与。而video关于cross-browser的稳定性非常低(5.48%),而熵将至2.32。这是因为类似于图片编码和解压,浏览器在解压WebM和MP4格式时会伴随着损失,但作者并没有找到像图片一样的针对视频的无损格式。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210326165442054.png" alt="image-20210326165442054"></p><h4 id="14-Writing-Scripts"><a href="#14-Writing-Scripts" class="headerlink" title="14) Writing Scripts"></a>14) Writing Scripts</h4><p>writing scripts(support)包含的是特定书写字体是否被支持的信息(比如,如果某个比特位为1就说明支持某个字体,否则就不支持,而我们获取是否支持的信息就是从box detection)</p><p>writing scripts(images)是客户端的图片渲染。 writing scripts中的single-browser熵要比writing script(support)大3.13,这是因为图像中包含着更多的信息。</p><p>writing scripts(support)的cross-browser稳定性是在应用了掩码之后基于结果计算得到的,因为有些writing scripts是随浏览器一起发布的,并且不是跨浏览器稳定的,相对而言,writing scripts(support)跨浏览器的熵要比单浏览器低。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210326202803918.png" alt="image-20210326202803918"></p><h4 id="15-CPU-Virtual-Cores"><a href="#15-CPU-Virtual-Cores" class="headerlink" title="15) CPU Virtual Cores"></a>15) CPU Virtual Cores</h4><p>CPU 虚拟内核的数量是从HardwareConcurrency值计算得到的(如果不支持HardwareConcurrency,那么该值为”undefined”),单浏览器指纹的熵值为1.92(作者认为未来该熵值会增大,因为就在论文提交不久前,Firefox48开始支持该特性)。cross-browser的稳定性为100%是因为作者检测浏览器是否支持HardwareConcurrency,并应用定制掩码。cross-browser的熵比single-browser的药效,是因为数据集比较小,并且他们归一化后值其实非常接近。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210327094903410.png" alt="image-20210327094903410"></p><h4 id="16-Normalized-WebGL-Render"><a href="#16-Normalized-WebGL-Render" class="headerlink" title="16) Normalized WebGL Render"></a>16) Normalized WebGL Render</h4><p>WebGL渲染器并不是cross-browser fingerprintable,因为不同浏览器提供不同层面的信息。作者从不同的浏览器中提取共同的信息,并用标准格式对其这些信息。对其标准化的过程会丢弃部分信息,使得single-browser的熵从5.70降到4.98,但另一方面,cross-browser的稳定性从15.39%上升到37.39%。</p><p>这里需要注意两个点</p><ul><li>WebGL供应商并没有提供比WebGL渲染器更多的信息,所以当结合两者的值时,熵值仍然是WebGL渲染器的值;</li><li>GPU任务提供了比WebGL供应商和渲染器更多的信息,因为GPU渲染是软件和硬件信息的共同作用,而WebGL仅仅提供了用于硬件渲染的物理信息。</li></ul><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210327101200107.png" alt="image-20210327101200107"></p><h3 id="D-Observation"><a href="#D-Observation" class="headerlink" title="D. Observation"></a>D. Observation</h3><p>接下来介绍作者观察到的几个有趣的事实。</p><p><strong>观察1:fingerprintable特征是高度可信赖(即使移除单个特征对总的指纹结果来说影响非常小)</strong></p><p>通过任意从AmIUnique和本方法中移除一个特征并计算两者的唯一性值,从而比较两个方法的可靠程度。结果表明即使任意移除表4中的特征,作者的fingerprinting的唯一性仍然保持在99%以上,而AmIUnique中移除6个属性(user agent, timezone, list of HTTP headers, screen resolution, color depth) 中的任何一个,AmIUnique的唯一性会将至84%。</p><p>得出结论:本方法相比较AmIUnique在使用的特征上有更可靠。</p><p><strong>观察2:软件渲染可以用于指纹</strong></p><p>对WebGL的一个共识是软件渲染可能会减少由显卡带来的差异。但是本实验表明软件渲染能够用于指纹识别。具体来说,作者选用WebGL中所有由SwiftShader渲染的数据,SwiftShader是一个Google开发的开源软件渲染器,当硬件渲染无法使用时由Chrome使用。作者计算了所有GPU渲染任务的特殊指纹(task(a)-(p)包括writing scripts和video)</p><p>由于大部分例子使用了硬件渲染,作者仅采集了88例使用SwiftShader的例子,并找到了11个不同的指纹,其中,7例是唯一的。软件渲染的唯一性是低于任何一种硬件渲染,但也不是0</p><p><strong>观察3:WebGL渲染是软件和硬件共同完成的,其中硬件贡献过于软件</strong></p><p>Microsoft Basic Rendering对各类显卡提供了通用驱动,也就是该渲染器能够最大地降低软件驱动的影响,从而显示硬件的效果。作者选择使用了Microsoft Basic Rendering的例子并计算指纹。</p><p>仅采集到32例使用Microsoft Basic Rendering的例子,并找到了18例独特的GPU指纹,其中15例是唯一的。Microsoft Basic Rendering的唯一性低于正常使用普通显卡驱动的例子,说明WebGL的渲染是通过软硬件共同完成的。但同时,硬件做出了更多的贡献,因为Microsoft Basic Rendering的唯一性高于观察2中的软件渲染器。</p><p><strong>观察4:DataURL在不同浏览器中有不同的实现</strong></p><p>DataURL是一种在先前的指纹识别中用于表示图像的通用格式。但本文作者发现DataURL在不同浏览器中的实现是非常不同的。这对于单浏览器指纹识别很好,但却不利于跨浏览器指纹识别。Canvas中的跨浏览器稳定性非常低,仅为8.17%,这是因为AmIUnique使用DataURL来存储图像信息。</p><p><img src="/2021/03/30/2021-05-09-Browser-NDSS17/image-20210327111906064.png" alt="image-20210327111906064"></p><p><strong>观察5:渲染结果之间的差别非常小(只有1到2个像素的差别)</strong></p><p>部分渲染结果(尤其软件和硬件渲染)差别很大,但有部分渲染结果差别很小(尤其是当显卡相似时)。比如,Suzanne模型当使用iMac和另一个Mac Pro渲染时,在纹理上的差异仅有1个像素,当作者旋转模型时,仅有的差异也消失了。</p><p>最后,刘老师真好!给分很高!</p>]]></content>
<summary type="html">
<p>(Cross-) Browser Fingerprinting via OS and Hardware Level Features</p>
<p>春季学期web追踪前沿分享的论文,由于本人的web知识浅薄,整理的笔记难免有偏颇的地方。</p>
<p>基于操作系统和硬件特征来进行单浏览器和跨浏览器指纹追踪。</p>
</summary>
<category term="课堂论文分享" scheme="http://yoursite.com/categories/%E8%AF%BE%E5%A0%82%E8%AE%BA%E6%96%87%E5%88%86%E4%BA%AB/"/>
<category term="paper" scheme="http://yoursite.com/tags/paper/"/>
<category term="web" scheme="http://yoursite.com/tags/web/"/>
</entry>
<entry>
<title>leaky -CCS17</title>
<link href="http://yoursite.com/2021/02/28/2021-02-28-leaky/"/>
<id>http://yoursite.com/2021/02/28/2021-02-28-leaky/</id>
<published>2021-02-28T14:17:15.000Z</published>
<updated>2021-02-28T09:41:48.614Z</updated>
<content type="html"><![CDATA[<p>Leaky Cauldron on the Dark Land: Understanding Memory Side-Channel Hazards in SGX </p><p>论文阅读摘要,建议阅读原文</p><a id="more"></a><h1 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h1><p>简单介绍了一下背景:目前的研究热点是针对在当时新提出来的page-fault attack(OS级别的攻击者通过产生page faults来观察SGX中受保护的进程的page-level访问模式)。虽然提出了很多保护措施,但是这些保护措施的功效其实还是未知的。</p><p>提出本篇论文做出的工作:</p><ul><li>系统地分析了SGX所面临的侧信道攻击的威胁(主要在内存管理上,从TLB到DRAM模块)</li><li>当从page channel中恢复EdDSA密钥时对enclave program进行细粒度的监控时能够避免高频率的AEXs(如何避免AEXs的同时在cache和cross-enclave DRAM中恢复EdDSA密钥)</li><li>揭露现有针对SGX的安全研究和side-channel weakness之间的鸿沟</li><li>启发如何安全地使用SGX这样的一个系统</li></ul><h1 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h1><p>第一段简单地介绍了TEE和SGX的概念,然后介绍了SGX存在的问题。SGX强调简单的设计理念导致很多资源都是部分或者是全部被untrusted OS所控制,因此很容易受到侧信道攻击的影响。</p><p>第二部分介绍了侧信道攻击的影响,当攻击者完全控制住了OS,他能够通过构造一个完全没有噪音的环境,在该环境中观察中断来操纵页表中运行的代码。在page level的级别定义程序运行轨迹,从而提取出运行的文本信息。(攻击者获得了对OS的控制权,他就能够操纵运行在enclave-mode中的页表代码怎么做的呢?通过在一个由攻击者构造的环境中观察中断,他能够根据page level的执行轨迹提取出运行的文本信息,从popular application libraries得到图片信息。)</p><p>第二部分讲的就是侧信道攻击的实现方法以及可能带来的危害。</p><p>提到了intel官方对于侧信道攻击的态度:intel官方非常清楚SGX是无法防御侧信道攻击的,他们把问题的解决留给了软件方。</p><p>提出的问题是对可能存在的攻击面的不完全理解会导致不能很好地防御。(提出问题)</p><p>第三部分,也就是理解侧信道攻击这部分,作者提出了本文最重要的三个点:</p><ul><li>不仅仅只有page faults会泄露程序的内存访问布局</li><li>不是所有的侧信道攻击都会触发大量的AEX(asynchronous enclave exits)</li><li>需要对side-channel进行一个更细粒度的观察,比如说在cache-line层面上</li></ul><p>作者所作出的工作:</p><ul><li>探索memory side-channel攻击面(该研究从address translation到memory operation之间的每一步都考虑在内)</li><li>通过一种更有效的侧信道攻击来减少副作用。(避免跟踪程序碎片进入和退出的轨迹,转而测量不同页面之间的执行时间从而推测它们之间的执行路径;另外,提供了一种新的方法来刷新TLBs,该方法利用了intel的HyperThreading,并通过与enclave code共享的CPU core的攻击进程,从而减少所需要的中断数量)(这部分主要是介绍了本文作者所完成的工作:提出了一种新的方法SPM,该方法已有的侧信道攻击方法相比优势所在,简单介绍了该方法运用了什么技术,用SPM技术最后呈现的效果如何)</li><li>提高了攻击空间粒度。(page-fault attacks的访问粒度在4KB,而本文是64B)</li></ul><p>影响</p><p>贡献:</p><ul><li>第一篇对SGX内存侧信道攻击面的in-depth研究</li><li>提出了新的攻击</li><li>提出了缓解措施,强调对类似于SGX等安全措施的更好的理解</li></ul><h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>背景主要从虚拟地址和物理地址管理,对内存隔离的安全检查以及内存加密三个方面进行表述。</p><p>虚拟地址和物理地址管理</p><p>SGX为enclave应用程序和相关的控制结构保留了一段Processor Reserved Memory(PRM),而EPC就是PRM的一个子集。虚拟地址转换成物理地址是通过untrusted system software来完成,比如TLB,CPU在enclave和非enclave之间的切换通过EENTER和EEXIT指令来完成。</p><p>然后SGX分成enclave和非enclave,接下来就介绍对内存隔离是如何保证的。如何enclave的地址被映射到了非PRM中或者非enclave的地址被映射到了PRM中,就会出现page fault。</p><p>最后介绍安全内存是如何来保证安全性的,也就是内存加密的介绍。如果有EPC被驱逐出来,就会通过加密手段对该内存进行加密,另外通过MAC机制保证加密的完整性。</p><h2 id="攻击模型"><a href="#攻击模型" class="headerlink" title="攻击模型"></a>攻击模型</h2><p>关注的重点在于威胁enclave programs的机密性的侧信道分析。</p><p>假设攻击者将恶意代码加载到enclave中,攻击者能够获取enclave binary在虚拟地址空间的基地址</p><p>以及攻击者能够在执行攻击之前获取对相同配置的机器的访问。</p><h1 id="理解攻击面"><a href="#理解攻击面" class="headerlink" title="理解攻击面"></a>理解攻击面</h1><p>通过对individual vectors的分析得到的攻击面总结了SGX中侧信道攻击面。</p><h2 id="攻击面"><a href="#攻击面" class="headerlink" title="攻击面"></a>攻击面</h2><p>作者从虚拟地址到物理地址的转换进行考虑,首先评估address translation caches,并且walk through 内存中的page tables。</p><p><strong>Address Translation Cache</strong>。这一部分负责完成虚拟地址到物理地址的转换工作来进行讨论,主要介绍TLB(translation lookaside buffer),如果TLB都miss了,就要进行虚拟地址到物理地址的转化,intel中的话就需要进行4步转换操作。</p><p>vector1. shared TLBs and paging-structure caches under hyperthreading</p><p>当超线程被开启的时候,enclave模式下的TLB和paging-structure caches会和非enclave mode下的代码共享。</p><p>vector2. Flushing selected entries in TLB and paging-structure caches at AEX.</p><p>vector3. referenced PTEs are cached as data.</p><p>第三章的攻击面从地址转换cache,页表,缓存内存层次三个方面选择了8个可能存在攻击的点进行讨论,而实施侧信道攻击的影响可以从空间粒度,时间角度以及AEXs,中断频率等方面观察。</p><p>第四章介绍了新的侧信道攻击(sneaky page monitoring attacks),使用这样的攻击在带来相同攻击效果的同时会极大地减少AEXs。在介绍sneaky攻击时,利用页表的accessed flag,时间监控,利用超线程的TLB flushing这三个攻击面实施的侧信道攻击。然后介绍了这三种攻击分别在三个软件工具上的实现效果来证明攻击的有效性。在第四章的最后部分,作者在最新的Libgcrypt库中以较少的EAXs恢复EdDSA密钥。</p><p>第五章考虑以一种更为细粒度的方式实施cache-DRAM侧信道攻击,本部分通过提高空间粒度来显示了更为有效的攻击效果,分别介绍了cross-enclave Prime-Probe cache attack,cross-enclave DRAMA攻击以及一个cache-DRAM攻击。最后根据构造的timer评价了实施攻击的准确性,作者根据结果总结得出如果实施64byte粒度的cache-DRAM攻击,将能够达到和flush-reload cache攻击相似的攻击结果。</p><p>第六章首先讨论的是对前文所介绍的攻击面进行讨论和总结,比如说应用64byte的cache-DRAM攻击能和FLUSH+RELOAD达到相同的效果(但是由于EPC只能由一个enclave拥有一次,也就是它只有在这个enclave运行的时候才能被拥有,所以该攻击是无法在SGX上执行的),并简单介绍了一些其他没有被介绍的攻击面。</p><p>接下来介绍的是现有的缓解措施的效果。这部分是对现有的5种不同角度考虑的tee安全防御措施的效果进行了讨论,指出他们的作用并提出了他们所存在的问题。</p><p>第三部分是根据前面的攻击和上面的讨论得出的一些思考,比如SGX开发者必须意识到可能存在的攻击民并在软件开发时尽量避免,给予软件层面的保护并进行硬件保护。</p><p>第七章就是介绍相关的工作</p><p>第八章是对本文工作的总结</p><p>第九章是致谢以及本工作依托的项目。</p>]]></content>
<summary type="html">
<p>Leaky Cauldron on the Dark Land: Understanding Memory Side-Channel Hazards in SGX </p>
<p>论文阅读摘要,建议阅读原文</p>
</summary>
<category term="SGX" scheme="http://yoursite.com/categories/SGX/"/>
<category term="SGX" scheme="http://yoursite.com/tags/SGX/"/>
</entry>
<entry>
<title>Keystone EuroSys20</title>
<link href="http://yoursite.com/2021/01/29/2021-01-29-keystone-2020/"/>
<id>http://yoursite.com/2021/01/29/2021-01-29-keystone-2020/</id>
<published>2021-01-29T09:53:50.000Z</published>
<updated>2021-01-29T13:31:15.281Z</updated>
<content type="html"><![CDATA[<p>Keystone论文的精读笔记~</p><a id="more"></a><p>Keystone是第一个用于构造<strong>定制TEE</strong>的开源框架。Keystone使用硬件提供的简单抽象比如内存隔离和不可信组件(比如操作系统)下的可编程层。Keystone在这些抽象中(memory isolation,programmable layer underneath untrusted components)构造可重复使用的TEE core primitives,同时允许特定于平台的修改和硬件特性。</p><h2 id="1-介绍"><a href="#1-介绍" class="headerlink" title="1 介绍"></a>1 介绍</h2><p>(简单地介绍一下TEE)</p><p>TEE被广泛地应用于诸如安全云服务、数据库、大数据计算、安全银行、区块链共识协议等场景中。</p><p>弊端:任何一个vendor TEE仅支持针对威胁模型、硬件需求、资源管理、移植操作以及功能兼容性的一小部分设计空间。一旦,一个软件开发者选择了一个特定的硬件平台,无论他们的实际需求是什么,他们都会被锁定在对应固定的硬件平台中。现有的TEE解决方案不管是在RISC-V平台或者是OpenSPARC中,进行重新设计都需要耗费巨大的经历,并且仅仅另一个固定的设计点中起效。(这边举了一个sgx的例子,SGXv1在设计之初只支持固定的enclave大小并且缺乏I/O和系统调用,所以就造成TCB非常大,尽管研究人员们提出了很多复杂的解决方法,但仅有intel能够对内部设计进行更改,所以用户得等到SGXv2,才能拥有动态的enclave虚拟内存。)</p><p>为了利用riscv的原语构造一个高可定制的TEE,keystone主张硬件必须提供安全原语而非点到点的解决方案。(将一部分重要的原语信息暴露给软件将会带来更好的用户定制效果)<strong>Keystone的宗旨是通过借鉴模块化内核的概念,允许一组通用的软件模块根据硬件平台和用例调整特性和安全模型。</strong></p><p>可定制化的TEEs的概念是允许实体创建一个硬件,操作它,并在相同的基准上构造和配置不同的TEE设计。(也就是同样的硬件,硬件通过提供原语,开发者可以根据这些提供的原语构造出针对不同用户需求和威胁模型的TEEs)</p><p><strong>作者考虑的需求</strong></p><ul><li>在不可信OS下构造a highly programmable trusted layer;</li><li>将资源管理、虚拟化以及信任边界的隔离机制解耦(hypervisor是负责安全和虚拟化的可编程层,将我们的需求变得复杂化;而硬件和微代码并不满足可编程的需求。另外,也不应该依赖于硬件隔离来实现一个固定的安全和非安全边界)</li></ul><p>–》构造一个common,portable的软件基来适应千变万化的硬件功能和用户需求</p><p>Keystone是第一个用于构造可定制TEE的开源框架。使用physical memory protection(PMP)。PMP通过利用RISC-V中的programmable machine mode来指定对物理内存区域的任意保护。keystone中使用该machine mode来运行一个security monitor(SM)在无需进行资源管理的同时提供安全边界。</p><p>每个enclave运行在各自的隔离物理内存区域,并且拥有自己的supervisor-mode runtime(RT)组件来管理enclave的虚拟内存。</p><p>基于这个设计,RT主要是完成特定于enclave的功能,而SM管理硬件完成的保证。RT仅完成特定的功能,比如说和SM进行通信,通过共享内存与主机进行通信,为enclave user-mode application(eapp)提供服务。(与安全无关的功能)</p><p>SM利用hardware primitives来提供对诸如secure boot,memory isolation以及attestation等TEE保证。而RT提供诸如系统调用接口,标准libc支持,in-enclave虚拟内存管理,自分页等enclave内部的功能性模块。<em>SM利用了一切可利用的硬件来构造额外的安全机制,比如highly configurable cache controller,与PMP协调,来抵御cache side-channel。</em>(这块的抵御侧信道攻击需要仔细看一下)</p><p><strong>总结</strong>:作者提出了当前TEEs的各种问题,然后提出了定制TEEs的概念(这个定制TEEs的概念其实也是从SDN中派生出来的),然后提出了自己对定制TEEs的需求,接着提出了设计理念。</p><p>作者构造了Keystone,两个RTs(自己制作的RT-Eyrie和一个现成的微内核seL4)以及几个模块,这些模块可以供由enclave绑定的user application选择性地配置和使用功能。</p><p><img src="/2021/01/29/2021-01-29-keystone-2020/1.png" alt="image-20200914110304034"></p><p><strong>贡献</strong></p><ul><li>customizable TEEs. 定义了一个新的范式,基于这样的一个范式,硬件制造商、开发商以及enclave开发人员能够定制自己的TEE;</li><li>Keystone框架。本文提供了第一个用于配置、建立以及实例化可定制TEEs的框架。主要方法是通过保证Keystone的模块化来根据需求定制TEE instances。</li><li>开源实现。在无需任何微架构的修改能够适应威胁模型、使用硬件特性、处理工作负载并且提供丰富的功能。</li><li>基准测试和实际应用。基于CoreMark,Beebs,RV8, IOZone来评估了Keystone,使用seL4搭载的Torchin Eyrie,FANN演示了真实世界中使用keystone的机器学习负载和一个keystone本地安全远程计算应用程序。最后演示对具有物理访问权限的攻击者的防御。</li></ul><h2 id="2-不同TEE的共同基准"><a href="#2-不同TEE的共同基准" class="headerlink" title="2 不同TEE的共同基准"></a>2 不同TEE的共同基准</h2><h3 id="2-1-商用TEEs"><a href="#2-1-商用TEEs" class="headerlink" title="2.1 商用TEEs"></a>2.1 商用TEEs</h3><p>目前很多的商用TEEs迎合特定且容易受到攻击的用例,但是仅仅在设计空间中仅考虑到了一部分。假设考虑在不可信云环境下的服务器运行高负载的程序,比如ML inferences。</p><ul><li>基于intel SGX的解决方案需要很大的软件栈来扩展额外支持的功能</li><li>AMD SEV-based solution用一个巨大的TCB隔离一个完整的VM(不管是SGX还是SEV,如果要考虑防御侧信道攻击,还需要格外的用户空间软件机制)</li><li>如果在IoT设备或者边缘感应器上,就需要考虑TrustZone,TrustZone位于一个叫secure world由硬件实现隔离的区域,但进一步的隔离需要基于软件的Secure world OS解决办法在secure application之间进行多路复用。</li></ul><p>一个新的研究方向是使用a thin layer of trusted software,类似于在kernel设计中的reference monitor。这样的设计能够抵御强大的攻击者且能够包含一个小的TCB。</p><ul><li><p>sanctum对硬件进行了修改为RISC-V构造user-space enclave。</p></li><li><p>Komodo:提供了一个verified monitor,该monitor能够在ARM TrustZone上运行</p></li></ul><h3 id="2-2-可定制的TEE"><a href="#2-2-可定制的TEE" class="headerlink" title="2.2 可定制的TEE"></a>2.2 可定制的TEE</h3><p>customizable TEEs:使用一个共同的软件框架,根据用户不同的需求来定制一个特定的TEE。硬件提供商仅需要提供基础的原语。实现一个特定的TEE实例需要涉及平台提供商对硬件接口,可信模型以及enclave开发人员的功能性需求。实体可以根据自己的需求把特定的模块组合起来实例化成一个特定的TEE。</p><p>现在的商用TEE系统都提供了固定的与各自的硬件平台相关的威胁模型。</p><ul><li>intel SGX不支持任何针对它的内存保护措施的修改,那么sgx对于不需要昂贵的内存加密的用例来说是不必要的</li><li>ARM TrustZone并不适合用来构造一个模块化的TEE(TrustZone的设计核心就是只分成了两个区域)如果有多个enclaves,还必须使用MMU(内存管理单元)</li><li>RISC-V基于machine mode和PMP寄存器提供per-hardware-thread 的物理内存视图(RISC-V同时允许多线程的enclave访问不相交的内存区域,同时允许enclave使用supervisor mode以及MMU)</li></ul><p>支持Keystone的安全硬件平台必须满足:</p><ul><li>仅对trusted boot process可见的device-specific secret key</li><li>a hardware source of randomness</li><li>a trusted boot process</li></ul><h3 id="2-3-TEE生命周期中的实体"><a href="#2-3-TEE生命周期中的实体" class="headerlink" title="2.3 TEE生命周期中的实体"></a>2.3 TEE生命周期中的实体</h3><ul><li>hardware manufacturer:负责设计和制造RISC-V硬件,包括trusted boot的相关IP</li><li>Keystone platform provider:购买制造商生产的硬件,操作硬件,并使之能够给它的客户使用,配置SM;</li><li>Keystone programmer:开发Keystone软件组件,包括SM,RT和eapps</li><li>Keystone user:选择RT和eapp的配置来构造一个Keystone,实例化一个能在Keystone platform provider提供的硬件上运行的enclave</li><li>Eapp user:与TEE中运行的eapp进行交互</li></ul><p>Acme 公司将他们的网站托管在 Apache 服务器上,该服务器运行在 Cloud 公司提供的一个基于 Keystone的安全区云服务上,该云服务基于 Bar 公司制造的硬件。 </p><p><strong>例子</strong></p><ul><li>Acme Corp:Keystone user</li><li>Apache webserver:eapp programmer</li><li>Bar Corp:hardware manufacturer</li><li>Cloud Corp提供云服务:Keystone platform provider,RT programmer,SM programmer</li></ul><h2 id="3-Keystone概览"><a href="#3-Keystone概览" class="headerlink" title="3 Keystone概览"></a>3 Keystone概览</h2><p>简单介绍一下RISC-V:RISC-V是一个有着不同开源内核实现的开源ISA。它支持4种特权模式:</p><ul><li>U-mode(user):user-space processes</li><li>S-mode(supervisor):kernel</li><li>H-mode(hypervisor):hypervisor</li><li>M-mode(machine):对物理资源的直接访问</li></ul><h3 id="3-1-设计原则"><a href="#3-1-设计原则" class="headerlink" title="3.1 设计原则"></a>3.1 设计原则</h3><ul><li>利用不可信代码下的可编程层和隔离原语。SM使用M-mode的特性来实现TEE保证:1)对平台提供者来说是可编程的;2)满足对于最小范围的最高特权级别的需求;3)在系统中控制系统中断和异常的代理;4)使用M-mode控制PMP,保证在运行时memory-mapped control features的隔离。(保证在运行时隔离内存映射功能)</li><li>解耦资源管理和安全检查。SM在最高权限以最少的代码实施安全策略,几乎只完成安全相关的功能。而S-mode的RT负责在enclave中运行的user code的生命周期,管理内存,系统调用,与SM进行通信,使用SBI(supervisor binary interface)代表eapp请求SM的操作。</li><li>设计模块化的层。Keystone使用模块化的层次(SM,RT,eapp)来支持不同的工作负载。(每个层都是独立的,上一层的安全性可以由下一层来进行检查?)</li><li>允许细粒度的TCB配置。Keystone可以根据用户的需求来实例化一个拥有最小TCB的TEEs、</li></ul><h3 id="3-2-Keystone-Enclave工作流"><a href="#3-2-Keystone-Enclave工作流" class="headerlink" title="3.2 Keystone Enclave工作流"></a>3.2 Keystone Enclave工作流</h3><p>platform用一个比较合适的硬件配置和安全扩展来初始化SM,这些安全扩展能够带来额外的隔离保证,比如cache partitioning;enclave开发者使用诸如virtual memory management和系统调用等丰富的功能来编写eapps和RTs。</p><p><img src="/2021/01/29/2021-01-29-keystone-2020/2.png" alt="image-20200914221209627"></p><h3 id="3-3-编写eapps"><a href="#3-3-编写eapps" class="headerlink" title="3.3 编写eapps"></a>3.3 编写eapps</h3><p>对于编写enclave application,Keystone提供了3种方法:(用英文原文)</p><ul><li>独立的Keystone原生eapps</li><li>具有RT支持的未修改的RISC-V binaries</li><li>partitioned application running in the selected parts in the enclave</li></ul><h3 id="3-4-威胁模型"><a href="#3-4-威胁模型" class="headerlink" title="3.4 威胁模型"></a>3.4 威胁模型</h3><p><strong>假设</strong></p><ul><li>Keystone设计框架相信PMP规范,认为PMP和RISC-V硬件都是无bug的</li><li>只有当验证完SM的测量值是正确的(被可信硬件签名且是正确的版本号),Keystone user才会信任SM</li><li>SM只信任硬件,host信任SM,RT信任SM,eapp信任SM和RT</li><li>SM,RT和eapp都是bug-free的,这可以通过formal verification来完成(eapp不可能是恶意的吗?)(对RT和eapp进行充分的检查,来</li></ul><p><strong>攻击者模型</strong></p><ul><li>physical attacks:拦截、修改、重放芯片发出的信号,假设物理攻击者不会修改芯片包内部的组件</li><li>software attacks:控制host app,untrusted OS,网络通信,配置攻击enclaves,任意篡改未受保护的内存,篡改enclave信息</li><li>side-channel attacks:通过观察可信组件和不可信组件之间的交互收集信息,分三种:cache side-channel,timing side-channel,controlled channel</li><li>denial-of-service attacker:take down enclave或者host OS</li></ul><p>Keystone没有针对speculative execution attacks和timing side-channel attacks的防护(相关的防护措施,需要程序开发人员和硬件制造商来完成),也不考虑off-chip的侧信道攻击,比如说memory bus中的侧信道攻击</p><h2 id="4-Keystone-Security-Monitor"><a href="#4-Keystone-Security-Monitor" class="headerlink" title="4 Keystone Security Monitor"></a>4 Keystone Security Monitor</h2><p>SM使用的是RISC-V平台上的标准功能,所以很容易与RISC-V平台兼容。</p><h3 id="4-1-内存隔离"><a href="#4-1-内存隔离" class="headerlink" title="4.1 内存隔离"></a>4.1 内存隔离</h3><p>PMP是由RISC-V提供的一项功能,它通过PMP条目限制S-mode和M-mode对物理内存区域的访问。</p><p><img src="/2021/01/29/2021-01-29-keystone-2020/3.png" alt="image-20200915163351206"></p><p>使用SM来完成memory isolation.</p><ul><li><p>所有的enclave不是共享一块大的内存区域,而是以多个不连续的enclave内存区域形式存在</p></li><li><p>PMP条目可以覆盖4bytes到DRAM的全部内存区域(enclave的地址范围不是固定的)</p></li><li><p>PMP条目分配得到的物理内存区域可以在运行的时候动态调节(可以随时多申请一块内存区域或者释放一块内存区域)</p></li></ul><p>SM首先给自己配置一块PMP entry来覆盖自己的内存区域,然后配置最低的PMP entry覆盖所有的内存并且允许所有mode访问。</p><p>OS创建一个enclave,然后找到一块合适的连续的物理内存通知SM。SM进行验证后,添加一个PMP条目并disable该条目的所有权限。OS和其他的进程都不能访问该PMP条目。</p><p><strong>内核之间的PMP实施</strong></p><p>每个内核都拥有自己的PMP条目的完整列表。对PMP条目的修改会通过inter-processor interrupts(IPIs)扩散到所有的内核中。在enclave执行的过程中,对PMP条目的修改只会在本地完成,不会扩散到其他内核中,IPIs的同步只会在enclave的创建和销毁中执行。(当enclave要进行扩容的时候,是否需要进行同步?如果不进行同步的话,一块内存是否会同时被两个enclave申请)</p><h3 id="4-2-enclave中的页管理-创建后"><a href="#4-2-enclave中的页管理-创建后" class="headerlink" title="4.2 enclave中的页管理(创建后)"></a>4.2 enclave中的页管理(创建后)</h3><p>在初始化过程中使用OS生成的页表,而在执行过程中,将virtual-to-physical的页表映射的任务完全交给enclave来完成。</p><p><img src="/2021/01/29/2021-01-29-keystone-2020/4.png" alt="image-20200915213129136"></p><p>enclave中具有S-mode的RT,所以keystone能够操纵特定enclave的页表来管理自己的virtual memory。–》消除了controlled side-channel attacks。</p><h3 id="4-3-中断和异常"><a href="#4-3-中断和异常" class="headerlink" title="4.3 中断和异常"></a>4.3 中断和异常</h3><p>在enclave执行期间,所有的machine interrupts 都由SM直接捕获。异常(page faults)会通过RISC-V的exception delegation register由RT代理,进而将异常报告给untrusted OS。</p><p>为了应对针对某个内核的DoS攻击,SM设置了一个machine timer,一旦timer interrupt触发,SM将会重新获取控制权–》DoS攻击的应对</p><h3 id="4-4-Enclave生命周期"><a href="#4-4-Enclave生命周期" class="headerlink" title="4.4 Enclave生命周期"></a>4.4 Enclave生命周期</h3><ul><li><p>creation:Keystone测量enclave memory来保证OS在物理内存中加载了正确的enclave binaries。(这里的测量值是initial virtual memory,所以,SM需要OS来初始化enclave页表,并且给enclave分配physical memory。SM walk OS提供的页表并检查其中是否有不合法的映射,并且保证一个虚拟地址只能映射到一个物理地址。接着计算page content和virtual address,configuration data的哈希值)</p></li><li><p>execution:SM设置PMP entry,并将控制权移交给enclave entry point</p></li><li><p>destruction:在将控制权还给OS之前清除enclave memory region</p></li></ul><h3 id="4-5-TEE原语"><a href="#4-5-TEE原语" class="headerlink" title="4.5 TEE原语"></a>4.5 TEE原语</h3><p>secure boot. Keystone的root-of-trust可以是一个temper-proof software或者是hardware。在每次CPU的reset阶段,root-of-proof</p><ul><li>测量SM image</li><li>从secure source of randomness生成一个新鲜的认证密钥</li><li>将认证密钥存储到SM memory中</li><li>用hardware-visiable key对测量结果和public key进行签名</li></ul><p>secure boot的实现可以有多种方式,目前通过一个修改的first-stage bootloader模拟一个secure boot。</p><p>secure source of randomness. SM提供了一个安全的SM SBI 调用-random,它能够返回64bit的随机值(没说具体咋实现的,而且应该不是依赖于硬件)</p><p>remote attestation:SM基于provisioned key来实现measurement和attestation。Keystone通过在signed attestation report中加入limited arbitrary data将attestation值与secure channel绑定起来。</p><p>其他的primitives. 1)通过标准rdcycle指令允许enclaves访问read-only hardware-maintained timer register;2)在SM内存中保持一个limited counter state(有限的计数器状态)来提供单调计数器。通过这些特征能够提供可信计数器,rollback defense以及sealed storage等。</p><h3 id="4-6-特定平台的扩展"><a href="#4-6-特定平台的扩展" class="headerlink" title="4.6 特定平台的扩展"></a>4.6 特定平台的扩展</h3><p><strong>secure on-chip memory.</strong> 利用L2 memory controller动态实例化一个最高可以到2MB的scratchpad memory,该内存区域可以用来生成一个usable on-chip memory region。(这边的on-chip memory region是将L2 cache分成一部分,这部分只能由特定的enclave来使用吗)该scratchpad memory可以分配给一个enclave,给enclave在自己的运行期间就可以独占该scratchpad memory。</p><p>如果一个enclave需要运行在on-chip memory,跟标准过程的差别仅在:</p><ul><li>enclave在被加载后,会被分配一个经过修改的page tables,该页表指向final scratchpad address</li><li>SM在进行measurement之前将标准enclave memory region复制到新的scratchpad region中。</li></ul><p>–》保护免受可以进入DRAM的物理攻击者的攻击</p><p><img src="/2021/01/29/2021-01-29-keystone-2020/5.png" alt="image-20200916095137404"></p><p>cache分块。实现的方式:</p><ul><li>类似于intel’s CAT(对CAT的理解是,通过增加一个CLOS位,管理缓存将缓存分给特定的应用,在本文中就是分给特定的enclave)的L2 cache controller’s waymasking primitive</li><li>PMP以透明的方式对L2 cahce进行分区,将其分配给OS和enclave</li></ul><p>在上下文切换的时候,enclave对应的cache partition中的值会被flushed。而在运行时,enclave physical memory中的值可以很好地被PMP保护。</p><p>–》抵御cache side-channel attacks</p><p><img src="/2021/01/29/2021-01-29-keystone-2020/6.png" alt="image-20200916105840680"></p><p>动态调整大小。(SBI是由SM提供的API,不同的caller通过调用SBI,能够执行不同的功能)</p><p><img src="/2021/01/29/2021-01-29-keystone-2020/7.png" alt="image-20200916112211215"></p><p>静态地定义了最大的enclave size,并分配static physical or virtual memory:1)避免因为workload而动态扩展大小;降低复杂性5 </p><p>当需要扩容的时候,RT需要OS发出SBI调用,来给enclave memory region增加连续的物理页。当成功分配后,SM通过扩容对应PMP条目来增加enclave的大小并通知RT。 </p><h2 id="5-Keystone-模块化runtime"><a href="#5-Keystone-模块化runtime" class="headerlink" title="5 Keystone 模块化runtime"></a>5 Keystone 模块化runtime</h2><p>enclave中的RT提供的功能类似于一个kernel,但是它并不需要大多数的kernel功能,作者构造了一个Eyrie的RT,其中仅仅包含了必要的功能从而降低了TCB。(仅允许RT访问shared memory buffer;另外,允许轻松移植成熟的微内核)</p><h3 id="5-1-enclave内存管理模块"><a href="#5-1-enclave内存管理模块" class="headerlink" title="5.1 enclave内存管理模块"></a>5.1 enclave内存管理模块</h3><p>默认情况下,Keystone enclave会占用一块由OS分配的固定的连续物理内存,该内存在加载之时就被静态映射到了一块virtual memory。(Keystone enclaves occupy a fixed contiguous physical memory allocated by the OS with a statically-mapped virtual address space at load time. )</p><p><strong>释放内存。</strong> 在enclave保存unmapped physical memory之后,<strong>构造一个模块让Eyrie RT来进行page table的管理</strong>。未被映射的memory region在eapp执行之前将被设置为0。–》page table management</p><p>In-Enclave自分页。为了Eyrie RT构造了一个in-enclave page wapping模块。该模块负责处理page-faults,并使用一个通用page backing-store来管理evicted page的存储和恢复。(页表驱逐策略是一个简单的random eapp-only page eviction policy)自分页和释放内存两个一起负责管理Eyrie RT中的virtual memory management。</p><p>保护离开enclave的页表内容。当某个enclave处理自己的page fault,那么就需要将secure physical memory中的部分页面驱逐出去。当这些页面被copied out时,它们的内容也需要被保护。完成了一个backing-store layer来完成page encryption和integrity protection来允许页表中的内容被驱逐到insecure storage中。这部分可以考虑由RT或者指定的trusted hardware unit——内存加密引擎来完成。</p><p>free-》page table管理,self-page-》wapping管理 前两者共同管理virtual memory</p><h3 id="5-2-功能性模块"><a href="#5-2-功能性模块" class="headerlink" title="5.2 功能性模块"></a>5.2 功能性模块</h3><p>Edge Call Interface. eapp不能访问Keystone中的非enclave memory。所以如果需要读取enclave以外的数据,Eyrie RT需要代表eapp执行edge calls。Eyrie将该调用安全传送到untrusted host,并将返回值传送给enclave,最后传给eapp。具体的实现方案是:</p><p>1)OS将host memory space中的一块shared buffer给SM;2)SM将地址传递给enclave使得RT能够访问该内存区域;3)SM使用一块单独的PMP条目来使得OS能够访问shared buffer。</p><p>通过该interface,Keystone可以利用现有的攻击措施来抵御Iago attacks。</p><p><strong>多线程。</strong> 通过将线程管理授权给runtime来实现multi-threaded eapps。(多个RT能够同时运行自己的eapps)</p><p>并不支持parallel multi-core enclave execution(多个处理器同时执行多个任务,每个任务分配在一个处理器上执行) (但是这可以通过SM在不同的内核中多次调用enclave execution,这个的意思是不是SM一次在core0上调用enclave execution,接下来再去在core1上调用enclave execution)</p><h2 id="6-安全分析"><a href="#6-安全分析" class="headerlink" title="6 安全分析"></a>6 安全分析</h2><h3 id="6-1-对enclave的保护"><a href="#6-1-对enclave的保护" class="headerlink" title="6.1 对enclave的保护"></a>6.1 对enclave的保护</h3><p>软件攻击者无法访问enclave memory</p><p>controlled side-channel无法实施:因为enclave拥有对应的page management和in-enclave page tables。</p><p>mapping attacks. 受信任的RT在enclave creation阶段来对page tables进行初始化或者加载由SM验证过的static mapping的页表。在动态内存调整时,RT会在映射到enclave中首先检查它们是否安全并在将内存还给OS之前,清空其中的内容。</p><p>syscall tampering attacks。(伪造系统调用攻击)–》利用现有的防御机制来作为RT模块的插件来抵御这种攻击</p><p>侧信道攻击:由于enclave不会和host OS或者其他的用户应用程序分享状态,因此不会受到controlled channel attacks的攻击。干净的上下文切换</p><h3 id="6-2-保护host-OS"><a href="#6-2-保护host-OS" class="headerlink" title="6.2 保护host OS"></a>6.2 保护host OS</h3><p>内存隔离</p><p>第一步,OS能够访问OS memory来进行boot操作</p><p>这边的图 是说enclave在执行的时候,所有的部分都能够读取enclave memory(all_perm)吗?但是如果两个enclave同时运行时,不应该只能一个keystone访问属于自己的内存区域,而不能访问其他的内存区域</p><h2 id="7-PMP"><a href="#7-PMP" class="headerlink" title="7 PMP"></a>7 PMP</h2><p>几个概念</p><ul><li>WPRI:Reserved Writes Preserve Values, Reads Ignore Values</li><li>WLRL: Write/Read Only Legal Values</li><li>WARL: Write Any Values, Reads Legal Values </li></ul><p>关于RISC-V的知识点</p><p>Sv32分页虚拟内存模式下,RV32可能会拥有34位物理地址空间-》支持34位的物理内存访问管理。</p><p>Sv32的虚拟地址是32位的,每个页的大小为4KB,它的物理地址是34位的。supervisor virtual address被转化为supervisor physical address需要一个two-level page tables,20-bit的VPN被转化为22-bit的physical page number(PPN),在最终转化为machine-level physical address之前,需要一些physical memory protection structures(PMP)。</p><p><img src="/2021/01/29/2021-01-29-keystone-2020/11.png" alt="image-20200919150238758"></p><p>PMP checks are also applied to page-table accesses for virtual-address translation, for which the effective privilege mode is S. </p><p>PMP仅支持固定数量的内存区域。</p><p>PMP entries是由一个8-bit configuration register和一个MXLEN-bit address register描述的。最多仅能支持16个PMP entries。仅有M-mode能够访问PMP CSRs。</p><p>控制寄存器是8位(1个字节)的,但是在实际的机器中是不会存在8位的寄存器,所以其实是将一个CSR分成4个pmp entry configuration register.</p><p>下图中展示的是RV32,用4个CSRs,pmpcfg0-pmpcfg3来保存pmp0cfg-pmp15cfg这16个PMP entries.</p><p><img src="/2021/01/29/2021-01-29-keystone-2020/8.png" alt="image-20200918171027557"></p><p>而在RV64中,pmpcfg0和pmpcfg2保存了16个PMP entries。将pmp8cfg到pmp15cfg保存在pmpcfg2,是因为在RV32和RV64的情况下,pmp8cfg到pmp11cfg都保存在pmpcfg2中,这样就可以减少对64位支持的开销。</p><p><img src="/2021/01/29/2021-01-29-keystone-2020/9.png" alt></p><p>而保存PMP address registers的是pmpaddr0-pmpaddr15这些CSRs。在RV32中,每个PMP address registers编码34bit物理地址中的2-33bit;在RV64中,每个PMP address registers编码56bit物理地址中的2-55bit。(Each PMP address register<br>encodes bits 33–2 of a 34-bit physical address for RV32, as shown in Figure 3.29. For RV64, each PMP address register encodes bits 55–2 of a 56-bit physical address, as shown in Figure 3.30. )(下图我认为3.29应该是到33结束)并不是所有的地址位都被设置了,所以pmpaddr registers也是WARL。</p><p><img src="/2021/01/29/2021-01-29-keystone-2020/10.png" alt="image-20200919135409757"></p><p>PMP configuration registers的构造如下图所示。R,W,X设置分别代表可读,可写,可执行。如果设置R=0,W=1,那么该寄存器将会被reserved for future use. </p><p>A:对相关的PMP address register的address-matching mode进行编码</p><p><img src="/2021/01/29/2021-01-29-keystone-2020/12.png" alt="image-20200919164058052"></p><p>(Keystone中仅支持TOR和NAPOT)</p><p><img src="/2021/01/29/2021-01-29-keystone-2020/13.png" alt="image-20200919165823798"></p><p>NAPOT是利用address registers的low-order bits来定义地址范围</p><p>类型为NAPOT的情况下:</p><p>yyyy…yyy0,连续1的个数为0,则XLEN=0,NAPOT range为2^(0+3)=8</p><p>yyyy…yy01,连续1的个数为1,则XLEN=1,NAPOT range为2^(1+3)=16</p><p>1111…1111,连续1的个数为XLEN,NAPOT range为2^(1+XLEN)</p><p>如果是y…y01…1,连续1的个数为n,则该PMP entry所控制的地址空间为从y…y00…0开始的2^(n+3)个字节</p><p>类型为NA4(Naturally Aligned Four-byte regions)的情况下:</p><p>当pmpaddr值为yyyy…yyyy,那么控制的地址范围就是从yyyy…yyyy开始的4个字节</p><p>类型为TOR的情况下:</p><p>该PMP entry所控制的地址范围由前一个地址寄存器和后一个地址寄存器共同决定,也就是匹配满足以下条件的地址y:</p><p>pmpaddri-1≤y≤pmpaddri</p><p>当第0个PMP entry的A字段为TOR,其所控制的地址空间的下界被认为是0,也就是匹配所有满足 0≤y≤pmpaddr0</p><p><img src="/2021/01/29/2021-01-29-keystone-2020/14.png" alt="image-20200919193458254"></p><p>L bit:锁定PMP entry。</p><p>当L bit=1时,对内存区域的访问将会应用在所有的privilege modes。(U, S, M模式都必须遵循配置寄存器的权限设置)</p><p>当L bit=0时,符合PMP entry的M-mode访问都会成功;S和U模式下需要遵循配置寄存器中的权限设置。</p><h3 id="7-1-物理内存保护以及分页"><a href="#7-1-物理内存保护以及分页" class="headerlink" title="7.1 物理内存保护以及分页"></a>7.1 物理内存保护以及分页</h3><p>通过S mode去对页表访问是最有效的。</p><p>当装有页表或者是指向page table的physical memory发生改变时,M-mode的软件必须在virtual memory system中同步PMP设置。这部分是通过SFENCE.VMA指令(rs1=x0, rs2=x0)来完成的。</p><h3 id="7-2-waymasking"><a href="#7-2-waymasking" class="headerlink" title="7.2 waymasking"></a>7.2 waymasking</h3><p>对waymasking的理解是通过waymask寄存器来允许特定的master访问某些way</p><p>waymaskX registers:仅影响分配,仍然可以尝试读取被masked的ways。</p><p>有16个waymaskX寄存器,分别是waymask0-waymask15,waymaskX register表示的是该L2 cache能够被master X驱逐。</p><p>在enclave执行过程中,只有enclave physical memory中的enclave lines会在对应的cache partition中,所以就会被PMP保护。</p><p>(我觉得目前,可能是直接把8个分区,比如0-7直接分给了keystone。如果一个时刻只有一个enclave运行的话,这些分区就不需要再细分。FU540中一共分了8个pmp entry,在waymasking的master这边有8个chiplink domain,可能就是一一对应的关系)</p><p>比如设置WayMask0[2].RW = 0x1,就表示enable way2 for Master0</p><p><img src="/2021/01/29/2021-01-29-keystone-2020/15.png" alt="image-20200920163932004"></p><p><img src="/2021/01/29/2021-01-29-keystone-2020/C:%5CUsers%5Crww%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20200920164733863.png" alt="image-20200920164733863"></p>]]></content>
<summary type="html">
<p>Keystone论文的精读笔记~</p>
</summary>
<category term="Keystone" scheme="http://yoursite.com/categories/Keystone/"/>
<category term="paper" scheme="http://yoursite.com/tags/paper/"/>
<category term="RISC-V" scheme="http://yoursite.com/tags/RISC-V/"/>
<category term="Keystone" scheme="http://yoursite.com/tags/Keystone/"/>
</entry>
<entry>
<title>CITM CCS20</title>
<link href="http://yoursite.com/2021/01/29/2021-01-29-CIMT/"/>
<id>http://yoursite.com/2021/01/29/2021-01-29-CIMT/</id>
<published>2021-01-28T16:00:00.000Z</published>
<updated>2021-01-29T14:38:34.718Z</updated>
<content type="html"><![CDATA[<p>Cache-in-the-Middle (CITM) Attacks : Manipulating Sensitive Data in Isolated Execution Environments的精读笔记~</p><p>这篇论文是我在组会讲解的第一篇论文,也很感激第一篇讲的是这篇,因为这篇完全就是中国人写的感觉,推荐阅读原文,非常好读懂</p><a id="more"></a><p>TrustZone的制造商希望通过约束安全世界中的第三方应用程序的安装来最小化可信计算基(TCB),但是第三方的开发人员更希望能够在安全世界中自由地安装自己的应用。</p><p><strong>解决方法</strong>:在普通世界中构造了一个Isolated Execution Environment(IEEs)来保护安全敏感的应用程序。</p><p><strong>本文所完成的工作:</strong>针对IEE的数据保护模型(data protection models)和ARM cache属性(cache attributes),发现了三种基于cache的漏洞(CITM 漏洞),这些漏洞能够被用来操纵保护在IEE内部的敏感数据。</p><p>另外,由于映射到IEE内存中的缓存的安全措施的低效和不连贯的,普通世界中的攻击者能够通过以下几点措施降低IEE数据的安全性:</p><p>1)并发执行;</p><p>2)在安全敏感的应用程序被suspend或者终止(finished)时绕过强制的安全措施;</p><p>3)在IEE上下文切换进程中国措施使用不完整的安全措施。</p><p><strong>完成的工作:</strong></p><p>1)作者通过在一些著名的IEE systems中,包括SANCTUARY,Ginseng和TrustICE进行案例研究揭示了CITM漏洞的广泛存在;</p><p>2)CITM漏洞是能够在硬件测试床上利用的</p><p>3)分析导致CITM的漏洞的根本原因,并提供了解决对策。</p><p>实验证明我们的防御测试具有一个很小的开销。</p><h2 id="1-介绍"><a href="#1-介绍" class="headerlink" title="1 介绍"></a>1 介绍</h2><p>IEEs的主要思想是通过一个安全世界中的trusted reference monitor确保只有授权的IEE app能够访问IEE敏感资源。</p><p><img src="/2021/01/29/2021-01-29-CIMT/image-20200824180749550.png" alt="image-20200824180749550"></p><p>Ginseng:在普通世界中构造第三方应用程序,同时避免部署安全世界中的application-specific logic( Ginseng [55] constructs the IEEs to protect secrets of third-party applications in the normal world without deploying any application-specific logic in the secure world.这段的意思是否是不依赖于安全世界中的特殊逻辑)</p><p>SANCTUARY:将它的IEEs定位在per-core环境中来保护IEE指定的内存(避免这些受保护的内存被其他非安全的内核访问)</p><p>TrustICE:通过IEE monitor动态控制IEE memory的安全属性来保护IEE内存。</p><p><strong>攻击方法</strong></p><ul><li>攻击者在并发执行中通过cross-core缓存操作操纵IEE数据;(侧信道攻击?)(在多核系统上,仅仅core-wise的隔离是无法保证安全的,因为缓存是允许cross-core的访问,因此能够被攻击者利用)–》针对Sancutary</li><li>通过将不安全的缓存映射到用于安全措施的IEE内存中来绕过switch out操作后的安全防护操作;–》针对Ginseng</li><li>针对动态控制内存的安全属性的方法,作者认为在switch out过程中能够窃取敏感IEE数据,而在switch in的过程中可以伪造IEE数据。–》针对TrustICE</li></ul><p>本文指出了为了确保IEE数据的安全性,除了内存以外,保护缓存安全的重要性。</p><p><strong>根本原因</strong></p><ul><li>缓存和内存安全属性的非相关性(这个非相关性如何理解?)</li><li>缓存和内存读写操作的不同步</li></ul><p><strong>本文工作</strong></p><ul><li>为IEE内存安全配置cache attributes</li><li>在进行上下文切换时,清除cache mapping to the IEE 内存(难道其他三个没有进行这步的操作吗?)</li></ul><p><strong>名词解释</strong></p><p>switch out:从IEE到untrusted rich OS的上下文切换,switch out之前是secure的</p><p>switch in:逆过程。switch in之前是non-secure的</p><p>cross-core cache:意味着这个cache是被多个核共享的,比如l2 cache</p><h2 id="2-背景知识"><a href="#2-背景知识" class="headerlink" title="2 背景知识"></a>2 背景知识</h2><p>L1-cache被进一步分为 I-cache(指令cache)和D-cache(数据cache)</p><p>L1-cache和L2-cache都被配置为N-way Set Associative cache。</p><p>全部的cache空间被分为N equally-sized块,也就是N路,每一路用k cache lines进行索引,每个cache line是一个数据单元。内存被分块成大小为一个cache line的多个数据块。j = i mod k</p><p>inner cacheability domain–>L1 cache</p><p>outer cacheability domain–>L2 cache</p><p>cacheability domain’s attributes:</p><ul><li>non-cacheable:内存上的任何读写操作都不会经过该cacheability domain;</li><li>write-through:在当前的cache上进行的写操作会立刻被传送到下一级别的storage;(L1 cache上的读写会被forwarded 到L2 cache——》主存)</li><li>write-back:当前cache上的改变暂存在缓冲区中,当被驱逐时写入下一级的存储部件中</li><li>write-allocate:当进行写操作时出现cache miss,该写结果将会被分配一个新的cache line,如果write-allocate未被设置,cache-missed write会对next level storage进行修改</li></ul><p>invalidation指令:储存在cache中的数据全部失效;</p><p>cleaning指令:将目标缓存上的内容强制写入下一级存储设备或者主存</p><h2 id="3-Threat-Model"><a href="#3-Threat-Model" class="headerlink" title="3 Threat Model"></a>3 Threat Model</h2><p>normal world中的rich OS是不可信的;</p><p>攻击者具有root权限,目的是破坏IEE中的敏感数据的机密性和完整性;</p><p>TrustZone能够提供normal world和secure world之间的安全隔离,安全世界中运行的app是可信的,并且是不会被rich OS降低安全性的;</p><p>安全敏感的app在IEE中运行,不会故意泄露信息。</p><h2 id="4-CITM漏洞"><a href="#4-CITM漏洞" class="headerlink" title="4 CITM漏洞"></a>4 CITM漏洞</h2><h3 id="4-1-IEE数据保护模型"><a href="#4-1-IEE数据保护模型" class="headerlink" title="4.1 IEE数据保护模型"></a>4.1 IEE数据保护模型</h3><p>本部分介绍IEE中的两种数据保护模型。</p><p>(1)安全敏感app能够和不可信app同时运行在normal world中的两个或多个core中。(在安全敏感app执行时,不可信app是可以运行的)</p><p>当安全敏感app在normal world中的一个核上运行时,不可信进程可以同时运行在不同的核或者以分时的状态(in a time-sharing manner)运行在相同的内核中。该模型的安全措施:–》SANCTUARY&Ginseng</p><ul><li>在并发执行时,敏感app使用core-isolated storage(不允许其他core访问的内存或者说是core上的寄存器)传递隐私数据;</li><li>在敏感app suspend或者执行结束时,它core-isolated storage中的所有的隐私数据会在switch out过程中被清除;</li><li>在安全敏感的app重新运行时,在switch in过程中由IEE monitor负责恢复或者分配新的core-isolated storage</li></ul><p>(2)不可信进程不被允许同时和安全敏感 app运行在normal world中。</p><p>在normal world中的任何时间,所有的core只能同时运行安全敏感的app或者是不可信进程。但是在IEE进行上下文切换的过程中,仍然需要继续安全操作。除了在model中介绍的措施外,还需要配置:</p><ul><li>在switch out进程中,对于normal world来说,IEE内存无法被访问;</li><li>在switch in进程中,允许normal world对IEE内存的访问。</li></ul><h2 id="4-2-CITM-Attack-Types"><a href="#4-2-CITM-Attack-Types" class="headerlink" title="4.2 CITM Attack Types"></a>4.2 CITM Attack Types</h2><p>当不可信rich OS操纵normal world中的cache时,两种数据保护模型的安全性都将被降低。从攻击者的角度,可以从两个方面进行攻击:</p><ul><li>manipulating the core-isolated memory (在并发执行中)</li><li>篡改IEE的上下文切换</li></ul><h3 id="漏洞1:在并发执行时操纵core-isolated内存"><a href="#漏洞1:在并发执行时操纵core-isolated内存" class="headerlink" title="漏洞1:在并发执行时操纵core-isolated内存"></a>漏洞1:在并发执行时操纵core-isolated内存</h3><p>在多核系统上,与安全敏感的app并发执行的恶意OS可以通过操纵normal world中的cache来窃取或者修改core-isolated 内存中的IEE数据。因为安全敏感的APP运行在normal world中,而它相应的cache line被标记为non-secure,所以能够被不可信rich OS操纵。(伪造页表,用一个物理地址指向一个core-isolated memory page,恶意OS能够通过访问相应的虚拟地址操纵它的cache line)</p><p>产生的原因:ARM平台提供了保证memory isolation的硬件特性,但缺少相应的针对cache isolation的特性。(比如,TZC-400的Identity-based Filtering features可以用于隔离内存,但不能保证cache的隔离)</p><h3 id="漏洞2:在IEE的switch-out过程中绕过安全措施"><a href="#漏洞2:在IEE的switch-out过程中绕过安全措施" class="headerlink" title="漏洞2:在IEE的switch out过程中绕过安全措施"></a>漏洞2:在IEE的switch out过程中绕过安全措施</h3><p>switch in过程是IEE monitor在secure world中完成的,所以被cache line被配置为secure,所以是很难绕过安全检查并利用,但是switch out不管是由normal world还是secure world中的IEE执行,都是可以绕过安全操作的。</p><p>memory cleaning是在normal world中被执行的,相应的cache line是non-secure的,所以有可能通过控制non-secure cache限制cache中的内存写操作或者保持内存不变。比如,当内存被设置为write-back,write-allocate时,所有的内存写都会被暂存在缓冲区中直到cache set被驱逐。攻击者通过使用lockdown技术阻止cache eviction,进而直到敏感app被悬挂或者终止时,隐私数据都没有被安全地写入IEE内存。</p><p>当安全措施需要在secure world中执行时,IEE需要在不涉及rich OS的情况下将控制权转移到secure world中的IEE monitor。从normal world到secure world的上下文切换是由rich OS kernel中的Secure Monitor Call(SMC)高特权指令完成的。SMC是无法由进程或者app直接触发的,所以安全敏感app通过故意访问secure memroy来故意触发一个external abort。但是只要是在normal world中被访问,安全内存所被映射的cache lines就是non-secure的。(即使是secure world中的secure memory,一旦被normal world中的non-secure cache 映射了,就会变成non-secure),OS就会操纵相应的cache lines来绕过上下文切换和安全措施(data cleaning)。</p><h3 id="漏洞3:IEE-cache上下文切换时不完整的安全措施"><a href="#漏洞3:IEE-cache上下文切换时不完整的安全措施" class="headerlink" title="漏洞3:IEE cache上下文切换时不完整的安全措施"></a>漏洞3:IEE cache上下文切换时不完整的安全措施</h3><p>某些IEE系统在上下文切换中的内存保护是通过动态控制IEE内存的安全属性来完成的(TrustICE)(在switch in过程是不安全的,在switch out过程是安全的)</p><p>switch out阶段不合适的cache cleaning可能会导致IEE data leakage;有害数据可能在switch in阶段被加载入安全敏感的APP。漏洞3是由于cache上不完整的安全措施,而漏洞2是在上下文切换的时候绕过安全检查。</p><p>Model1–》3种漏洞</p><p>Model2–》2和3</p><p>漏洞1,2–》当内存用于存储IEE数据,能够被利用(为什么?)</p><p>漏洞3–》只有在IEE执行switch out的安全措施使才有此要求</p><h2 id="4-3-Cache-lockdown技术"><a href="#4-3-Cache-lockdown技术" class="headerlink" title="4.3 Cache lockdown技术"></a>4.3 Cache lockdown技术</h2><p>cache lockdown:允许程序加载代码和数据到cache中,并标记它们不被eviction(驱逐)–》提高更快的系统反应速度,减少执行时间</p><p>但是CITM中的漏洞2可以利用该lockdown技术,将cache中针对内存的写操作锁住,从而使memory-cleaning操作无效。</p><p>实现cache lockdown的三个方法:</p><ul><li>一些ARM开发板允许用户通过配置L2 auxiliary cache control register来lock L2 cahce ways。–》ARMv8处理器目前已经不支持该基于硬件的locking control 寄存器;</li><li>攻击者将自己控制的内存区域设置为outer cacheable,其他内存区域全部设置为outer non-cacheable,从而独占L2缓存–》huge overhead</li><li>更细粒度地控制内存页面的缓存属性从而独占L2缓存</li></ul><p>相同编号的blocks构成了一个page cache set,攻击者将想要控制的内存页面标记为outer cacheable,其他标记为non-cacheable。</p><p><img src="/2021/01/29/2021-01-29-CIMT/image-20200825222106254.png" alt="image-20200825222106254"></p><p>page1和page6分享相同的page cache set,page1设置为cacheable,而page6被设置为non-cacheable(这边即使是修改了page1,由于page1没有被驱逐,所以page1的修改不会被写入到内存中)</p><p>本文选择了使用第三种方法,针对L2 cache实施了cache lockdown。</p><h2 id="5-case-study-of-CITM-attacks"><a href="#5-case-study-of-CITM-attacks" class="headerlink" title="5 case study of CITM attacks"></a>5 case study of CITM attacks</h2><p>SANCTUARY实际上是在ARM fast models virtualization tools上完成的,本文作者在i.MX6Quad开发板上模拟了SANCTUARY的cache操作,Ginseng在HiKey620开发板,TrustICE在i.MX6Quad SABRE开发板上实现。</p><h3 id="5-1-SANCTUARY:操作L1-cache"><a href="#5-1-SANCTUARY:操作L1-cache" class="headerlink" title="5.1 SANCTUARY:操作L1 cache"></a>5.1 SANCTUARY:操作L1 cache</h3><p>每个IEE运行在拥有core-isolated内存的core中,并且每次运行时IEE不会被其他core干扰。</p><h4 id="5-1-1-SANCTUARY的数据保护机制。"><a href="#5-1-1-SANCTUARY的数据保护机制。" class="headerlink" title="5.1.1 SANCTUARY的数据保护机制。"></a>5.1.1 SANCTUARY的数据保护机制。</h4><p>switch out:当IEE结束运行,隐私数据将会被IEE中的微内核清除(向受保护的core-isolated memory中写入全0,并使L1 cache无效)(L2cache在SANCTUARY中被禁止给core-isolated的内存使用)</p><p>switch in:在启动IEE之前,secure world中的IEE monitor将会为IEE建造一个干净的环境。在敏感app加载之前,L1 cache也会被invalided。</p><p>由于在switch in和switch out过程中,core-isolated memory和L1 cache都被安全地清空,所以对于漏洞3是免疫的。由于data cleaning操作是被IEE中的微内核完成的,所以对于漏洞2也是免疫的。如果想要绕过data cleaning,那么就必须锁住L1 cache,但是L1 cache的eviction是由该内核以及core-isolated内存页表的cache属性决定的,攻击者无法通过操纵另一个内核来控制eviction。(core-isolated内存的cache属性是由IEE中的微内核来控制和保护的)</p><p>SANCTUARY给每个内核分配一个独特的Non-Secure Access IDentifier(NSAID),并且通过配置TZC-400给每个CPU(根据NSAID)分配独立的内存区域。ARM平台上的NSAID都是相同的,所以这部分是在ARM的快速模型虚拟化工具上来完成的。</p><p>受保护的内存区域通过配置cache属性为outer non-cacheable来避免缓存到L2 cache中,而L1 cache一般位于每个内核的内部,不会直接被其他内核访问,但除此之外,SANCTUARY就没有在并发执行时给予其他的保护。</p><h4 id="5-1-2-SANCTUARY中的漏洞"><a href="#5-1-2-SANCTUARY中的漏洞" class="headerlink" title="5.1.2 SANCTUARY中的漏洞"></a>5.1.2 SANCTUARY中的漏洞</h4><p>通过调研,作者发现了名为shareability的缓存特性,该特性能够通过操作一个内核的L1 data cache进而读/写另一个内核的L1 data cache。–》缓存的一致性</p><p>inner shareability domain:在一个cluster中的core的数据一致性(一个cluster表示one group of cores)</p><p>outer shareability domain:所有的cluster的core的数据一致性</p><p>当处理器运行在Symmetric Multi-Processing(对称多核处理器,SMP)模式时,shareability属性将会被配置。(SMP模式在多核平台中是默认设置的)当shareability属性被设置,Snoop Control Unit(窥探控制单元,SCU)会通过处理直接的核之间cache-to-cache转移来保证数据一致性。如果设置为inner shareable,在某个内核中的L1 data缓存中的某个值的修改会同步到该簇上其他内核的L1数据缓存。但是non-secure cache中的数据不会扩散到secure cache。</p><p>结论:当内核都运行在normal world中,访问相同的物理内存地址,并且该物理内存的内存页表的缓存属性都是inner shareable或者outer shareable时,某个内核上的L1数据缓存可能会被另一个内核上的L1数据缓存读取或者篡改。(当设置为non-shareable时,就不会被窃取或者篡改)</p><p>(IEE能否去将SANCTUARY所在内核的内存页表的缓存属性设置为non-shareable)</p><h4 id="5-1-3-攻击过程"><a href="#5-1-3-攻击过程" class="headerlink" title="5.1.3 攻击过程"></a>5.1.3 攻击过程</h4><p>首先,为core1构造一个page table entry,将它的物理页表的缓存属性设置为shareable,并将它的物理地址指向core0的内存页。接下来,进入core1中对应的虚拟地址,core0中的L1数据缓存中的隐私数据就能够通过shareability属性被窃取或者修改。</p><p>(这边有个问题是:一个内存能否被两个内核共同访问吗?making its physical address point to a memory page of core_0)</p><p><img src="/2021/01/29/2021-01-29-CIMT/image-20200826093249629.png" alt="image-20200826093249629"></p><p>对IEE内存范围的获取是通过排除法,排除已知的不可信内存,而TEE内存是secure的,IEE内存是non-secure的,对TEE内存的访问会返回0或者触发中断,在读取IEE内存时当内存数据在cache中缓存时,是会得到真实的数据值的,虽然cache要比内存小很多,但经过多次探测,也能确定IEE内存的地址范围。</p><h3 id="5-2-Ginseng:映射到不安全的缓存"><a href="#5-2-Ginseng:映射到不安全的缓存" class="headerlink" title="5.2 Ginseng:映射到不安全的缓存"></a>5.2 Ginseng:映射到不安全的缓存</h3><p>Ginseng是在多核平台上保护选定功能的敏感数据的IEE系统。为了避免被同时运行的有害OS攻击,Ginseng选择将敏感数据仅存放在寄存器中,而不是core-isolated内存,所以这就对漏洞1免疫。另外,由于寄存器中的数据不经过缓存,所以也不受漏洞3的影响。</p><p>Ginseng在switch out过程中依赖于secure world中的TEE monitor来执行data cleaning操作。(因为IEE中的rich OS是不可信的,所以需要借助于TEE)通过试图访问TEE中的安全内存来触发一个安全中断,从而由IEE进入TEE。但是secure memory所映射的缓存是non-secure的,通过操纵该缓存,可以阻止从IEE到TEE monitor的控制流切换,从而绕过data cleaning操作。</p><h4 id="5-2-1-数据保护措施"><a href="#5-2-1-数据保护措施" class="headerlink" title="5.2.1 数据保护措施"></a>5.2.1 数据保护措施</h4><p>Ginseng提供了一个编译器,通过静态污点分析识别携带隐私数据的变量,并将它们保存在寄存器中。包含隐私数据的函数将会被判定为隐私函数,在退出前要进行代码完整性检查。</p><p>Ginseng引入了六种安全的API函数,用于将控制流从normal world中的用户空间直接转移到GService(Ginseng中的IEE monitor)</p><ul><li>ss_write(),ss_read():用于和GService安全通信</li><li>其余四种被编译器自动插入到程序中,</li><li>ss_saveCleanv():被插入在敏感函数中的每次函数调用之前,用于加密隐私数据,将加密后的数据保存在内存中,并清除相应的寄存器</li><li>ss_readV():被插入在敏感函数的每次函数调用之后,解密隐私数据,并恢复这些数据在寄存器中</li><li>ss_start():被插入在每个敏感函数的开始,用于进行诸如代码完整性检查等准备工具</li><li>ss_end():插入在敏感函数的末尾,清楚寄存器中的数据防止数据泄露。</li></ul><p><img src="/2021/01/29/2021-01-29-CIMT/image-20200826140050212.png" alt="image-20200826140050212"></p><p>当需要GService进行安全敏感操作时,需要从normal world中的用户空间上下文切换到运行在secure world中的GService。cross-world的上下文切换是通过SMC指令来完成的。通过配置TZASC,每个API函数被分配了一个独特的安全内存,调用一个安全的API函数将会触发一个security violation,因为它试图在普通世界读取安全内存。</p><p>在处理该violation时,处理器会在normal world中发出一个external abort(EA), GService在Secure Configuration Register中设置了external abort比特位,所以GService无需kernel的参与就可以直接处理EA和secure API发出的请求。</p><h4 id="5-2-2-Ginseng中的type-II-攻击"><a href="#5-2-2-Ginseng中的type-II-攻击" class="headerlink" title="5.2.2 Ginseng中的type II 攻击"></a>5.2.2 Ginseng中的type II 攻击</h4><p>ss_saveCleanv首先加载__channel_save_clean的地址(指向ss_saveCleanv函数的安全内存的虚拟地址)到寄存器4中,然后将该地址为x4的安全内存中的数据加载到寄存器x0中。</p><p><img src="/2021/01/29/2021-01-29-CIMT/image-20200826161849045.png" alt="image-20200826161849045"></p><p><img src="/2021/01/29/2021-01-29-CIMT/image-20200826160551699.png" alt="image-20200826160551699"></p><ol><li>当执行“安全内存加载”指令(第18行),处理器首先会尝试去缓存中加载数据;</li><li>由于缓存中不会存放敏感数据,所以应是cache miss;cache根据提供的地址去secure memory中获取数据;</li><li>由于是从normal world中尝试去访问secure world中的数据,所以会触发一个external abort,该EA被GService捕获;</li><li>GService根据该安全API的要求,加密敏感数据并清除相应的寄存器;</li><li>控制流回到secure API ss_saveCleanV();</li><li>调用非敏感函数insensitive_func()</li></ol><p>其中,1中的cache是non-secure cache,这儿可以被攻击者利用。</p><p>在HiKey620开发板(8-core ARM Cortex-A53 processor)上实现了原型系统</p><p>攻击过程就是红线描述的过程,最重要的是其中的步骤0</p><p>步骤0在缓存中填充了映射secure memory的缓存行,这样在步骤1的时候就会cache hit,那么步骤2-5都不会执行,就直接执行了步骤6中的insensitive function。而insensitive_func()可能会被攻击者利用读取未被清零的敏感寄存器,insensitive function和sensitive function都运行在相同的内核中,但缺少了安全检查。所以,我们可以通过修改libc.so库利用printf()函数来读取寄存器中的隐私数据,比如说keys。</p><p>接下来详细阐述cache0的攻击过程:writeSM是用于向cache中写入__channel_save_clean(ss_saveCleanV()的安全内存)的函数。</p><ol><li>__channel_save_clean的虚拟地址被加载到寄存器x4中;</li><li>由于__channel_save_clean的虚拟地址-物理地址映射被保存在rich OS内核中,攻击者可以很容易地获得它的物理地址;</li><li>将寄存器x0中的数据保存到地址值为x4的安全内存。该数据会被首先写入到安全内存__channel_save_clean的缓存中,因为Ginseng中的安全内存被设置为write-back和write-allocate,所以数据会被暂时存放在缓存中,直到缓存满了之后才被驱逐。</li><li>作者利用lockdown技术将映射到同一个page cache set的内存页表设置为outer non-cacheable(阻止缓存到l2缓存中)。</li></ol><p>通过在ss_saveCleanV()之前调用writeSM函数,本文作者成功实现了绕过GService中的安全保护。</p><p><img src="/2021/01/29/2021-01-29-CIMT/image-20200826170916865.png" alt="image-20200826170916865"></p><h3 id="5-3-TrustICE:不完整的cache-cleaning"><a href="#5-3-TrustICE:不完整的cache-cleaning" class="headerlink" title="5.3 TrustICE:不完整的cache cleaning"></a>5.3 TrustICE:不完整的cache cleaning</h3><p>TrustICE是在一个单核平台上实现的IEE系统,安全敏感app运行在包含一个用户程序和一个微内核的IEE中,该app不会受到rich OS的干扰。</p><h4 id="5-3-1-数据保护措施"><a href="#5-3-1-数据保护措施" class="headerlink" title="5.3.1 数据保护措施"></a>5.3.1 数据保护措施</h4><p>TrustICE静态地将物理内存分成了三个分离的区域给normal world中的rich OS,normal world中的IEE和secure world中的Trusted Domain Controller(TrustICE中的IEE monitor)。隐私数据保护是通过动态配置IEE内存的安全属性完成的。</p><ul><li><p>当系统boot up时,可信区域控制器设置IEE内存为安全的。</p></li><li><p>在新建一个IEE之前(switch in),可信区域控制器从IEE内存中给该IEE分配一块内存空间,并将其属性设置为non-secure。</p></li><li><p>当IEE结束运行时(switch out),IEE中的微内核通过调用SMC指令将当前控制流直接转移到secure world。在将控制权转交给rich OS之前,可信区域控制器将配置相应的IEE内存空间为secure。</p></li></ul><h4 id="5-3-2-TrustICE中的type-III-攻击"><a href="#5-3-2-TrustICE中的type-III-攻击" class="headerlink" title="5.3.2 TrustICE中的type III 攻击"></a>5.3.2 TrustICE中的type III 攻击</h4><p>TrustICE遵从了model2,所以对漏洞1来说是免疫的。</p><p>当malicious OS运行时,IEE内存被设置为secure;当security-sensitive app退出时IEE被设置为non-secure。在switch out过程中,通过动态配置IEE内存为secure,保证数据的安全性。该保护是无法绕过的,因为这是通过在secure world中调用SMC指令强制将控制流从IEE中的微内核转移到secure world。所以不受漏洞2的干扰。</p><p>虽然在上下文切换的过程中,内存得到了严格的保护,但相应的缓存还是non-secure的,并且没有被完全清理干净,因此,可以利用漏洞3。</p><h4 id="5-3-3-攻击过程"><a href="#5-3-3-攻击过程" class="headerlink" title="5.3.3 攻击过程"></a>5.3.3 攻击过程</h4><p>在normal world中构造一个页表条目,该页表条目的的物理地址指向IEE内存页,并且该内存页的cache属性被设置为write-back和write-allocate。</p><p>switch out:由于TrustICE并不会去清除缓存行中的数据,所以能够访问残留在对应缓存行的残留数据</p><p><img src="/2021/01/29/2021-01-29-CIMT/image-20200826210642807.png" alt="image-20200826210642807"></p><p>switch in:rich OS根据IEE内存页向缓存中写入malicious data,并使用lockdown技术锁住这些缓存行。当IEE执行时,它首先读到的是被污染后的缓存行,而不是IEE内存页中的合法数据。</p><p><img src="/2021/01/29/2021-01-29-CIMT/image-20200826210712579.png" alt="image-20200826210712579"></p><h2 id="6-应对措施"><a href="#6-应对措施" class="headerlink" title="6 应对措施"></a>6 应对措施</h2><p>本文作者通过配置IEE内存中的缓存属性以及在进行上下文切换时清除IEE内存来消除CITM漏洞。</p><h3 id="6-1-抵御措施"><a href="#6-1-抵御措施" class="headerlink" title="6.1 抵御措施"></a>6.1 抵御措施</h3><p>产生CITM漏洞的主要原因在于缓存和主存这两级内存架构之间的不连贯性。</p><p>漏洞1的原因:内存隔离并不能自动确保缓存隔离。比如在SANCTUARY中,当一个特定内核的内存区域通过TZC-400的identity-based filtering features获得了一块独立的内存区域,相应的L1缓存仍然能够在内核之间共享。</p><p>解决方法:将该core-isolated内存的cache属性设置为outer non-cacheable,non-shareable。</p><p>漏洞2产生的主要原因:内存和缓存中的读写操作是不同步的。Ginseng中的cross-world的切换就通过限制安全缓存中的读写绕过了。预加载并提前将恶意数据锁定在对应于secure memory的缓存中,IEE在读取安全内存时也会先hit预加载的恶意花奴才能。</p><p>解决方法:使内存和缓存中的读写同步。可以将IEE内存中cache属性设置为write-through,non-write-allocate.</p><p>漏洞3:缓存行是被自动设置的,当被一个运行在normal world中的内核访问时,该缓存行就被定义为non-secure;反之,如果被一个运行在secure world中的内核访问时,该缓存行就被定义为secure。所以,在TrustICE上完成的CITM攻击是通过在switch out之后读取IEE内存的non-secure缓存,并且在switch in之前在non-secure缓存中写入并锁定恶意数据。</p><p>解决办法:在switch in和switch out过程中清除缓存行,那么攻击者就不会读取缓存中残留的数据或者是在缓存中留下恶意数据。</p><p>综上所述,CITM漏洞可以通过以下几点来进行消除:</p><ul><li>配置IEE内存的页表属性为inner write-through non write-allocate,outer non-cacheable, non-shareable</li><li>在进行上下文切换时清除IEE内存对应的缓存。缓存清除可以通过调用IEE中的invalidation和cleaning指令来完成。在switch in中调用invalidation来保证cache的干净,在switch out中调用cleaning来同步cache和内存的数据,接着调用invalidation来清除cache数据。</li></ul><h3 id="6-2-defense-overhead"><a href="#6-2-defense-overhead" class="headerlink" title="6.2 defense overhead"></a>6.2 defense overhead</h3><p>设备:i.MX6Quad SABRE开发板(quad-core ARM Cortex-A9处理器 at 1.2GHz with 1GB DDR3 SDRAM)</p><p>强制缓存属性的情况,在一个IEE中运行一个AES加密app</p><p>第一列是大部分的IEE,第三列是本文中介绍的defense系统,由于关闭了L2,所以导致开销达到了90%,但是和同样关闭了L2的SANCTUARY相比,开销是忽略不计的。而第三列是开启了L2的defense系统,与第一列的开销类似。</p><p><img src="/2021/01/29/2021-01-29-CIMT/image-20200826225035890.png" alt="image-20200826225035890"></p><p>评估在rich OS中引入额外的cross-domain上下文切换,该上下文切换针对针对页表更新操作。相比较没有进行保护时,仅有2.65%的额外开销。</p><p>数据库的I/O操作需要17.74%是由于从硬盘到内存的数据拷贝需要修改一堆页表映射。</p><p><img src="/2021/01/29/2021-01-29-CIMT/image-20200826225341791.png" alt="image-20200826225341791"></p><p>在涉及到频繁的页表更新操作时的开销,所有的开销都小于10%</p><p><img src="/2021/01/29/2021-01-29-CIMT/image-20200826230608632.png" alt="image-20200826230608632"></p><h2 id="7-讨论"><a href="#7-讨论" class="headerlink" title="7 讨论"></a>7 讨论</h2><p>intel SGX:对于SGX来说,CITM攻击是无效的。因为SGX中的敏感信息都放在EPC(enclave page cache)上。EPC的页表和对应的缓存只能由运行着enclave的处理器来访问(避免恶意OS对缓存行的操纵)。并且,每个EPC页只会被分配给一个enclave。(避免另一个恶意enclave对EPC页缓存行的访问)–》该解决方法搭载在intel架构上,而移动设备往往是使用的arm处理器</p><p>基于虚拟化的解决办法:ARMv7中引入了hyp CPU模式,该模式运行在一个高特权的hypervisor中,一般认为该hypervisor是安全且可信的。在利用该hypervisor的解决办法中,IEE内存通过两个阶段的地址转换机制来进行管理。第一步,是由OS内核来完成的,将虚拟地址转化成一个intermediate physical address(过渡物理地址,IPA)。在第二阶段,由hypervisor将IPA进一步转化成一个真正的物理地址。为了抵御恶意OS,hypervisor通常会根据IPA到PA的映射来进一步确认分配给IEE和rich OS的隔离物理地址空间。–》依赖于一个可靠的hypervisor,本文的TrustZone-based IEE系统基于一个secure world中的small-sized的IEE来保护安全敏感程序免受normal world中的不可信os和hypervisor的攻击。</p>]]></content>
<summary type="html">
<p>Cache-in-the-Middle (CITM) Attacks : Manipulating Sensitive Data in Isolated Execution Environments的精读笔记~</p>
<p>这篇论文是我在组会讲解的第一篇论文,也很感激第一篇讲的是这篇,因为这篇完全就是中国人写的感觉,推荐阅读原文,非常好读懂</p>
</summary>
<category term="TrustZone" scheme="http://yoursite.com/categories/TrustZone/"/>
<category term="TrustZone" scheme="http://yoursite.com/tags/TrustZone/"/>
<category term="paper" scheme="http://yoursite.com/tags/paper/"/>
<category term="System Security" scheme="http://yoursite.com/tags/System-Security/"/>
</entry>
</feed>