디버그 메모리 -3- (Page Owner 추적)

<kernel v5.0>

디버그 메모리 -3- (Page Owner 추적)

사용 조건

  • CONFIG_PAGE_EXTENSION  커널 옵션
  • CONFIG_PAGE_OWNER 커널 옵션
  • “page_owner=on” 커널 파라미터

 

  • “/sys/kernel/debug/page_owner” 파일을 출력하여 모든 페이지들의 onwer 추적을 수행할 수 있다.
  • 커널 v3.19-rc1에서 추가되었다.

 

page_ext_init_flatmem-2

early_page_owner_param()

mm/page_owner.c

static int early_page_owner_param(char *buf)
{
        if (!buf)
                return -EINVAL;

        if (strcmp(buf, "on") == 0)
                page_owner_disabled = false;

        return 0;
}               
early_param("page_owner", early_page_owner_param);

“page_owner=on” early 커널 파라메터를 사용하는 경우 페이지 owner 추적 기능을 사용한다.

 

need_page_owner()

mm/page_owner.c

static bool need_page_owner(void)
{
        if (page_owner_disabled)
                return false;

        return true;
}

page owner 추적 기능이 필요한지 여부를 반환한다.

 

mm/page_owner.c

static bool page_owner_disabled = true;

 

init_page_owner()

mm/page_owner.c

static void init_page_owner(void)
{
        if (page_owner_disabled)
                return;

        page_owner_inited = true;
        init_early_allocated_pages();
}

page owner 추적 기능이 사용되는 경우 할당 페이지를 초기화한다.

 

mm/page_owner.c

bool page_owner_inited __read_mostly;

 

init_early_allocated_pages()

mm/page_owner.c

static void init_early_allocated_pages(void)
{
        pg_data_t *pgdat;

        drain_all_pages(NULL);
        for_each_online_pgdat(pgdat)
                init_zones_in_node(pgdat);
}

Per-CPU Page Frame Cache를 버디 할당자로 다시 돌려보내고 노드안에 있는 zone을 초기화한다.

 

init_zones_in_node()

mm/page_owner.c

static void init_zones_in_node(pg_data_t *pgdat)
{
        struct zone *zone;
        struct zone *node_zones = pgdat->node_zones;
        unsigned long flags;
        
        for (zone = node_zones; zone - node_zones < MAX_NR_ZONES; ++zone) {
                if (!populated_zone(zone))
                        continue;

                spin_lock_irqsave(&zone->lock, flags);
                init_pages_in_zone(pgdat, zone);
                spin_unlock_irqrestore(&zone->lock, flags);
        }       
}

해당 노드에 대해 populate zone에 속한 페이지를 초기화한다.

 

init_pages_in_zone()

mm/page_owner.c

static void init_pages_in_zone(pg_data_t *pgdat, struct zone *zone)
{
        struct page *page;
        struct page_ext *page_ext;
        unsigned long pfn = zone->zone_start_pfn, block_end_pfn;
        unsigned long end_pfn = pfn + zone->spanned_pages;
        unsigned long count = 0;

        /* Scan block by block. First and last block may be incomplete */
        pfn = zone->zone_start_pfn;

        /*
         * Walk the zone in pageblock_nr_pages steps. If a page block spans
         * a zone boundary, it will be double counted between zones. This does
         * not matter as the mixed block count will still be correct
         */
        for (; pfn < end_pfn; ) {
                if (!pfn_valid(pfn)) {
                        pfn = ALIGN(pfn + 1, MAX_ORDER_NR_PAGES);
                        continue;
                }

                block_end_pfn = ALIGN(pfn + 1, pageblock_nr_pages);
                block_end_pfn = min(block_end_pfn, end_pfn);

                page = pfn_to_page(pfn);

                for (; pfn < block_end_pfn; pfn++) {
                        if (!pfn_valid_within(pfn))
                                continue;

                        page = pfn_to_page(pfn);

                        /*
                         * We are safe to check buddy flag and order, because
                         * this is init stage and only single thread runs.
                         */
                        if (PageBuddy(page)) {
                                pfn += (1UL << page_order(page)) - 1;
                                continue;
                        }

                        if (PageReserved(page))
                                continue;

                        page_ext = lookup_page_ext(page);

                        /* Maybe overraping zone */
                        if (test_bit(PAGE_EXT_OWNER, &page_ext->flags))
                                continue;

                        /* Found early allocated page */
                        set_page_owner(page, 0, 0);
                        count++;
                }
        }

        pr_info("Node %d, zone %8s: page owner found early allocated %lu pages\n",
                pgdat->node_id, zone->name, count);
}

zone에 구성된 페이지 중 onwer 설정이 필요한 경우 page_ext[]에서 해당 페이지를 찾아 owner 비트를 설정한다.

 

set_page_owner()

include/linux/page_owner.h

static inline void set_page_owner(struct page *page,
                        unsigned int order, gfp_t gfp_mask)
{
        if (likely(!page_owner_inited))
                return; 

        __set_page_owner(page, order, gfp_mask);
}

page_ext[]의 해당 페이지에 owner 비트를 설정한다.

 

__set_page_owner()

mm/page_owner.c

void __set_page_owner(struct page *page, unsigned int order, gfp_t gfp_mask)
{
        struct page_ext *page_ext = lookup_page_ext(page);
        struct stack_trace trace = {
                .nr_entries = 0,
                .max_entries = ARRAY_SIZE(page_ext->trace_entries),
                .entries = &page_ext->trace_entries[0],
                .skip = 3,
        };

        save_stack_trace(&trace);

        page_ext->order = order;
        page_ext->gfp_mask = gfp_mask;
        page_ext->nr_entries = trace.nr_entries;

        __set_bit(PAGE_EXT_OWNER, &page_ext->flags);
}

page_ext에 owner를 설정한다.

 

reset_page_owner()

include/linux/page_owner.h

static inline void reset_page_owner(struct page *page, unsigned int order)
{
        if (likely(!page_owner_inited))
                return;

        __reset_page_owner(page, order);
}

2^order 만큼의 페이지들에 대항하는 page_ext의 owner 비트를 clear한다.

 

__reset_page_owner()

mm/page_owner.c

void __reset_page_owner(struct page *page, unsigned int order)
{
        int i;
        struct page_ext *page_ext;

        for (i = 0; i < (1 << order); i++) {
                page_ext = lookup_page_ext(page + i);
                __clear_bit(PAGE_EXT_OWNER, &page_ext->flags);
        }
}

2^order 만큼의 페이지들에 대항하는 page_ext의 owner 비트를 clear한다.

 


page_owner 분석을 위한 덤프

$ cat /sys/kernel/debug/page_owner
Page allocated via order 0, mask 0x6212ca(GFP_HIGHUSER_MOVABLE|__GFP_NOWARN|__GFP_NORETRY)
PFN 262144 type Movable Block 512 type Movable Flags 0xfffc00000020836(referenced|uptodate|lru|activ
e|arch_1|mappedtodisk)
 get_page_from_freelist+0x1050/0x1e68
 __alloc_pages_nodemask+0x220/0xffc
 alloc_pages_current+0xac/0xe8
 __page_cache_alloc+0x130/0x138
 __do_page_cache_readahead+0x118/0x2c4
 filemap_fault+0x3d4/0x9a0
 ext4_filemap_fault+0x44/0x60
 __do_fault+0x7c/0x33c
 __handle_mm_fault+0x112c/0x1cd0
 handle_mm_fault+0x1b0/0x27c
 do_page_fault+0x290/0x4d8
 do_translation_fault+0x88/0x8c
 do_mem_abort+0x58/0xe8
 el0_da+0x20/0x24

Page allocated via order 0, mask 0x6212ca(GFP_HIGHUSER_MOVABLE|__GFP_NOWARN|__GFP_NORETRY)
PFN 262148 type Movable Block 512 type Movable Flags 0xfffc00000020836(referenced|uptodate|lru|activ
e|arch_1|mappedtodisk)
 get_page_from_freelist+0x1050/0x1e68
 __alloc_pages_nodemask+0x220/0xffc
 alloc_pages_current+0xac/0xe8
 __page_cache_alloc+0x130/0x138
 __do_page_cache_readahead+0x118/0x2c4
 filemap_fault+0x3d4/0x9a0
 ext4_filemap_fault+0x44/0x60
 __do_fault+0x7c/0x33c
 __handle_mm_fault+0x112c/0x1cd0
 handle_mm_fault+0x1b0/0x27c
 do_page_fault+0x290/0x4d8
 do_translation_fault+0x88/0x8c
 do_mem_abort+0x58/0xe8
 el0_da+0x20/0x24

...(생략)...

 

참고

 

디버그 메모리 -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을 사용할 수도 있다.

 

참고

디버그 메모리 -1- (Page Alloc)

<kernel v5.0>

디버그 메모리 -1- (Page Alloc)

minorder 미만의 페이지들 할당 시마다 별도의 가드 페이지를 준비하고, 이 페이지에 접근(R/W)하는 경우 예외를 발생시키기 위해 사용된다. 버디 시스템에서 minorder 미만의 페이지들에 대해 이에 대응하는 버디 페이지들을 별도의 가드 페이지들로 활용하기 위해  guard 플래그를 가진채로 free_list에서 제외된다. 이 guard 페이지들은 유저 가상 주소에 매핑하지 않으므로 접근할 수 없다.

 

사용 조건

  • CONFIG_PAGE_EXTENSION  & CONFIG_DEBUG_PAGEALLOC 커널 옵션
    • 커널 v5.3-rc1 부터는 CONFIG_PAGE_EXTENSION 없이도 동작한다.
  • CONFIG_HIBERNATION과 같이 사용할 수 없다.
  • “debug_pagealloc=on” 커널 파라미터
  • “debug_guardpage_minorder=<minorder>” 커널 파라미터
    • minorder는 디폴트 값으로 가드 페이지가 동작하지 않는 0 이고, 최대 MAX_ORDER의 절반까지 지정될 수 있다.
    • 이 값 이상의 페이지들은 가드 페이지들을 생성하지 않는다.
    • 이 값이 크면 클 수록 메모리 낭비가 커진다.

 

 

다음 그림은 가드 페이지를 사용하지 않는 보통 상태의 버디 시스템에서 페이지 할당을 보여준다.

 

다음 그림은 가드 페이지를 사용할 때 할당되는 페이지와 인접한 추가 가드 페이지들을 보여준다.

 

need_debug_guardpage()

mm/page_alloc.c

static bool need_debug_guardpage(void)
{
        /* If we don't use debug_pagealloc, we don't need guard page */
        if (!debug_pagealloc_enabled())
                return false; 

        if (!debug_guardpage_minorder())
                return false;
        return true;
}

가드 페이지 디버깅이 필요한지 여부를 반환한다.

  • 디버그용 guard 페이지를 사용하지 않는 경우에도 false를 반환한다.

 

debug_pagealloc_enabled()

include/linux/mm.h

static inline bool debug_pagealloc_enabled(void) 
{       
        return _debug_pagealloc_enabled;
}

페이지 할당 디버깅 기능 사용 여부를 반환한다.

 

debug_guardpage_minorder()

include/linux/mm.h

static inline unsigned int debug_guardpage_minorder(void)
{
        return _debug_guardpage_minorder;
}

디버그용 guard 페이지로 설정된 최소 order 값을 반환한다.

  • “debug_guardpage_minorder=” 커널 파라메터 값은 1~MAX_ORDER의 절반까지 가능하다.
    • MAX_ORDER가 11인 경우 1~5까지 설정 가능하다.

 

init_debug_guardpage()

mm/page_alloc.c

static void init_debug_guardpage(void)
{                           
        if (!debug_pagealloc_enabled())
                return;

        _debug_guardpage_enabled = true; 
}

가드 페이지 디버깅 기능을 enable 한다.

 

page_is_guard()

include/linux/mm.h

#ifdef CONFIG_DEBUG_PAGEALLOC
static inline bool page_is_guard(struct page *page)
{       
        struct page_ext *page_ext;

        if (!debug_guardpage_enabled())
                return false;

        page_ext = lookup_page_ext(page); 
        return test_bit(PAGE_EXT_DEBUG_GUARD, &page_ext->flags);
}
#endif

페이지에 대항하는 page_ext의 guard 플래그 비트가 설정되었는지 여부를 반환한다.

 

clear_page_guard()

mm/page_alloc.c

static inline void clear_page_guard(struct zone *zone, struct page *page,
                                unsigned int order, int migratetype)
{
        struct page_ext *page_ext;

        if (!debug_guardpage_enabled())
                return;

        page_ext = lookup_page_ext(page);
        __clear_bit(PAGE_EXT_DEBUG_GUARD, &page_ext->flags);

        set_page_private(page, 0);
        if (!is_migrate_isolate(migratetype))
                __mod_zone_freepage_state(zone, (1 << order), migratetype);
}

페이지에 대항하는 page_ext의 guard 플래그 비트를 clear 하고 _private에 0을 대입한다. zone에 대한 free page stat도 2^order 만큼 증가시킨다.

 


버디 페이지 expand 시 Guard 페이지로 사용

expand()

mm/page_alloc.c

/*
 * The order of subdivision here is critical for the IO subsystem.
 * Please do not alter this order without good reasons and regression
 * testing. Specifically, as large blocks of memory are subdivided,
 * the order in which smaller blocks are delivered depends on the order
 * they're subdivided in this function. This is the primary factor
 * influencing the order in which pages are delivered to the IO
 * subsystem according to empirical testing, and this is also justified
 * by considering the behavior of a buddy system containing a single
 * large block of memory acted on by a series of small allocations.
 * This behavior is a critical factor in sglist merging's success.
 *
 * -- nyc
 */
static inline void expand(struct zone *zone, struct page *page,
        int low, int high, struct free_area *area,
        int migratetype)
{
        unsigned long size = 1 << high;

        while (high > low) {
                area--;
                high--;
                size >>= 1;
                VM_BUG_ON_PAGE(bad_range(zone, &page[size]), &page[size]);

                /*
                 * Mark as guard pages (or page), that will allow to
                 * merge back to allocator when buddy will be freed.
                 * Corresponding page table entries will not be touched,
                 * pages will stay not present in virtual address space
                 */
                if (set_page_guard(zone, &page[size], high, migratetype))
                        continue;

                list_add(&page[size].lru, &area->free_list[migratetype]);
                area->nr_free++;
                set_page_order(&page[size], high);
        }
}

버디 시스템에서 요청한 order 페이지가 부족한 경우 더 큰 order 페이지를 확장하여 사용할 수 있는데 이 때 호출되어 동작하는 expand() 함수이다. 가드 페이지와 관련된 코드만을 잠시 살펴본다.

  • 코드 라인 10에서 size에는 할당할 페이지의 다음 페이지인 버디 페이지를 가드 페이지로 사용하기 위해 얻어온다.
  • 코드 라인 19~20에서 guard 페이지를 사용하는 경우 guard 페이지 플래그를 설정한다. 그 후 가드 페이지는 free_list에 추가하지 않기 위해 skip 한다.

 

page_is_guard()

mm/page_alloc.c

static inline bool page_is_guard(struct page *page)
{
        struct page_ext *page_ext;

        if (!debug_guardpage_enabled())
                return false;

        page_ext = lookup_page_ext(page);
        if (unlikely(!page_ext))
                return false;

        return test_bit(PAGE_EXT_DEBUG_GUARD, &page_ext->flags);
}

@page가 가드 페이지인지 여부를 알아온다.

 

가드 페이지 지정

set_page_guard()

mm/page_alloc.c

static inline bool set_page_guard(struct zone *zone, struct page *page,
                                unsigned int order, int migratetype)
{
        struct page_ext *page_ext;

        if (!debug_guardpage_enabled())
                return false;

        if (order >= debug_guardpage_minorder())
                return false;

        page_ext = lookup_page_ext(page);
        if (unlikely(!page_ext))
                return false;

        __set_bit(PAGE_EXT_DEBUG_GUARD, &page_ext->flags);

        INIT_LIST_HEAD(&page->lru);
        set_page_private(page, order);
        /* Guard pages are not available for any usage */
        __mod_zone_freepage_state(zone, -(1 << order), migratetype);

        return true;
}

@page를 guard 페이지로 지정한다. 정상적으로 설정한 경우 true를 반환한다.

  • guard 페이지는 private 멤버에 order 값이 지정되고, 확장 페이지에 guard 플래그가 설정된다.

 

가드 페이지 지정 해제

clear_page_guard()

mm/page_alloc.c

static inline void clear_page_guard(struct zone *zone, struct page *page,
                                unsigned int order, int migratetype)
{
        struct page_ext *page_ext;

        if (!debug_guardpage_enabled())
                return;

        page_ext = lookup_page_ext(page);
        if (unlikely(!page_ext))
                return;

        __clear_bit(PAGE_EXT_DEBUG_GUARD, &page_ext->flags);

        set_page_private(page, 0);
        if (!is_migrate_isolate(migratetype))
                __mod_zone_freepage_state(zone, (1 << order), migratetype);
}

@page가 guard 페이지인 경우 취소한다.

  • private 멤버에 0 값이 지정되고, 확장 페이지에 guard 플래그를 클리어한다.

 

Early 커널 파라메터

early_debug_pagealloc()

mm/page_alloc.c

static int __init early_debug_pagealloc(char *buf)
{
        if (!buf)
                return -EINVAL;

        if (strcmp(buf, "on") == 0)
                _debug_pagealloc_enabled = true;
        
        return 0;
}       
early_param("debug_pagealloc", early_debug_pagealloc);

“debug_pagealloc=on” early 커널 파라메터를 사용하는 경우 페이지 할당 디버깅 기능을 사용한다.

 

커널 Tools

tools/vm에서 make를 사용하여 빌드하면 3개의 유틸리티가 생성된다.

  • page-types
    • 참조하는 proc 인터페이스들
      • /proc/<pid>/pagemap
      • /proc/<pid>maps
      • /proc/self/pagemap
      • /proc/kpageflags
      • /proc/kpagecount
      • /proc/kpagecgroup
      • /sys/kernel/mm/page_idle/bitmap
  • page_owner_sort
  • slabinfo

 

page-types

이 유틸리티를 통해 페이지 타입 유형들을 알아볼 수 있다.

  • 옵션을 사용하여 특정 가상 주소, 태스크, 파일에 관여된 페이지 타입들을 관찰할 수 있다.
$ tools/vm/page-types --help
page-types [options]
            -r|--raw                   Raw mode, for kernel developers
            -d|--describe flags        Describe flags
            -a|--addr    addr-spec     Walk a range of pages
            -b|--bits    bits-spec     Walk pages with specified bits
            -c|--cgroup  path|@inode   Walk pages within memory cgroup
            -p|--pid     pid           Walk process address space
            -f|--file    filename      Walk file address space
            -i|--mark-idle             Mark pages idle
            -l|--list                  Show page details in ranges
            -L|--list-each             Show page details one by one
            -C|--list-cgroup           Show cgroup inode for pages
            -M|--list-mapcnt           Show page map count
            -N|--no-summary            Don't show summary info
            -X|--hwpoison              hwpoison pages
            -x|--unpoison              unpoison pages
            -F|--kpageflags filename   kpageflags file to parse
            -h|--help                  Show this usage message
flags:
            0x10                       bitfield format, e.g.
            anon                       bit-name, e.g.
            0x10,anon                  comma-separated list, e.g.
addr-spec:
            N                          one page at offset N (unit: pages)
            N+M                        pages range from N to N+M-1
            N,M                        pages range from N to M-1
            N,                         pages range from N to end
            ,M                         pages range from 0 to M-1
bits-spec:
            bit1,bit2                  (flags & (bit1|bit2)) != 0
            bit1,bit2=bit1             (flags & (bit1|bit2)) == bit1
            bit1,~bit2                 (flags & (bit1|bit2)) == bit1
            =bit1,bit2                 flags == (bit1|bit2)
bit-names:
          locked              error         referenced           uptodate
           dirty                lru             active               slab
       writeback            reclaim              buddy               mmap
       anonymous          swapcache         swapbacked      compound_head
   compound_tail               huge        unevictable           hwpoison
          nopage                ksm                thp            balloon
       zero_page          idle_page            pgtable           reserved(r)
         mlocked(r)    mappedtodisk(r)         private(r)       private_2(r)
   owner_private(r)            arch(r)        uncached(r)       softdirty(r)
       readahead(o)       slob_free(o)     slub_frozen(o)      slub_debug(o)
            file(o)            swap(o)  mmap_exclusive(o)
                                   (r) raw mode bits  (o) overloaded bits

 

$ tools/vm/page-types
             flags      page-count       MB  symbolic-flags                     long-symbolic-flags
0x0000000000000000           92360      360  ___________________________________________
0x0000000000100000             512        2  ____________________n______________________        nopage
0x0000000001000000               1        0  ________________________z__________________        zero_page
0x0000000000000014              10        0  __R_D______________________________________        referenced,dirty
0x0000000000000020              18        0  _____l_____________________________________        lru
0x0000000000000024              16        0  __R__l_____________________________________        referenced,lru
0x0000000000000028          102116      398  ___U_l_____________________________________        uptodate,lru
0x0000000000004028            1536        6  ___U_l________b____________________________        uptodate,lru,swapbacked
0x000000000000002c           90364      352  __RU_l_____________________________________        referenced,uptodate,lru
0x0000000000004030           11454       44  ____Dl________b____________________________        dirty,lru,swapbacked
0x000000000000403c           15846       61  __RUDl________b____________________________        referenced,uptodate,dirty,lru,swapbacked
0x0000000000000040               1        0  ______A____________________________________        active
0x0000000000000060            7293       28  _____lA____________________________________        lru,active
0x0000000000000064           19994       78  __R__lA____________________________________        referenced,lru,active
0x0000000000000068          340912     1331  ___U_lA____________________________________        uptodate,lru,active
0x000000000000006c          233190      910  __RU_lA____________________________________        referenced,uptodate,lru,active
0x0000000000000074               4        0  __R_DlA____________________________________        referenced,dirty,lru,active
0x0000000000004078           16146       63  ___UDlA_______b____________________________        uptodate,dirty,lru,active,swapbacked
0x0000000000000078               1        0  ___UDlA____________________________________        uptodate,dirty,lru,active
0x000000000000407c              91        0  __RUDlA_______b____________________________        referenced,uptodate,dirty,lru,active,swapbacked
0x000000000000007c               1        0  __RUDlA____________________________________        referenced,uptodate,dirty,lru,active
0x0000000000000080           37452      146  _______S___________________________________        slab
0x0000000000000228            2202        8  ___U_l___I_________________________________        uptodate,lru,reclaim
0x0000000000000268              26        0  ___U_lA__I_________________________________        uptodate,lru,active,reclaim
0x0000000000000400             965        3  __________B________________________________        buddy
0x0000000000000800               1        0  ___________M_______________________________        mmap
0x0000000000000804               1        0  __R________M_______________________________        referenced,mmap
0x0000000000000828             232        0  ___U_l_____M_______________________________        uptodate,lru,mmap
0x0000000000004828               8        0  ___U_l_____M__b____________________________        uptodate,lru,mmap,swapbacked
0x0000000000004838            4600       17  ___UDl_____M__b____________________________        uptodate,dirty,lru,mmap,swapbacked
0x000000000000483c               2        0  __RUDl_____M__b____________________________        referenced,uptodate,dirty,lru,mmap,swapbacked
0x0000000000000868             173        0  ___U_lA____M_______________________________        uptodate,lru,active,mmap
0x000000000000086c            4925       19  __RU_lA____M_______________________________        referenced,uptodate,lru,active,mmap
0x0000000000004878               1        0  ___UDlA____M__b____________________________        uptodate,dirty,lru,active,mmap,swapbacked
0x0000000000005048               3        0  ___U__A_____a_b____________________________        uptodate,active,anonymous,swapbacked
0x0000000000005848               2        0  ___U__A____Ma_b____________________________        uptodate,active,mmap,anonymous,swapbacked
0x0000000000005868           33295      130  ___U_lA____Ma_b____________________________        uptodate,lru,active,mmap,anonymous,swapbacked
0x000000000000586c              54        0  __RU_lA____Ma_b____________________________        referenced,uptodate,lru,active,mmap,anonymous,swapbacked
             total         1015808     3968

 

특정 유저 태스크(–pid 옵션)가 사용한 페이지 타입들을 보여준다.

$ tools/vm/page-types --pid 959
             flags      page-count       MB  symbolic-flags                     long-symbolic-flags
0x0000000000000800               1        0  ___________M_______________________________        mmap
0x0000000000000804               1        0  __R________M_______________________________        referenced,mmap
0x000000000000086c            1271        4  __RU_lA____M_______________________________        referenced,uptodate,lru,active,mmap
0x0000000000005868             311        1  ___U_lA____Ma_b____________________________        uptodate,lru,active,mmap,anonymous,swapbacked
0x000000000000586c               1        0  __RU_lA____Ma_b____________________________        referenced,uptodate,lru,active,mmap,anonymous,swapbacked
             total            1585        6

 

특정 파일(–file 옵션)에 사용한 페이지 타입들을 보여준다.

$ tools/vm/page-types --file abc.txt
             flags      page-count       MB  symbolic-flags                     long-symbolic-flags
0x0000000000000068               1        0  ___U_lA____________________________________        uptodate,lru,active
             total               1        0

 

참고

copy_from_user()

 

프로세스 주소 공간(process address space)을 access하는 커널 API

가장 잘 알려진 다음 API 두 개를 알아본다.

  • copy_from_user()
  • copy_to_user()

 

그리고 유저 프로세스 공간으로의 안전한 접근이 가능한지 여부를 체크하는 다음 API를 알아본다.

  • access_ok()

 


유저 프로세스 공간으로 부터 커널로 데이터 복사

 

copy_from_user()

include/linux/uaccess.h

static __always_inline unsigned long __must_check
copy_from_user(void *to, const void __user *from, unsigned long n)
{
        if (likely(check_copy_size(to, n, false)))
                n = _copy_from_user(to, from, n);
        return n;
}

유저 가상 주소 @from에서 @n 바이트만큼 커널 가상 주소 @to로 데이터를 복사한다.

 

_copy_from_user()

include/linux/uaccess.h

#ifdef INLINE_COPY_FROM_USER
static inline unsigned long
_copy_from_user(void *to, const void __user *from, unsigned long n)
{
        unsigned long res = n;
        might_fault();
        if (likely(access_ok(from, n))) {
                kasan_check_write(to, n);
                res = raw_copy_from_user(to, from, n);
        }
        if (unlikely(res))
                memset(to + (n - res), 0, res);
        return res;
}
#endif

이 함수는 유저 application에서 사용될 때에는 INLINE_COPY_FROM_USER 옵션이 적용되어 인라인 함수로 제공된다. 그렇지 않고 커널을 통해 호출되는 경우 라이브러리를 통해 제공된다.

 

아키텍처별 raw_copy_from_user()

다음 아키텍처별로 권한 설정을 아키텍처 고유의 방법을 사용한다.

  • ARM32
    • 도메인 접근 제어 레지스터를 사용하여 제어한다.
  • ARM64
    • 커널에서의 유저 액세스 권한 제어 플래그를 사용하여 제어한다.

 

raw_copy_from_user() – ARM64

arch/arm64/include/asm/uaccess.h

#define raw_copy_from_user(to, from, n)                                 \
({                                                                      \
        __arch_copy_from_user((to), __uaccess_mask_ptr(from), (n));     \
})

유저 가상 주소 @from(x1)에서 @n 바이트(x2)만큼 커널 가상 주소 @to(x0)로  복사한다.

 

다음 그림은 ARM64 시스템에서 copy_from_user() 함수의 처리 흐름을 보여준다.

 

__arch_copy_from_user() – ARM64

arch/arm64/lib/copy_from_user.S

ENTRY(__arch_copy_from_user)
        uaccess_enable_not_uao x3, x4, x5
        add     end, x0, x2
#include "copy_template.S"
        uaccess_disable_not_uao x3, x4
        mov     x0, #0                          // Nothing to copy
        ret
ENDPROC(__arch_copy_from_user)
EXPORT_SYMBOL(__arch_copy_from_user)

커널이 유저 데이터에 접근할 수 있도록 잠시 허용한 후 유저 가상 주소 src(x1)에서 n 바이트(x2)만큼 커널 가상 주소 dest(x0)로 복사한다. 그런 후 커널이 유저 데이터에 접근하지 못하게 막는다.

  • copy_template.S 에는 어셈블리로 복사 코드가 담겨있다.

 

raw_copy_from_user() – ARM32

arch/arm/include/asm/uaccess.h

static inline unsigned long __must_check
raw_copy_from_user(void *to, const void __user *from, unsigned long n)
{
        unsigned int __ua_flags;

        __ua_flags = uaccess_save_and_enable();
        n = arm_copy_from_user(to, from, n);
        uaccess_restore(__ua_flags);
        return n;
}

유저 가상 주소 @from(r1)에서 @n 바이트(r2)만큼 커널 가상 주소 @to(r0)로 복사한다.

 

다음 그림은 ARM32 시스템에서 copy_from_user() 함수의 처리 흐름을 보여준다.

 

arm_copy_from_user()

lib/copy_from_user.S

ENTRY(arm_copy_from_user)
#ifdef CONFIG_CPU_SPECTRE
        get_thread_info r3
        ldr     r3, [r3, #TI_ADDR_LIMIT]
        uaccess_mask_range_ptr r1, r2, r3, ip
#endif

#include "copy_template.S"

ENDPROC(arm_copy_from_user)
  • copy_template.S 에는 어셈블리로 복사 코드가 담겨있다.

 


유저 프로세스 공간으로 커널 데이터 복사

 

copy_to_user()

include/linux/uaccess.h

static __always_inline unsigned long __must_check
copy_to_user(void __user *to, const void *from, unsigned long n)
{
        if (likely(check_copy_size(from, n, true)))
                n = _copy_to_user(to, from, n);
        return n;
}

커널 가상 주소 @from에서 @n 바이트만큼 유저 가상 주소 @to로 데이터를 복사한다.

 

_copy_to_user()

include/linux/uaccess.h

#ifdef INLINE_COPY_TO_USER
static inline unsigned long
_copy_to_user(void __user *to, const void *from, unsigned long n)
{
        might_fault();
        if (access_ok(to, n)) {
                kasan_check_read(from, n);
                n = raw_copy_to_user(to, from, n);
        }
        return n;
}
#endif

이 함수는 유저 application에서 사용될 때에는 INLINE_COPY_TO_USER 옵션이 적용되어 인라인 함수로 제공된다. 그렇지 않고 커널을 통해 호출되는 경우 라이브러리를 통해 제공된다.

 

아키텍처별 raw_copy_to_user()

다음 아키텍처별로 권한 설정을 아키텍처 고유의 방법을 사용한다.

  • ARM32
    • 도메인 접근 제어 레지스터를 사용하여 제어한다.
  • ARM64
    • 커널에서의 유저 액세스 권한 제어 플래그를 사용하여 제어한다.

 

raw_copy_to_user() – ARM64

arch/arm64/include/asm/uaccess.h

#define raw_copy_to_user(to, from, n)                                   \
({                                                                      \
        __arch_copy_to_user(__uaccess_mask_ptr(to), (from), (n));       \
})

커널 가상 주소 @from(x1)에서 @n 바이트(x2)만큼 유저 가상 주소 @to(x0)로 복사한다.

 

__arch_copy_to_user()

arch/arm64/lib/copy_to_user.S

ENTRY(__arch_copy_to_user)
        uaccess_enable_not_uao x3, x4, x5
        add     end, x0, x2
#include "copy_template.S"
        uaccess_disable_not_uao x3, x4
        mov     x0, #0
        ret
ENDPROC(__arch_copy_to_user)
EXPORT_SYMBOL(__arch_copy_to_user)

커널이 유저 데이터에 접근할 수 있도록 잠시 허용한 후 커널 가상 주소 src(x1)에서 n 바이트(x2)만큼 유저 가상 주소 dest(x0)로 복사한다. 그런 후 커널이 유저 데이터에 접근하지 못하게 막는다.

  • copy_template.S 에는 어셈블리로 복사 코드가 담겨있다.

 

raw_copy_to_user() – ARM32

arch/arm/include/asm/uaccess.h

static inline unsigned long __must_check
raw_copy_to_user(void __user *to, const void *from, unsigned long n)
{
#ifndef CONFIG_UACCESS_WITH_MEMCPY
        unsigned int __ua_flags;
        __ua_flags = uaccess_save_and_enable();
        n = arm_copy_to_user(to, from, n);
        uaccess_restore(__ua_flags);
        return n;
#else
        return arm_copy_to_user(to, from, n);
#endif
}

커널 가상 주소 @from(r1)에서 @n 바이트(r2)만큼 유저 가상 주소 @to(r0)로 복사한다.

 

arm_copy_to_user()

lib/copy_to_user.S

ENTRY(__copy_to_user_std)
WEAK(arm_copy_to_user)
#ifdef CONFIG_CPU_SPECTRE
        get_thread_info r3
        ldr     r3, [r3, #TI_ADDR_LIMIT]
        uaccess_mask_range_ptr r0, r2, r3, ip
#endif

#include "copy_template.S"

ENDPROC(arm_copy_to_user)
  • copy_template.S 에는 어셈블리로 복사 코드가 담겨있다.

 


User 공간 접근 제어 – ARM64

보안을 향상시킬 목적으로 커널에서 유저 공간 액세스를 컨트롤하기 위해 다음 두 가지 방법 중 하나를 사용할 수 있다. 이러한 기능이 적용되지 않는 경우 기본적으로 커널에서 유저 공간의 접근이 언제나 허용된다.

  • ARMv8.1-PAN 기능을 사용한 제어 (HW 기법)
    • 커널 옵션: CONFIG_ARM64_PAN
  • SW_TTBR0_PAN 기능을 사용한 제어 (SW 기법)
    • TTBR0_EL1을 빈 페이지 테이블에 연결하고, TTBR1_EL1의 ASID를 클리어하는 SW 에뮬레이션 방법을 사용하여 커널에서 유저 접근을 금지시킬 수 있다.
    • 커널 옵션:CONFIG_ARM64_SW_TTBR0_PAN

 

그 외 – UAO 기능을 사용한 커널에서의 유저 공간 접근 권한 Override

커널과 유저 데이터 교환 함수들(copy_from_user(), copy_to_user()…)에서 유저 공간에 접근할 때 ldr/str 대신 유저 페이지 테이블에 기록된 권한 설정들을 override 하여 사용하는 ldtr/sttr 명령을 사용할 수 있다.

  • ARMv8.2-UAO
    • 커널에서 유저 주소에 접근할 때 ldr/str 명령으로 접근하는 경우 유저 페이지 테이블에 기록된 권한 설정들을 사용하지 못한다.
    • 그러나 UAO 기능이 지원되는 아키텍처에서 ldtr/sttr 명령을 사용 시 유저 페이지 테이블에 기록된 권한 설정들을 Override 할 수 있다.
    • 커널 옵션: CONFIG_ARM64_UAO

 

1) 유저 공간 접근 허용 – for ARM64 Assembly

uaccess_enable_not_uao() 매크로

arch/arm64/include/asm/asm-uaccess.h

        .macro  uaccess_enable_not_uao, tmp1, tmp2, tmp3
        uaccess_ttbr0_enable \tmp1, \tmp2, \tmp3
alternative_if ARM64_ALT_PAN_NOT_UAO
        SET_PSTATE_PAN(0)
alternative_else_nop_endif
        .endm

커널에서 유저 데이터에 접근할 수 있도록 허용한다.

  • 코드 라인 2에서 ARMv8.1 아키텍처 이상에서 지원되는 PAN 기능이 지원되지 않는 경우 SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 허용하게 한다.
  • 코드 라인 3~5에서 PAN 기능이 지원되면 ARMv8.2 아키텍처 이상에서만 지원하는 UAO 기능에 의지 하지 않고 PAN 기능을 disable하여 커널에서 유저 공간에 접근할 수 있도록 허용한다.

 

uaccess_ttbr0_enable() 매크로

arch/arm64/include/asm/asm-uaccess.h

        .macro  uaccess_ttbr0_enable, tmp1, tmp2, tmp3
alternative_if_not ARM64_HAS_PAN
        save_and_disable_irq \tmp3              // avoid preemption
        __uaccess_ttbr0_enable \tmp1, \tmp2
        restore_irq \tmp3
alternative_else_nop_endif
        .endm

ARMv8.1 아키텍처 이상에서 지원되는 PAN 기능이 지원되지 않는 경우 SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 허용하게 한다.

 

__uaccess_ttbr0_enable() 매크로

        .macro  __uaccess_ttbr0_enable, tmp1, tmp2
        get_thread_info \tmp1
        ldr     \tmp1, [\tmp1, #TSK_TI_TTBR0]   // load saved TTBR0_EL1
        mrs     \tmp2, ttbr1_el1
        extr    \tmp2, \tmp2, \tmp1, #48
        ror     \tmp2, \tmp2, #16
        msr     ttbr1_el1, \tmp2                // set the active ASID
        isb
        msr     ttbr0_el1, \tmp1                // set the non-PAN TTBR0_EL1
        isb
        .endm

SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 허락하도록 ttbr을 설정한다.

  • 유저 페이지 테이블을 위해 사용되는 TTBR0의 asid를 커널 페이지 테이블을 위해 사용되는 TTBR1에 복사하여 커널이 유저 액세스가 가능하도록 허용한다.
  • 유저 페이지 테이블을 위해 사용되는 TTBR0의 asid를 읽어 커널 페이지 테이블을 위해 사용되는 TTBR1에 asid 필드만을 기록하여 커널에서 유저 영역에 접근할 수 있게 허용한다.

 

2) 유저 공간 접근 금지 – for ARM64 Assembly

uaccess_disable_not_uao() 매크로

arch/arm64/include/asm/asm-uaccess.h

        .macro  uaccess_disable_not_uao, tmp1, tmp2
        uaccess_ttbr0_disable \tmp1, \tmp2
alternative_if ARM64_ALT_PAN_NOT_UAO
        SET_PSTATE_PAN(1)
alternative_else_nop_endif
        .endm

커널에서 유저 데이터에 접근하지 못하게 금지한다.

  • 코드 라인 2에서 ARMv8.1 아키텍처 이상에서 지원되는 PAN 기능이 지원되지 않는 경우 SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 금지하게 한다.
  • 코드 라인 3~5에서 PAN 기능이 지원되면 ARMv8.2 아키텍처 이상에서만 지원하는 UAO 기능에 의지 하지 않고 PAN 기능을 enable하여 커널에서 유저 공간에 접근하지 못하게 금지한다.

 

uaccess_ttbr0_disable() 매크로

arch/arm64/include/asm/asm-uaccess.h

        .macro  uaccess_ttbr0_enable, tmp1, tmp2, tmp3
alternative_if_not ARM64_HAS_PAN
        save_and_disable_irq \tmp3              // avoid preemption
        __uaccess_ttbr0_enable \tmp1, \tmp2
        restore_irq \tmp3
alternative_else_nop_endif
        .endm

ARMv8.1 아키텍처 이상에서 지원되는 PAN 기능이 지원되지 않는 경우 SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 금지하게 한다.

 

__uaccess_ttbr0_disable() 매크로

arch/arm64/include/asm/asm-uaccess.h

        .macro  __uaccess_ttbr0_disable, tmp1
        mrs     \tmp1, ttbr1_el1                        // swapper_pg_dir
        bic     \tmp1, \tmp1, #TTBR_ASID_MASK
        sub     \tmp1, \tmp1, #RESERVED_TTBR0_SIZE      // reserved_ttbr0 just before swapper_pg_dir
        msr     ttbr0_el1, \tmp1                        // set reserved TTBR0_EL1
        isb
        add     \tmp1, \tmp1, #RESERVED_TTBR0_SIZE
        msr     ttbr1_el1, \tmp1                // set reserved ASID
        isb
        .endm

SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 금지하도록 ttbr을 설정한다.

  • 커널 페이지 테이블을 가리키는 TTBR1의 asid를 클리어한다.
  • 유저 페이지 테이블을 가리키는 TTBR0에 reserved_ttbr0 라는 이름의 빈 페이지 테이블을 가리키게한다.
    • CONFIG_ARM64_SW_TTBR0_PAN 커널 옵션을 사용하는 경우 swapper_pg_dir 이전에 reserved_ttbr0 라는 이름의 빈 페이지 테이블이 구성되어 있다.

 

Process State(PSTATE) 설정

/*
 * Instructions for modifying PSTATE fields.
 * As per Arm ARM for v8-A, Section "C.5.1.3 op0 == 0b00, architectural hints,
 * barriers and CLREX, and PSTATE access", ARM DDI 0487 C.a, system instructions
 * for accessing PSTATE fields have the following encoding:
 *      Op0 = 0, CRn = 4
 *      Op1, Op2 encodes the PSTATE field modified and defines the constraints.
 *      CRm = Imm4 for the instruction.
 *      Rt = 0x1f
 */
#define pstate_field(op1, op2)          ((op1) << Op1_shift | (op2) << Op2_shift)
#define PSTATE_Imm_shift                CRm_shift

#define PSTATE_PAN                      pstate_field(0, 4)
#define PSTATE_UAO                      pstate_field(0, 3)
#define PSTATE_SSBS                     pstate_field(3, 1)

#define SET_PSTATE_PAN(x)               __emit_inst(0xd500401f | PSTATE_PAN | ((!!x) << PSTATE_Imm_ss
hift))
#define SET_PSTATE_UAO(x)               __emit_inst(0xd500401f | PSTATE_UAO | ((!!x) << PSTATE_Imm_ss
hift))
#define SET_PSTATE_SSBS(x)              __emit_inst(0xd500401f | PSTATE_SSBS | ((!!x) << PSTATE_Imm__
shift))

MSR을 사용하고 immediate 접근을 통해 PSTATE 필드 들 중 다음의 필드들을 설정할 수 있다.

  •  SET_PSTATE_PAN()
    • PSTATE 중 PAN(Privileged Access Never) 비트를 설정한다.
    • ARMv8.1-PAN 이 구현된 아키텍처만 사용가능하다.
  • SET_PSTATE_UAO()
    • PSTATE 중 UAO(User Access Override) 비트를 설정한다.
    • ARMv8.2-UAO가 구현된 아키텍처만 사용가능하다.

 

다음 그림은 MSR의 immediate 접근을 통해 PSTATE 관련 몇 개의 플래그를 설정하기 위해 사용되는 op1, CRm, op2의 위치를 보여준다.

  • CRm 값을 지정하기 위해 필요한 CRm_shift 값은 8이다.
  • op1 값을 지정하기 위해 필요한 op1_shift 값은 16이다.
  • op2 값을 지정하기 위해 필요한 op2_shift 값은 5이다.

 

3) 유저 공간 접근 허용 – for ARM64 C

uaccess_enable_not_uao()

arch/arm64/include/asm/uaccess.h

static inline void uaccess_enable_not_uao(void)
{
        __uaccess_enable(ARM64_ALT_PAN_NOT_UAO);
}

커널에서 유저 데이터에 접근할 수 있도록 잠시 허용한다.

 

__uaccess_enable()

arch/arm64/include/asm/uaccess.h

#define __uaccess_enable(alt)                                           \
do {                                                                    \
        if (!uaccess_ttbr0_enable())                                    \
                asm(ALTERNATIVE("nop", SET_PSTATE_PAN(0), alt,          \
                                CONFIG_ARM64_PAN));                     \
} while (0)

커널에서 유저 액세스가 가능하도록 허용한다.

  • 코드 라인 3에서 ARMv8.1 아키텍처 이상에서 지원되는 PAN 기능이 지원되지 않는 경우 SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 허용하게 한다.
  • 코드 라인 4~5에서 PAN 기능 지원 여부에 따라 ARMv8.2 아키텍처 이상에서만 지원하는 UAO 기능에 의지 하지 않고 PAN 기능을 disable하여 커널에서 유저 공간에 접근할 수 있도록 허용한다.

 

uaccess_ttbr0_enable() – ARM64

arch/arm64/include/asm/uaccess.h

static inline bool uaccess_ttbr0_enable(void)
{
        if (!system_uses_ttbr0_pan())
                return false;
        __uaccess_ttbr0_enable();
        return true;
}

커널에서 유저 데이터에 접근할 수 있도록 허용한다.

  • 코드 라인 2에서 ARMv8.1 아키텍처 이상에서 지원되는 PAN 기능이 지원되지 않는 경우 SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 허용하게 한다.
  • 코드 라인 3~5에서 PAN 기능이 지원되면 ARMv8.2 아키텍처 이상에서만 지원하는 UAO 기능에 의지 하지 않고 PAN 기능을 enable하여 커널에서 유저 공간에 접근할 수 있도록 허용한다.

 

system_uses_ttbr0_pan() – ARM64

arch/arm64/include/asm/cpufeature.h

static inline bool system_uses_ttbr0_pan(void)
{
        return IS_ENABLED(CONFIG_ARM64_SW_TTBR0_PAN) &&
                !cpus_have_const_cap(ARM64_HAS_PAN);
}

현재 동작 중인 ARM64 커널에 CONFIG_ARM64_SW_TTBR0_PAN 커널 옵션이 설정되어 있지만 아키텍처가 PAN(Previlidge Access Never) 기능을 가지고 있지 않으면 TTBR0을 사용하여 PAN을 에뮬레이션을 하기 위해  1을 반환한다.

 

__uaccess_ttbr0_enable() – ARM64

arch/arm64/include/asm/uaccess.h

static inline void __uaccess_ttbr0_enable(void)
{
        unsigned long flags, ttbr0, ttbr1;

        /*
         * Disable interrupts to avoid preemption between reading the 'ttbr0'
         * variable and the MSR. A context switch could trigger an ASID
         * roll-over and an update of 'ttbr0'.
         */
        local_irq_save(flags);
        ttbr0 = READ_ONCE(current_thread_info()->ttbr0);

        /* Restore active ASID */
        ttbr1 = read_sysreg(ttbr1_el1);
        ttbr1 &= ~TTBR_ASID_MASK;               /* safety measure */
        ttbr1 |= ttbr0 & TTBR_ASID_MASK;
        write_sysreg(ttbr1, ttbr1_el1);
        isb();

        /* Restore user page table */
        write_sysreg(ttbr0, ttbr0_el1);
        isb();
        local_irq_restore(flags);
}

SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 허락하도록 ttbr을 설정한다.

  • TTBR0의 asid를 TTBR1에 복사하여 커널이 유저 액세스가 가능하도록 허용한다.
  • 유저 페이지 테이블을 위해 사용되는 ttbr0의 asid를 읽어 커널 페이지 테이블을 위해 사용되는 ttbr1에 asid 필드만을 기록하여 커널에서 유저 영역에 접근할 수 있게 허용한다.

 

4) 유저 공간 접근 금지 – for ARM64 C

uaccess_disable_not_uao()

arch/arm64/include/asm/uaccess.h

static inline void uaccess_disable_not_uao(void)
{
        __uaccess_disable(ARM64_ALT_PAN_NOT_UAO);
}

다시 커널에서 유저 데이터에 접근하지 못하게 한다.

 

__uaccess_disable()

arch/arm64/include/asm/uaccess.h

#define __uaccess_disable(alt)                                          \
do {                                                                    \
        if (!uaccess_ttbr0_disable())                                   \
                asm(ALTERNATIVE("nop", SET_PSTATE_PAN(1), alt,          \
                                CONFIG_ARM64_PAN));                     \
} while (0)

커널에서 유저 데이터에 접근하지 못하게 금지한다.

  • 코드 라인 3에서 ARMv8.1 아키텍처 이상에서 지원되는 PAN 기능이 지원되지 않는 경우 SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 금지하게 한다.
  • 코드 라인 4~6에서 PAN 기능이 지원되면 ARMv8.2 아키텍처 이상에서만 지원하는 UAO 기능에 의지 하지 않고 PAN 기능을 enable하여 커널에서 유저 공간에 접근하지 못하게 금지한다.

 

uaccess_ttbr0_disable()

arch/arm64/include/asm/uaccess.h

static inline bool uaccess_ttbr0_disable(void)
{
        if (!system_uses_ttbr0_pan())
                return false;
        __uaccess_ttbr0_disable();
        return true;
}

커널에서 유저 데이터에 접근할 수 없도록 금지한다.

  • 코드 라인 2에서 ARMv8.1 아키텍처 이상에서 지원되는 PAN 기능이 지원되지 않는 경우 SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 금지하게 한다.
  • 코드 라인 3~5에서 PAN 기능이 지원되면 ARMv8.2 아키텍처 이상에서만 지원하는 UAO 기능에 의지 하지 않고 PAN 기능을 enable하여 커널에서 유저 공간에 접근하지 못하게 금지한다.

 

__uaccess_ttbr0_disable()

arch/arm64/include/asm/uaccess.h

static inline void __uaccess_ttbr0_disable(void)
{
        unsigned long flags, ttbr;

        local_irq_save(flags);
        ttbr = read_sysreg(ttbr1_el1);
        ttbr &= ~TTBR_ASID_MASK;
        /* reserved_ttbr0 placed before swapper_pg_dir */
        write_sysreg(ttbr - RESERVED_TTBR0_SIZE, ttbr0_el1);
        isb();
        /* Set reserved ASID */
        write_sysreg(ttbr, ttbr1_el1);
        isb();
        local_irq_restore(flags);
}

SW_TTBR0_PAN 기능을 통해 커널에서 유저 공간의 접근을 금지하도록 ttbr을 설정한다.

  • TTBR1의 asid를 클리어하여 다시 TTBR1에 기록한다.
  • TTBR0에 reserved_ttbr0 라는 이름의 빈 페이지 테이블을 가리키게한다.
    • CONFIG_ARM64_SW_TTBR0_PAN 커널 옵션을 사용하는 경우 swapper_pg_dir 이전에 reserved_ttbr0 라는 이름의 빈 페이지 테이블이 구성되어 있다.

 


유저 도메인의 권한 설정 및 복원 – ARM32

uaccess_save_and_enable()

arch/arm/include/asm/uaccess.h

/*
 * These two functions allow hooking accesses to userspace to increase
 * system integrity by ensuring that the kernel can not inadvertantly
 * perform such accesses (eg, via list poison values) which could then
 * be exploited for priviledge escalation.
 */
static inline unsigned int uaccess_save_and_enable(void)
{
#ifdef CONFIG_CPU_SW_DOMAIN_PAN
        unsigned int old_domain = get_domain();

        /* Set the current domain access to permit user accesses */
        set_domain((old_domain & ~domain_mask(DOMAIN_USER)) |
                   domain_val(DOMAIN_USER, DOMAIN_CLIENT));

        return old_domain;
#else
        return 0;
#endif
}

유저 도메인의 값을 반환하고, 클라이언트 권한(페이지 테이블에 설정된 permission 대로 동작하기)을 부여한다.

 

uaccess_restore()

arch/arm/include/asm/uaccess.h

static inline void uaccess_restore(unsigned int flags)
{
#ifdef CONFIG_CPU_SW_DOMAIN_PAN
        /* Restore the user access mask */
        set_domain(flags);
#endif
}

유저 도메인의 값을 원래 값(flags)으로 복구한다.

 

도메인별 permission 권한 설정하기

set_domain()

arch/arm/include/asm/domain.h

static inline void set_domain(unsigned val)
{
        asm volatile(
        "mcr    p15, 0, %0, c3, c0      @ set domain"
          : : "r" (val) : "memory");
        isb();
}

DACR(Domain Access Control Register)을 사용하여 16개의 도메인을 지정한다.

  • ARM32 커널은 16개의 도메인 중 커널, 유저, IO 및 벡터 도메인을 지정하여 총 4개의 도메인을 사용한다.
  • 각 도메인 마다 2비트를 사용하여 다음과 같은 permission 사용 여부를 지정할 수 있다.
    • 0=no access
      • 항상 permission 에러가 발생한다.
    • 1=client
      • 페이지 변환 테이블에 지정된 permission 비트에 해당하는 체크를 수행한다.
    • 2=reserved
    • 3=manager
      • 페이지 변환 테이블에 지정된 permission 비트를 무시한다.

 

uaccess_mask_range_ptr() 매크로

include/asm/assembler.h

        .macro uaccess_mask_range_ptr, addr:req, size:req, limit:req, tmp:req
#ifdef CONFIG_CPU_SPECTRE
        sub     \tmp, \limit, #1
        subs    \tmp, \tmp, \addr       @ tmp = limit - 1 - addr
        addhs   \tmp, \tmp, #1          @ if (tmp >= 0) {
        subhss  \tmp, \tmp, \size       @ tmp = limit - (addr + size) }
        movlo   \addr, #0               @ if (tmp < 0) addr = NULL
        csdb
#endif
        .endm

 

 

uaccess_save 매크로

arch/arm/include/asm/assembler.h

        .macro  uaccess_save, tmp
#ifdef CONFIG_CPU_SW_DOMAIN_PAN
        mrc     p15, 0, \tmp, c3, c0, 0
        str     \tmp, [sp, #SVC_DACR]
#endif
        .endm

보안을 위해 커널에서 user 영역에 접근을 하지 못하게 제한한다. 그리고 이전 유저 도메인에 대한 도메인 타입을 스택이 가리키고 있는 pt_regs의 멤버 dacr에 백업한다.

 

uaccess_restore 매크로

arch/arm/include/asm/assembler.h

        .macro  uaccess_restore
#ifdef CONFIG_CPU_SW_DOMAIN_PAN
        ldr     r0, [sp, #SVC_DACR]
        mcr     p15, 0, r0, c3, c0, 0
#endif
        .endm

보안을 위해 커널에서 user 영역에 접근에 대한 여부를 기록하는데, 백업해 두었던 스택에 위치한 pt_regs의 멤버 dacr을 읽은 값을 기록한다.

 

uaccess_disable 매크로

arch/arm/include/asm/assembler.h

        .macro  uaccess_disable, tmp, isb=1
#ifdef CONFIG_CPU_SW_DOMAIN_PAN
        /*
         * Whenever we re-enter userspace, the domains should always be
         * set appropriately.
         */
        mov     \tmp, #DACR_UACCESS_DISABLE
        mcr     p15, 0, \tmp, c3, c0, 0         @ Set domain register
        .if     \isb
        instr_sync
        .endif
#endif
        .endm

보안을 위해 커널에서 user 영역에 접근을 하지 못하게 제한한다.

  • 코드 라인 7~8에서 커널에서 user 영역에 접근을 제한한다.
  • 코드 라인 9~11에서 @isb=1인 경우 instruction 베리어를 수행한다.

 

uaccess_enable 매크로

arch/arm/include/asm/assembler.h

        .macro  uaccess_enable, tmp, isb=1
#ifdef CONFIG_CPU_SW_DOMAIN_PAN
        /*
         * Whenever we re-enter userspace, the domains should always be
         * set appropriately.
         */
        mov     \tmp, #DACR_UACCESS_ENABLE
        mcr     p15, 0, \tmp, c3, c0, 0
        .if     \isb
        instr_sync
        .endif
#endif
        .endm

보안을 위해 커널에서 user 영역에 접근을 하지 못하게 제한한 것을 허용하게 한다.

  • 코드 라인 7~8에서 커널에서 user 영역에 접근하도록 허용한다.
  • 코드 라인 9~11에서 @isb=1인 경우 instruction 베리어를 수행한다.

 

DACR_UACCESS_DISABLE

arch/arm/include/asm/domain.h

#define DACR_UACCESS_DISABLE    \
        (__DACR_DEFAULT | domain_val(DOMAIN_USER, DOMAIN_NOACCESS))

유저 도메인의 타입을 noaccess로 지정한다. (커널에서 유저 영역의 액세스 금지)

 

DACR_UACCESS_ENABLE

arch/arm/include/asm/domain.h

#define DACR_UACCESS_DISABLE    \
        (__DACR_DEFAULT | domain_val(DOMAIN_USER, DOMAIN_NOACCESS))
#define DACR_UACCESS_ENABLE     \
        (__DACR_DEFAULT | domain_val(DOMAIN_USER, DOMAIN_CLIENT))

유저 도메인의 타입을 client로 지정한다. (커널에서 유저 영역의 액세스 허용)

 

arch/arm/include/asm/domain.h

#define __DACR_DEFAULT \
        domain_val(DOMAIN_KERNEL, DOMAIN_CLIENT) | \
        domain_val(DOMAIN_IO, DOMAIN_CLIENT) | \
        domain_val(DOMAIN_VECTORS, DOMAIN_CLIENT)

 

#define domain_val(dom,type)    ((type) << (2 * (dom)))

16개의 도메인 중 요청한 도메인 @dom에 도메인 @type을 지정한다.

  • DACR(Domain Access Control Register) 레지스터는 16개의 도메인에 2비트씩 도메인 타입을 지정할 수 있다.

 

도메인 타입

#define DOMAIN_NOACCESS 0
#define DOMAIN_CLIENT   1
#ifdef CONFIG_CPU_USE_DOMAINS
#define DOMAIN_MANAGER  3
#else
#define DOMAIN_MANAGER  1
#endif

 


유효한 유저 프로세스 주소 사용 여부 체크

 

access_ok() – ARM64

arch/arm64/include/asm/uaccess.h

#define access_ok(addr, size)   __range_ok(addr, size)

@addr 주소에서 @size 만큼의 영역이 유효한 유저 프로세스 주소인지 여부를 체크한다. 유효하지 않는 경우 0을 반환한다.

 

__range_ok() – ARM64

arch/arm64/include/asm/uaccess.h

/*
 * Test whether a block of memory is a valid user space address.
 * Returns 1 if the range is valid, 0 otherwise.
 *
 * This is equivalent to the following test:
 * (u65)addr + (u65)size <= (u65)current->addr_limit + 1
 */
static inline unsigned long __range_ok(const void __user *addr, unsigned long size)
{
        unsigned long ret, limit = current_thread_info()->addr_limit;

        __chk_user_ptr(addr);
        asm volatile(
        // A + B <= C + 1 for all A,B,C, in four easy steps:
        // 1: X = A + B; X' = X % 2^64
        "       adds    %0, %3, %2\n"
        // 2: Set C = 0 if X > 2^64, to guarantee X' > C in step 4
        "       csel    %1, xzr, %1, hi\n"
        // 3: Set X' = ~0 if X >= 2^64. For X == 2^64, this decrements X'
        //    to compensate for the carry flag being set in step 4. For
        //    X > 2^64, X' merely has to remain nonzero, which it does.
        "       csinv   %0, %0, xzr, cc\n"
        // 4: For X < 2^64, this gives us X' - C - 1 <= 0, where the -1
        //    comes from the carry in being clear. Otherwise, we are
        //    testing X' - C == 0, subject to the previous adjustments.
        "       sbcs    xzr, %0, %1\n"
        "       cset    %0, ls\n"
        : "=&r" (ret), "+r" (limit) : "Ir" (size), "0" (addr) : "cc");

        return ret;
}

 

access_ok() – ARM32

arch/arm/include/asm/uaccess.h

#define access_ok(addr, size)     (__range_ok(addr, size) == 0)

@addr 주소에서 @size 만큼의 영역이 유효한 유저 프로세스 주소인지 여부를 체크한다. 유효하지 않는 경우 0을 반환한다.

 

__range_ok() – ARM32

arch/arm/include/asm/uaccess.h

/* We use 33-bit arithmetic here... */
#define __range_ok(addr, size) ({ \
        unsigned long flag, roksum; \
        __chk_user_ptr(addr);   \
        __asm__("adds %1, %2, %3; sbcccs %1, %1, %0; movcc %0, #0" \
                : "=&r" (flag), "=&r" (roksum) \
                : "r" (addr), "Ir" (size), "0" (current_thread_info()->addr_limit) \
                : "cc"); \
        flag; })

 


커널에서 유저 영역의 값(1, 2, 4, 8 바이트) 읽어오기

get_user() API는 아키텍처별로 약간의 다른 구현을 사용한다.

  • arm64
    • exception 테이블을 활용하여 읽기를 시도한다.
  • arm32
    • MMU를 사용하는 ARMv7 아키텍처에 한해 exception 테이블 사용없이 미리 체크를 한 후 읽기를 시도한다.
    • MMU를 사용하지 않는 경우 exception 테이블을 활용하여 읽기를 시도한다.

 

get_user() – ARM64

arch/arm64/include/asm/uaccess.h

#define get_user        __get_user

커널에서 유저 영역의 주소 @p에 담긴 데이터를 @p 타입 사이즈만큼 읽어 커널 영역의 주소 @x에 대입한다. 만일 읽을 수 없으면 -EFAULT 에러를 반환한다.

 

__get_user() – ARM64

arch/arm64/include/asm/uaccess.h

#define __get_user(x, ptr)                                              \
({                                                                      \
        int __gu_err = 0;                                               \
        __get_user_check((x), (ptr), __gu_err);                         \
        __gu_err;                                                       \
})

 

__get_user_check() – ARM64

arch/arm64/include/asm/uaccess.h

#define __get_user_check(x, ptr, err)                                   \
({                                                                      \
        __typeof__(*(ptr)) __user *__p = (ptr);                         \
        might_fault();                                                  \
        if (access_ok(__p, sizeof(*__p))) {                             \
                __p = uaccess_mask_ptr(__p);                            \
                __get_user_err((x), __p, (err));                        \
        } else {                                                        \
                (x) = 0; (err) = -EFAULT;                               \
        }                                                               \
})

 

__get_user_err() – ARM64

arch/arm64/include/asm/uaccess.h

#define __get_user_err(x, ptr, err)                                     \
do {                                                                    \
        unsigned long __gu_val;                                         \
        __chk_user_ptr(ptr);                                            \
        uaccess_enable_not_uao();                                       \
        switch (sizeof(*(ptr))) {                                       \
        case 1:                                                         \
                __get_user_asm("ldrb", "ldtrb", "%w", __gu_val, (ptr),  \
                               (err), ARM64_HAS_UAO);                   \
                break;                                                  \
        case 2:                                                         \
                __get_user_asm("ldrh", "ldtrh", "%w", __gu_val, (ptr),  \
                               (err), ARM64_HAS_UAO);                   \
                break;                                                  \
        case 4:                                                         \
                __get_user_asm("ldr", "ldtr", "%w", __gu_val, (ptr),    \
                               (err), ARM64_HAS_UAO);                   \
                break;                                                  \
        case 8:                                                         \
                __get_user_asm("ldr", "ldtr", "%x",  __gu_val, (ptr),   \
                               (err), ARM64_HAS_UAO);                   \
                break;                                                  \
        default:                                                        \
                BUILD_BUG();                                            \
        }                                                               \
        uaccess_disable_not_uao();                                      \
        (x) = (__force __typeof__(*(ptr)))__gu_val;                     \
} while (0)

 

__get_user_asm() – ARM64

arch/arm64/include/asm/uaccess.h

/*
 * The "__xxx" versions of the user access functions do not verify the address
 * space - it must have been done previously with a separate "access_ok()"
 * call.
 *
 * The "__xxx_error" versions set the third argument to -EFAULT if an error
 * occurs, and leave it unchanged on success.
 */
#define __get_user_asm(instr, alt_instr, reg, x, addr, err, feature)    \
        asm volatile(                                                   \
        "1:"ALTERNATIVE(instr "     " reg "1, [%2]\n",                  \
                        alt_instr " " reg "1, [%2]\n", feature)         \
        "2:\n"                                                          \
        "       .section .fixup, \"ax\"\n"                              \
        "       .align  2\n"                                            \
        "3:     mov     %w0, %3\n"                                      \
        "       mov     %1, #0\n"                                       \
        "       b       2b\n"                                           \
        "       .previous\n"                                            \
        _ASM_EXTABLE(1b, 3b)                                            \
        : "+r" (err), "=&r" (x)                                         \
        : "r" (addr), "i" (-EFAULT))

 

get_user() – ARM32

arch/arm/include/asm/uaccess.h

#define get_user(x, p)                                                  \
        ({                                                              \
                might_fault();                                          \
                __get_user_check(x, p);                                 \
         })

커널에서 유저 영역의 주소 @p에 담긴 데이터를 @p 타입 사이즈만큼 읽어 커널 영역의 주소 @x에 대입한다. 만일 읽을 수 없으면 -EFAULT 에러를 반환한다.

 

__get_user_check() – ARM32

arch/arm/include/asm/uaccess.h

#define __get_user_check(x, p)                                          \
        ({                                                              \
                unsigned long __limit = current_thread_info()->addr_limit - 1; \
                register typeof(*(p)) __user *__p asm("r0") = (p);      \
                register __inttype(x) __r2 asm("r2");                   \
                register unsigned long __l asm("r1") = __limit;         \
                register int __e asm("r0");                             \
                unsigned int __ua_flags = uaccess_save_and_enable();    \
                switch (sizeof(*(__p))) {                               \
                case 1:                                                 \
                        if (sizeof((x)) >= 8)                           \
                                __get_user_x_64t(__r2, __p, __e, __l, 1); \
                        else                                            \
                                __get_user_x(__r2, __p, __e, __l, 1);   \
                        break;                                          \
                case 2:                                                 \
                        if (sizeof((x)) >= 8)                           \
                                __get_user_x_64t(__r2, __p, __e, __l, 2); \
                        else                                            \
                                __get_user_x(__r2, __p, __e, __l, 2);   \
                        break;                                          \
                case 4:                                                 \
                        if (sizeof((x)) >= 8)                           \
                                __get_user_x_64t(__r2, __p, __e, __l, 4); \
                        else                                            \
                                __get_user_x(__r2, __p, __e, __l, 4);   \
                        break;                                          \
                case 8:                                                 \
                        if (sizeof((x)) < 8)                            \
                                __get_user_x_32t(__r2, __p, __e, __l, 4); \
                        else                                            \
                                __get_user_x(__r2, __p, __e, __l, 8);   \
                        break;                                          \
                default: __e = __get_user_bad(); break;                 \
                }                                                       \
                uaccess_restore(__ua_flags);                            \
                x = (typeof(*(p))) __r2;                                \
                __e;                                                    \
        })

 

__get_user_x() – ARM32

arch/arm/include/asm/uaccess.h

#define __get_user_x(__r2, __p, __e, __l, __s)                          \
           __asm__ __volatile__ (                                       \
                __asmeq("%0", "r0") __asmeq("%1", "r2")                 \
                __asmeq("%3", "r1")                                     \
                "bl     __get_user_" #__s                               \
                : "=&r" (__e), "=r" (__r2)                              \
                : "0" (__p), "r" (__l)                                  \
                : __GUP_CLOBBER_##__s)

사이즈(__s)에 따라 커널이 유저 영역을 읽는 함수를 호출한다.

  • 1, 2, 4, 8 바이트에 따른 호출함수가 라이브러리에 따로 준비되어 있다.

 

__get_user_1() – ARM32

arch/arm/lib/getuser.S

ENTRY(__get_user_1)
        check_uaccess r0, 1, r1, r2, __get_user_bad
1: TUSER(ldrb)  r2, [r0]
        mov     r0, #0
        ret     lr
ENDPROC(__get_user_1)
_ASM_NOKPROBE(__get_user_1)

커널에서 유저 영역의 1바이트를 읽어낸다. 만일 읽을 수 없으면 -EFAULT 에러를 반환한다.

 

check_uaccess() 매크로 – ARM32

arch/arm/include/asm/assembler.h

        .macro check_uaccess, addr:req, size:req, limit:req, tmp:req, bad:req
#ifndef CONFIG_CPU_USE_DOMAINS
        adds    \tmp, \addr, #\size - 1
        sbcccs  \tmp, \tmp, \limit
        bcs     \bad
#ifdef CONFIG_CPU_SPECTRE
        movcs   \addr, #0
        csdb
#endif
#endif
        .endm

커널에서 유저 데이터 엑세스에 문제가 없는지 확인한다. 에러 발생 시 -EFAULT를 반환한다.

  • MMU를 enable 시킨 ARMv7 아키텍처를 사용하면 CONFIG_CPU_SPECTRE 커널 옵션을 디폴트로 사용한다.
  • csdb 는 Consumption of Speculative Data Barrier를 의미한다.

 

__get_user_bad() & __get_user_bad8() – ARM32

arch/arm/lib/getuser.S

__get_user_bad8:
        mov     r3, #0
__get_user_bad:
        mov     r2, #0
        mov     r0, #-EFAULT
        ret     lr
ENDPROC(__get_user_bad)
ENDPROC(__get_user_bad8)
_ASM_NOKPROBE(__get_user_bad)
_ASM_NOKPROBE(__get_user_bad8)

커널에서 유저 데이터 액세스에 문제가 발생했을 때 처리할 함수이다. -EFAULT 에러를 r0 레지스터를 통해 반환한다.

 

참고

커널 스터디 참고 사이트

국내

 

해외

 

추천 도서

  • 디버깅을 통해 배우는 리눅스 커널의 구조와 원리
    • 디버깅을 통해 리눅스 커널을 분석할 때 이해하기 쉽도록 2권으로 구성된 도서, 난이도: 초~중
    • 저자: Linux Kernel(4.19) Hacks 운영자(김동현)

  • 시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리

 

ARM64 아키텍처 참고 PDF 문서

 

추천 ARM64 하드웨어