<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 구조체를 사용하다 부트업에서 처리하는 것으로 바뀌었다.
- 더 이상 CONFIG_PAGE_EXTENSION 을 사용하지 않는다.
- 참고: mm: enable page poisoning early at boot
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을 사용할 수도 있다.