亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何測試Linux內核入口代碼

發布時間:2021-10-26 15:28:50 來源:億速云 閱讀:146 作者:iii 欄目:web開發

本篇內容介紹了“如何測試Linux內核入口代碼”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

通用寄存器+delta狀態

到目前為止,我們尚未涉及的一件事是將其他通用寄存器也設置為隨機值。入口代碼在工作過程中確實會用到某些通用寄存器,如果我們真的在某個地方遇到了問題,那么它很可能因隨機值而崩潰。

我們可能還想找出更細微的漏洞——雖然這些漏洞不會使得內核徹底崩潰,但可能會將內核地址泄漏到用戶空間從未見過的某個寄存器中。一種檢查內核是否正確,是否保存了我們的寄存器/標志等的方法是在從內核模式返回后寫出寄存器的狀態。這并不難實現,因為我們可以將所有(或者至少是大部分)寄存器的值存放到固定的地址中(例如,在我們已經用于其他用途的數據頁中)。這里的難點在于如何將其與在一個子進程中運行多個進入嘗試(entry attempts)/系統調用結合起來,因為需要將健全性檢查與進入嘗試交織在一起,這可能會非常麻煩。

最大限度地降低崩潰的概率

我們在第二篇文章中已經提到,令子進程崩潰的代價相當高,因為這意味著要啟動一個全新的子進程。因此,盡可能避免崩潰(并在同一個子進程中運行盡可能多的進入嘗試)可能是提高fuzzer性能的可行策略。這包括兩個主要部分:

· 保存/恢復行內所需的狀態,例如,你要保存和恢復%rsp,以便后續的pushf/popf指令能夠繼續工作。

· 從信號處理程序中恢復,例如通過安裝處理程序,可以將進程恢復到已知的良好狀態。

檢查生成的匯編代碼

雖然代碼很容易在生成匯編代碼的時候出錯,但人們卻很難注意到,因為程序都崩潰了,你也看不出你得到的是一個意外的結果。我曾經遇到過類似的問題,但是在2年的時間里一直沒有覺察到:我在編碼ljmp操作數的地址時,不小心用錯了字節順序,所以在32位兼容模式下,它實際上從來沒有運行過任何東西!

一種檢查匯編代碼的簡便方法是使用像udis86這樣的反匯編庫,然后通過手動方式驗證生成的代碼。

#include   ...   ud_t u; ud_init(&u);   ud_set_vendor(&u, UD_VENDOR_INTEL); ud_set_mode(&u, 64); ud_set_pc(&u, (uint64_t) mem); ud_set_input_buffer(&u, (unsigned char *) mem, (char *) out - (char *) mem);   ud_set_syntax(&u, UD_SYN_ATT);   while (ud_disassemble(&u))     fprintf(stderr, "  %08lx %s\n", ud_insn_off(&u), ud_insn_asm(&u));   fprintf(stderr, "\n");

KVM/Xen/Intel/AMD的交互

在一個案例中,我們看到了與KVM的交互,其中啟動任何KVM實例都會破壞GDTR(GDT寄存器)的大小,并允許fuzzer通過使用超出GDT預期大小的段而導致崩潰。事實證明,這個漏洞是可利用的,并能獲得ring 0的執行權限。在另一個案例中,我們看到了在硬件加速的嵌套式客戶機(客戶機中的客戶機)中運行時的交互。

通常,KVM需要模擬底層硬件的某些特性,這增加了相當多的復雜性。fuzzer很有可能在KVM或Xen等管理程序中發現漏洞,因此在不同的裸機CPU和多種管理程序下運行fuzzer是很有價值的。

要想以編程方式創建KVM實例,請參閱Serge Zaitsev撰寫的KVM host in a few lines of code一文。

一個相關的有趣實驗可能是為運行在x86上的Windows或其他操作系統編譯fuzzer,看看它們的效果如何。我在WSL(Windows Subsystem for Linux)上簡單地測試了Linux二進制文件,沒有發生什么不良情況。

配置/啟動選項

配置/啟動選項會影響入口代碼的具體操作。下面是我在最新的內核中發現的相關選項:

$ grep -o 'CONFIG_[A-Z0-9_]*' arch/x86/entry/entry_64*.S | sort | uniq CONFIG_DEBUG_ENTRY CONFIG_IA32_EMULATION CONFIG_PARAVIRT CONFIG_RETPOLINE CONFIG_STACKPROTECTOR CONFIG_X86_5LEVEL CONFIG_X86_ESPFIX64 CONFIG_X86_L1_CACHE_SHIFT CONFIG_XEN_PV

其實,還有更多的選項,它們都隱藏在頭文件中。通過這些選項的不同組合來構建多個內核,可以幫助揭示那些被破壞的組合,也許只有在由fuzzer觸發的邊緣情況下才會出現。

通過查看Documentation/admin-guide/kernel-parameters.txt,你還可以找到一些可能影響入口代碼的選項。這里有一個Python腳本,它可以生成隨機的配置選項組合,這對于用KVM傳遞內核命令行非常有用:

import random   flags = """nopti nospectre_v1 nospectre_v2 spectre_v2_user=off spec_store_bypass_disable=off l1tf=off mds=off tsx_async_abort=off kvm.nx_huge_pages=off noapic noclflush nosmap nosmep noexec32 nofxsr nohugeiomap nosmt nosmt noxsave noxsaveopt noxsaves intremap=off nolapic nomce nopat nopcid norandmaps noreplace-smp nordrand nosep nosmp nox2apic""".split()   print(' '.join(random.sample(flags, 5)), "nmi_watchdog=%u" % (random.randrange(2), ))

ftrace

Ftrace啟用時,會在入口代碼中插入一些代碼,例如用于系統調用和irqflags跟蹤。這可能也非常值得進行測試,所以我建議在運行fuzzer之前,不妨調整一下這些文件(位于/sys/kernel/tracing路徑下):

如何測試Linux內核入口代碼

PTRACE_SYSCALL

我們已經看到,ptrace改變了處理系統調用進入/退出的方式(因為需要停止進程并通知跟蹤器),所以最好在ptrace()下使用ptrace_syscall運行一部分進入嘗試。當被ptrace停止時,嘗試調整被跟蹤的進程的一些/所有寄存器也可能很有趣。要完全正確地完成這個任務是非常困難的,所以這里就不多介紹了。

mkinitrd.sh

當我在VM中進行測試時,我更喜歡將程序綁定在initrd中,并以init(pid1)的形式運行,這樣就不需要將其復制到文件系統映像上。您可以使用如下所示的腳本:

#! /bin/bash   set -e set -x   rm -rf initrd/ mkdir initrd/ g++ -static -Wall -std=c++14 -O2 -g -o initrd/init main.cc -lm   (cd initrd/ && (find | cpio -o -H newc)) \     | gzip -c \     > initrd.entry-fuzz.gz

如果你使用的是Qemu/KVM,只要傳入-initrd initrd.entry-fuzz.gz,它就會在開機后立即運行fuzzer。

污點檢查

如果fuzzer真的遇到了某種內核崩潰或漏洞,那么確保我們不會遺漏它們是很有用的。我個人喜歡在內核命令行中使用參數ops=panic panic_on_warn panic=-1,并將-no-reboot傳遞給Qemu/KVM;這將確保任何警告都會立即導致Qemu退出(將任何診斷程序留在終端上)。如果你正在使用專門的裸機運行fuzzer(例如,使用上面的initrd方法),可以令panic=0,這樣只會掛起機器。

如果你在普通的工作站上進行測試,并且不想讓整臺機器掛掉,則可以檢查內核是否被污染(每當出現警告或漏洞時都會被污染),然后直接地退出:

int tainted_fd = open("/proc/sys/kernel/tainted", O_RDONLY); if (tainted_fd == -1)     error(EXIT_FAILURE, errno, "open()");   char tainted_orig_buf[16]; ssize_t tainted_orig_len = pread(tainted_fd, tainted_orig_buf, sizeof(tainted_orig_buf), 0); if (tainted_orig_len == -1)     error(EXIT_FAILURE, errno, "pread()");   while (1) {     // generate + run test case       ...       char tainted_buf[16];     ssize_t tainted_len = pread(tainted_fd, tainted_buf, sizeof(tainted_buf), 0);     if (tainted_len == -1)         error(EXIT_FAILURE, errno, "pread()");       if (tainted_len != tainted_orig_len || memcmp(tainted_buf, tainted_orig_buf, tainted_len)) {         fprintf(stderr, "Kernel became tainted, stopping.\n");         // TODO: dump hex bytes or disassembly         exit(EXIT_FAILURE);     } }

網絡日志

如果內核崩潰了,并且不清楚問題出在哪里,那么將所有正在嘗試的內容記錄到網絡中是非常有用的。我將給出一個UDP日志的簡單框架:

int main(...) {     int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);     if (udp_socket == -1)         error(EXIT_FAILURE, errno, "socket(AF_INET, SOCK_DGRAM, 0)");       struct sockaddr_in remote_addr = {};     remote_addr.sin_family = AF_INET;     remote_addr.sin_port = htons(21000);     inet_pton(AF_INET, "10.5.0.1", &remote_addr.sin_addr.s_addr);       if (connect(udp_socket, (const struct sockaddr *) &remote_addr, sizeof(remote_addr)) == -1)         error(EXIT_FAILURE, errno, "connect()");       ... }

然后,在生成了每個入口/出口的代碼之后,您可以簡單地將其轉儲到這個套接字上:

write(udp_socket, (char *) mem, out - (uint8_t *) mem);

我們希望日志服務器最后接收到的數據(這里是10.5.0.1:21000)會包含導致崩潰的匯編代碼。根據具體的用例,有時需要添加某種框架,以便可以輕松地判斷出測試用例的具體開始和結束位置。

檢查fuzzer是否能捕捉到已知的漏洞

多年來,人們已經在入口代碼中找到了許多漏洞。因此,我們可以構建一些舊的、有漏洞的內核,并在它們上面運行fuzzer,以確保它確實能捕捉到這些已知的漏洞。我們也可以用尋找漏洞所花費的時間來衡量fuzzer的效率,但是,我們必須小心,不要過度優化,以防止它們只找到這些漏洞。

代碼覆蓋率/插樁技術反饋

插樁技術

AFL和syzkaller這樣的fuzzer如此有效的原因之一是,它們使用代碼復蓋率來非常精確地衡量調整測試用例的各個二進制位的效果。這通常是通過使用一個特殊的編譯器標志編譯C代碼來實現的,該標志發出額外的代碼來收集覆蓋率數據。對于匯編代碼,尤其是入口代碼,這是一個非常棘手的問題,因為如果不手動檢查代碼的每個指令,我們就無法知道CPU到底處于什么狀態(以及我們可以破壞哪些寄存器/狀態)。

但是,如果我們真的想要提高代碼覆蓋率,有一種方法可以做到:x86指令集包含一條指令,該指令同時接受一個立即數和一個立即數地址,并且不影響任何其他狀態(例如標志):movb$value,(addr)。我們唯一需要注意的是:確保addr是一個編譯時常量地址,它總是映射到某個物理內存,并在頁表中標記為present,這樣我們在訪問它時就不會出現頁面錯誤。幸運的是,Linux已經提供了一種機制:fixmaps,也就是“編譯時虛擬內存分配”。這樣,我們就可以靜態地分配一個編譯時常量虛擬地址,該地址指向所有任務和上下文的相同底層物理頁面。由于它是在任務之間共享的,因此當在進程之間切換時,我們必須清除或以其他方式保存/恢復這些值。

通過組合使用C宏和匯編器宏,我們可以得到一個侵入性非常低的覆蓋原語,你可以在入口代碼中的任何地方加入這個原語,來記錄所采用的代碼路徑。我已經編寫了一個補丁,但還有一些邊緣情況需要解決(例如,當SMAP被啟用時,它并不完全有效)。此外,我懷疑x86的維護者是否會喜歡在入口代碼中摻雜這些覆蓋率注釋。

在fuzzer方面,有一件事讓插樁技術反饋變得更加復雜,那就是你需要一個完整的系統來跟蹤測試用例、結果以及(可能的)你對每個測試用例應用了哪些突變。正因為如此,我選擇暫時忽略代碼覆蓋率;無論如何,這都是一個寬泛的fuzzing話題,與x86或特別是入口代碼沒有太大關系。

性能計數器/硬件反饋

收集代碼覆蓋率的一種完全不同的方法是使用性能計數器。我知道最近有兩個項目就是這樣做的:

· Resmack Fuzz Test

· kAFL

這里最大的好處顯然是不需要進行檢測(修改內核)。最大的缺點在于性能計數器不是完全確定的(可能是由于硬件中斷等外部因素所致)。也許它對入口代碼也不起作用,因為在匯編代碼上只花費了很短的時間。

“如何測試Linux內核入口代碼”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

柞水县| 义马市| 广灵县| 徐闻县| 南川市| 宣城市| 房产| 汝州市| 贵定县| 建昌县| 资阳市| 鄂伦春自治旗| 泸西县| 汝州市| 黑水县| 故城县| 金湖县| 内乡县| 滨州市| 屯昌县| 东方市| 宣汉县| 永善县| 会理县| 巴中市| 濮阳县| 临清市| 南通市| 论坛| 浦北县| 深圳市| 蒙山县| 淅川县| 高碑店市| 义乌市| 镇宁| 平远县| 北票市| 宜黄县| 同德县| 广饶县|