[英]How to check an address is accessible in the kernel space
我正在研究 kernel 實時補丁,實時補丁模塊中的一些代碼如下所示:
void list_checker() {
struct list_head *head, *iter;
head = (struct list_head *)kallsyms_lookup_name("module_name:symbol_name");
for (iter = head->next; iter != head; iter = iter->next) {
// do something.
}
}
此代碼獲取 kernel 符號的地址(類型為struct list_head
)並嘗試迭代列表。 但由於某些原因,鏈表中的某些節點可能會被破壞,導致某些節點的next
指針無效(如 NULL、0xABABABAB 或其他隨機數),並且取消引用next
指針可能會導致 kernel 崩潰。
那么有沒有辦法檢查一個指針是否可以安全訪問呢?
我檢查了兩個以前的答案:
如何檢查 memory 地址在 Linux kernel 中是否有效?
如何判斷虛擬地址是否在 ARM linux kernel 中具有有效映射?
他們告訴我使用virt_addr_valid
。 我有一些肯定可以訪問的地址,比如 0xFFFFFFFFA032C040,但是virt_addr_valid
總是返回 false,這讓我無法區分實時補丁模塊中的“可訪問”和“不可訪問”地址。
在我的例子中,我想檢查的 memory 地址應該使用kmalloc()
分配,但由於一些錯誤可能會被污染(即隨機值)。
virt_addr_valid()
檢查地址是否位於 kernel 中的“內核代碼區域”或“直接映射區域”中(查看此鏈接以了解 x86_64 memory 布局)。 並且由kmalloc()
分配的 memory 位於“直接映射區域”中,因此在 kmalloc 的內存上使用virt_addr_valid()
總是正確的。 但是另一方面,由於我的實驗,一些地址可能會得到virt_addr_valid=true,但是無法訪問,取消引用地址可能會導致機器崩潰。 所以我還需要確保地址在頁表中正確映射,以免機器崩潰。
所以解決方案包含兩個步驟:
virt_addr_valid()
是否在地址上返回 true由於“virt_addr_valid-is-true”區域中的memory映射沒有改變,所以不需要持有鎖。
下面是帶有 4 級頁表的 x86_64 的代碼。
static bool page_mapping_exist(unsigned long addr, size_t size) {
pgd_t *pgd;
pmd_t *pmd;
pud_t *pud;
pte_t *pte;
struct mm_struct *mm = current->mm;
unsigned long end_addr;
pgd = pgd_offset(mm, addr);
if (unlikely(!pgd) || unlikely(pgd_none(*pgd)) || unlikely(!pgd_present(*pgd)) )
return false;
pud = pud_offset(pgd, addr);
if (unlikely(!pud) || unlikely(pud_none(*pud)) || unlikely(!pud_present(*pud)))
return false;
pmd = pmd_offset(pud, addr);
if (unlikely(!pmd) || unlikely(pmd_none(*pmd)) || unlikely(!pmd_present(*pmd)))
return false;
if (pmd_trans_huge(*pmd)) {
end_addr = (((addr >> PMD_SHIFT) + 1) << PMD_SHIFT) - 1;
goto end;
}
pte = pte_offset_map(pmd, addr);
if (unlikely(!pte) || unlikely(!pte_present(*pte)))
return false;
end_addr = (((addr >> PAGE_SHIFT) + 1) << PAGE_SHIFT) - 1;
end:
if (end_addr >= addr + size - 1)
return true;
return page_mapping_exist(end_addr + 1, size - (end_addr - addr + 1));
}
static bool addr_valid(unsigned long addr, size_t size) {
int i;
for (i = 0; i < size; i++) {
if (!virt_addr_valid(addr + i))
return false;
}
if (!page_mapping_exist(addr, size))
return false;
return true;
}
我花了一段時間,但我發現 source/arch/x86/mm/init_64.c 中的 kern_addr_valid(addr) 可以解決問題。 它遍歷頁表,計算大頁以確保地址有效。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.