内存管理-47-Poison荼毒

基于msm-5.4


一、简介

主要是利用头文件定义一些宏,若是向使用物理页的Poison需要使能开关 CONFIG_PAGE_POISONING, 它实现比较简单,只有一个较短的C文件。相关文件:

include/linux/poison.h
mm/page_poison.c。

C文件中对外只有一个接口, 就是 kernel_poison_pages(),它只在物理页分配释放中使用。

poison.h 头文件中还定义了一些其它荼毒值,

//链表list
#define LIST_POISON1  ((void *) 0x100 + POISON_POINTER_DELTA) //0xdead000000000100UL
#define LIST_POISON2  ((void *) 0x122 + POISON_POINTER_DELTA) //0xdead000000000122UL
//定时器timer
#define TIMER_ENTRY_STATIC    ((void *) 0x300 + POISON_POINTER_DELTA) //0xdead000000000300UL
//page_poison.c
#define PAGE_POISON 0xaa
//page_alloc.c
#define TAIL_MAPPING    ((void *) 0x400 + POISON_POINTER_DELTA) //0xdead000000000400UL
//slab.c
#define    RED_INACTIVE    0x09F911029D74E35BULL    /* when obj is inactive */
#define    RED_ACTIVE        0xD84156C5635688C0ULL    /* when obj is active */
#define SLUB_RED_INACTIVE    0xbb
#define SLUB_RED_ACTIVE        0xcc
#define    POISON_INUSE    0x5a    /* for use-uninitialised poisoning */ //对应ASCII就是'Z'
#define POISON_FREE        0x6b    /* for use-after-free poisoning */    //对应ASCII就是'k'
#define    POISON_END        0xa5    /* end-byte of poisoning */           //超出ASCII码范围
//init.c
#define POISON_FREE_INITMEM    0xcc
//fs/journal.c
#define JBD_POISON_FREE        0x5b
#define JBD2_POISON_FREE    0x5c
//dmapool.c
#define    POOL_POISON_FREED    0xa7    /* !inuse */
#define    POOL_POISON_ALLOCATED    0xa9    /* !initted */
//mutexes
#define MUTEX_DEBUG_INIT    0x11
#define MUTEX_DEBUG_FREE    0x22
#define MUTEX_POISON_WW_CTX    ((void *) 0x500 + POISON_POINTER_DELTA) //0xdead000000000500UL
//security
#define KEY_DESTROY        0xbd


二、物理页分配释放中的Poison

调用路径:

prep_new_page //page_alloc.c
split_map_pages //compaction.c
unset_migratetype_isolate //page_isolation.c
    post_alloc_hook //page_alloc.c 分配物理页,传参 (page, 1 << order, 1) 检查page flip
__free_pages_ok //page_alloc.c
free_pcp_prepare //page_alloc.c
    free_pages_prepare //page_alloc.c 释放物理页,传参 (page, 1 << order, 0) 进行荼毒
        kernel_poison_pages //page_poison.c

分配释放内存时被荼毒成什么值由 PAGE_POISON 宏指定,默认是 0xaa.


1. kernel_poison_pages()

void kernel_poison_pages(struct page *page, int numpages, int enable)
{
    /* debug版本默认是true,不退出 */
    if (!page_poisoning_enabled())
        return;

    /* 分配出去时是unpoison,释放后进行poison */
    if (enable)
        unpoison_pages(page, numpages); //这里面若是有pageflip的话会进行打印的
    else
        poison_pages(page, numpages); /* 将整个page清成0xaa */
}


1.1 unpoison_pages()

static void unpoison_pages(struct page *page, int n)
{
    int i;

    /* 对对所有分配出来的物理页都执行unpoison */
    for (i = 0; i < n; i++)
        unpoison_page(page + i);
}

static void unpoison_page(struct page *page)
{
    void *addr;

    /* 关抢占关page_fault并获取此page对应物理内存的虚拟地址 */
    addr = kmap_atomic(page);
    /*
     * 翻译: 启用页面投毒功能后,所有释放给buddy的页面都会被投毒。因此,页面是否被投毒没有额外的检查。
     *
     * 这里会遍历每一个字节检查是否有page flip, 性能有害 ####
     */
    check_poison_mem(page, addr, PAGE_SIZE);
    /* 只是开抢占开page_fault */
    kunmap_atomic(addr);
}


1.1.1 check_poison_mem()

/* unpoison_page: (page, addr, PAGE_SIZE) 参数page和page对应的虚拟地址 */
static void check_poison_mem(struct page *page, unsigned char *mem, size_t bytes)
{
    static DEFINE_RATELIMIT_STATE(ratelimit, 5 * HZ, 10);
    unsigned char *start;
    unsigned char *end;

    /* 默认不使能,不退出 */
    if (IS_ENABLED(CONFIG_PAGE_POISONING_NO_SANITY))
        return;

    /*
     * 定义在string.c中,返回除 0xaa 以外的第一个字符的地址,如果整个缓冲区仅包含 0xaa,
     * 则返回 NULL。若成功检查通过,这里就返回了。
     */
    start = memchr_inv(mem, PAGE_POISON, bytes);
    if (!start)
        return;

    /* 再从后往前找第一个不是0xaa的位置,保存在end指针中 */
    for (end = mem + bytes - 1; end > start; end--) {
        if (*end != PAGE_POISON)
            break;
    }

    /* 默认5秒内最多打印10条, 返回0表示速率限制了,不应该做打印,直接返回,否则进行报错 */
    if (!__ratelimit(&ratelimit))
        return;
    /* 判断是否只是一个bit flip了。#### 这里只打印了页的物理地址,还可以将页内偏移给打印出来 */
    else if (start == end && single_bit_flip(*start, PAGE_POISON))
        pr_err("pagealloc: single bit error on page with phys start 0x%lx\n",
            (unsigned long)page_to_phys(page));
    else
        /* 多个字节或一个字节的多个bit flip了,报这个错 */
        pr_err("pagealloc: memory corruption on page with phys start 0x%lx\n",
            (unsigned long)page_to_phys(page));

    /* dump此page内存在flip的范围的物理内存的值 */
    print_hex_dump(KERN_ERR, "", DUMP_PREFIX_ADDRESS, 16, 1, start, end - start + 1, 1);
    /* 默认不使能 CONFIG_PANIC_ON_DATA_CORRUPTION, 这里并不会死机 */
    BUG_ON(PANIC_CORRUPTION);
    /* 只是打印一个栈回溯 */
    dump_stack();
}


1.2 poison_pages()

static void poison_pages(struct page *page, int n)
{
    int i;

    /* 遍历所有物理页,对每个物理页都进行荼毒 */
    for (i = 0; i < n; i++)
        poison_page(page + i);
}

/* 将整个page清成0xaa */
static void poison_page(struct page *page)
{
    void *addr = kmap_atomic(page); //返回page对应的pfn的虚拟地址

    /* KASAN still think the page is in-use, so skip it. */
    kasan_disable_current(); //current->kasan_depth--;
    memset(addr, PAGE_POISON, PAGE_SIZE); //0xaa 整个page memset为0xaa
    kasan_enable_current(); //current->kasan_depth++;
    kunmap_atomic(addr);
}


三、总结

1. 对物理页内存进行荼毒,释放内存页时将整个内存空间设置为0xaa,分配物理页时检测pageflip,但是负载较重,影响性能,默认只有debug版本才会使能 CONFIG_PAGE_POISONING。

 

posted on 2025-06-10 16:21  Hello-World3  阅读(81)  评论(0)    收藏  举报

导航