问题描述
我正在开发内核实时补丁,实时补丁模块中的一些代码如下所示:
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.
}
}
此代码获取内核符号(类型为 struct list_head
)的地址并尝试迭代列表。但由于某些原因,链表中的某些节点可能会被破坏,导致某些节点的next
指针无效(如NULL、0xABABABAB或其他随机数),而取消引用next
指针可能导致内核崩溃。
那么有没有办法检查一个指针是否可以安全访问?
我已经检查了两个以前的答案:
How to check a memory address is valid inside Linux kernel?
How can I tell if a virtual address has valid mapping in ARM linux kernel?
他们告诉我使用 virt_addr_valid
。我有一些肯定可以访问的地址,例如 0xFFFFFFFFA032C040,但是 virt_addr_valid
总是返回 false,这使我无法在实时补丁模块中区分“可访问”和“不可访问”地址。
解决方法
如 here 所述,kallsyms_lookup_name()
引起了一些许可问题,并且在当前内核中未导出。
您可能需要检查 livepatch。
,在我的情况下,我想检查的内存地址应该使用 kmalloc()
分配,但可能由于某些错误而被污染(即随机值)。
virt_addr_valid()
检查地址是否驻留在内核中的“内核代码区”或“直接映射区”(检查此 link 以获取 x86_64 内存布局)。并且由 kmalloc()
分配的内存驻留在“直接映射区域”中,因此在 kmalloc 的内存上使用 virt_addr_valid()
总是正确的。但另一方面,由于我的实验,某些地址可能会得到 virt_addr_valid=true ,但无法访问,并且取消引用该地址可能会导致机器崩溃。所以我还需要确保地址在页表中正确映射,以免机器崩溃。
所以解决方案包含两个步骤:
-
virt_addr_valid()
是否在地址上返回 true - 如果为 true,则执行 page-table-walk 以检查地址是否正确映射
由于“virt_addr_valid-is-true”区域的内存映射没有变化,所以不需要持有锁。
以下是带有 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;
}