디버그 메모리 -2- (Page Poisoning)

<kernel v5.0>

디버그 메모리 -2- (Page Poisoning)

free 페이지에 poison 표식을 한 후 페이지 할당 시마다 poison 표식을 확인하여 다음과 같이 오염된 경우 이에 대한 메시지 출력과 메모리 덤프 및 스택 트레이싱을 출력한다. 출력 내용은 rate limit이 걸려있으므로 5초 간격 이내에 10회까지로 출력을 제한한다.

  • 비트 하나만 바뀐 경우
    • “pagealloc: single bit error”
  • 그 외
    • “pagealloc: memory corruption”

 

 

사용 조건

  • CONFIG_PAGE_POISONING 커널 옵션
  • CONFIG_PAGE_POISONING_NO_SANITY 커널 옵션을 사용하는 경우 poison 체크는 skip 한다.
    • CONFIG_HIBERNATION을 사용하면 항상 CONFIG_PAGE_POISONING_NO_SANITY 가 enable 된다. 따라서 poison check를 수행하려면 하이버네이션 기능을 꺼야한다.
  • “page_poison” 커널 파라미터

 

  • 이 기능은 커널 v4.12-rc1에서 page_ext 구조체를 사용하다 부트업에서 처리하는 것으로 바뀌었다.

 

Early 커널 파라메터로 설정

early_page_poison_param()

mm/page_poison.c

static int __init early_page_poison_param(char *buf)
{
        if (!buf)
                return -EINVAL;
        return strtobool(buf, &want_page_poisoning);
}
early_param("page_poison", early_page_poison_param);

 

mm/page_poison.c

static bool want_page_poisoning __read_mostly;

 

kernel_poison_pages()

이 함수가 호출되는 곳은 다음과 같다.

  • 페이지 할당 과정
    • post_alloc_hook() 함수에서 kernel_poison_pages(page, 1 << order, 1);와 같이 호출된다.
  • 페이지 회수 과정
    • free_pages_prepare() 함수에서 kernel_poison_pages(page, 1 << order, 0);와 같이 호출된다.

mm/page_poison.c

void kernel_poison_pages(struct page *page, int numpages, int enable)
{
        if (!page_poisoning_enabled())
                return;

        if (enable)
                unpoison_pages(page, numpages);
        else
                poison_pages(page, numpages);
}

@page 부터 @numpages 수 만큼의 페이지 프레임을 대상으로 @enable 여부에 따라 poison 표식을 하거나 제거한다. 제거할 때에는 poison 체크를 수행하여 해당 페이지의 침범 등의 이상 여부를 알아낸다.

  • 코드 라인 3~4에서 “page_poison” 커널 파라미터가 지정되지 않은 경우 함수를 빠져나간다.
  • 코드 라인 6~7에서 페이지 할당 과정에서 @enable이 1로 요청되는데, 이 때 요청한 페이지 프레임 내용을 모두 poison 값으로 채운다.
  • 코드 라인 8~9에서 페이지 회수 과정에서 @enable이 0으로 요청되는데, 이 때 요청한 페이지 프레임 내용을 조사하여 poison 값이 아닌 경우 해당 메모리 덤프 및 stack 트레이싱 출력을 수행한다.

 


메모리 할당 시 unpoison 과정에서 poison 표식 훼손 체크

unpoison_pages()

mm/page_poison.c

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

        for (i = 0; i < n; i++)
                unpoison_page(page + i);
}

@page 페이지 프레임부터 @n 개 페이지 프레임 내용을 조사하여 poison 표식이 훼손된 경우 해당 메모리 덤프 및 stack 트레이싱 출력을 수행한다.

 

unpoison_page()

mm/page_poison.c

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

        addr = kmap_atomic(page);
        /*
         * Page poisoning when enabled poisons each and every page
         * that is freed to buddy. Thus no extra check is done to
         * see if a page was posioned.
         */
        check_poison_mem(addr, PAGE_SIZE);
        kunmap_atomic(addr);
}

@page 페이지 프레임 내용을 조사하여 poison 표식이 훼손된 경우 해당 메모리 덤프 및 stack 트레이싱 출력을 수행한다.

 

check_poison_mem()

mm/page_poison.c

static void check_poison_mem(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;

        start = memchr_inv(mem, PAGE_POISON, bytes);
        if (!start)
                return;

        for (end = mem + bytes - 1; end > start; end--) {
                if (*end != PAGE_POISON)
                        break;
        }

        if (!__ratelimit(&ratelimit))
                return;
        else if (start == end && single_bit_flip(*start, PAGE_POISON))
                pr_err("pagealloc: single bit error\n");
        else
                pr_err("pagealloc: memory corruption\n");

        print_hex_dump(KERN_ERR, "", DUMP_PREFIX_ADDRESS, 16, 1, start,
                        end - start + 1, 1);
        dump_stack();
}

@mem 주소부터 @bytes 만큼 메모리 내용을 조사하여 poison 표식이 훼손된 경우 해당 메모리 덤프 및 stack 트레이싱 출력을 수행한다.

  • 력 형태는 두 가지로 출력한다.
    • 1 비트만 훼손된 경우 “pagealloc: single bit error” 메시지 출력
    • 그 외의 경우 “pagealloc: memory corruption” 메시지 출력

 

single_bit_flip()

mm/page_poison.c

static bool single_bit_flip(unsigned char a, unsigned char b)
{
        unsigned char error = a ^ b;

        return error && !(error & (error - 1));
}

주어진 @a 와 @b 바이트에서 1개 비트만 다른 경우 1을 반환한다.

 


메모리 회수 시 poison  표식

poison_pages()

mm/page_poison.c

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

        for (i = 0; i < n; i++)
                poison_page(page + i);
}

@page에 해당하는 페이지 프레임부터 n개 페이지 프레임의 내용을 PAGE_POISON(0xaa) 값으로 표식한다.

 

poison_page()

mm/page_poison.c

static void poison_page(struct page *page)
{
        void *addr = kmap_atomic(page);

        memset(addr, PAGE_POISON, PAGE_SIZE);
        kunmap_atomic(addr);
}

@page에 해당하는 한 페이지 프레임의 내용을 PAGE_POISON(0xaa) 값으로 표식한다.

  • CONFIG_PAGE_POISONING_ZERO 커널 옵션을 사용하는 경우 PAGE_POISON 값으로 0x00을 사용할 수도 있다.

 

참고

댓글 남기기