Demon 시즌2/linux kernel exploitation

[linux kernel] (4) - Slab Allocator(슬랩 할당자)

jir4vvit 2020. 12. 26. 19:38
더보기

20.12.28 슬랩 캐시에 대한 내용 추가

 

만약 kmalloc을 이용해 커널이 16byte를 할당받으려고 한다고 하자.

page의 단위가 4kb이기 때문에, 고작 16바이트를 위해 4k바이트를 할당해야 할까?

그렇다면 나머지 4096 - 16 = 4080byte는 어떻게 될까? 4080byte는 사용되지 못한 채 방치되며 심한 메모리 낭비가 발생한다. 

 

이를 막기 위한 방법이 바로 Slab Allocator(슬랩 할당자)이다. 

 

슬랩 할당자는 미리 할당해 놓은 작은 메모리 조각을 kmalloc의 요청에 따라 요청한 양에 가장 가까운 메모리 조각을 반환해 준다. 

예를 들어, 16byte를 요청했을 경우 가장 가까운 slab의 조각 32byte를 사용할 수 있도록 제공(응답)해준다. 

 

Slab Allocator(슬랩 할당자)란? 

'슬랩 할당자'는 커널에서 사용하는 동적 메모리 할당자이다.

메모리 풀 구조를 가지고 있어서 미리 고정된 크기의 메모리 블록들을 할당해 놓는다.

 

커널은 Slab, Slub, Slob 중 하나를 선택하여 빌드되어 사용되는데, 구조 및 소스 분석은 현재 default로 사용되는 Slub 구현만 분석할 예정이다. Slab, Slub, Slob을 구분하지 않고 설명할 때에는 한글로 슬랩이라고 표현한다고 한다.

 

Slab, Slub, Slob

  • Slab
    - 2007~2008년까지 default로 사용됨
    - 처음 생성된 slab은 full 리스트에서 관리되다가 object가 하나라도 사용되는 경우, 그 slab은 partial 리스트로 이동되어 관리됨. 다 사용하는 경우 다시 이동되어 empty 리스트에서 관리됨
  • Slub
    - 현재까지 defualt로 사용됨
    - 처음 생성된 slub은 object 사용 개수가 0으로 시작하고 partial 리스트로만 관리됨

  • Slob 
    - 적은 메모리 자원을 가진 시스템에서 사용됨
    - 속도는 느리지만 메모리 소모가 가장 적음

 

슬랩 구조

  • 슬랩 cache : 커널에서 자주 요청하는 크기에 대한 동적 메모리를 미리 확보하고 관리하는 주체
  • 슬랩 object : 슬랩 cache가 할당해 놓은 메모리 블록 (유저모드에서 사용되는 malloc()의 chunk와 비슷한 개념)
  • 슬랩 page : 슬랩 object로 구성된 page

Alloc이라고 되어 있는 슬랩 object들은 할당이 되어 있는 상태이고, Free라고 되어 있는 슬랩 object들은 해제되어 있는 상태이다.

이때, Free된 슬랩 object들은 freelist라는 리스트 자료구조로 관리된다. (유저공간에서 free된 chunck들이 bins 리스트로 관리되는 것과 비슷한 개념이다.) 위 그림을 보면 freelist가 첫 free 슬랩 object를 가리키고, 각 free object끼리 순서대로 연결되어 있다.

참고로 page의 단위는 4kb 이다. 

 

슬랩 캐시

 

 

 

 

1개 이상의 슬랩 page가 모여 슬랩 cache가 구성된다.

즉, 슬랩 cache 내의 모든 object 들은 동일한 사이즈만을 제공한다.

 

 

 

 

 

kmalloc 슬랩 캐시

대부분 디바이스 드라이버에서는 kmalloc 슬랩 캐시를 사용해 동적 메모리를 할당한다.

kmalloc() 함수를 호출하면 커널 내부에서는 kmalloc 슬랩 캐시를 사용한다.

 

커널에서 kmalloc() 함수에 대한 슬랩 캐시를 제공하는데 이름은 아래와 같다.

  • "kmalloc-64"
  • "kmalloc-128" 
  • "kmalloc-192"
  • "kmalloc-256"
  • "kmalloc-512"
  • "kmalloc-1024"
  • "kmalloc-2048"
  • "kmalloc-4096"
  • "kmalloc-8192"

각 슬랩 캐시 이름과 같이 메모리 크기만큼 동적 메모리를 미리 할당해 놨다.

 

예를 들어 "kmalloc-64" 슬랩 캐시는 64바이트의 slub object로 구성되어 있다. 이미 할당 받은 64바이트 단위의 메모리 블록이 "kmalloc-64" slub object이다.

 

kmalloc() 함수를 호출하여 동적 메모리를 54바이트 크기로 할당을 받으려고 한다. 이 때 커널은 "kmalloc-64" 슬랩 캐시를 지정한다. "kmalloc-64" 슬랩 캐시가 미리 할당해 놓은 64바이트 사이즈의 slub object를 할당해준다.

그럼 나머지 10바이트는 어떻게 될까? 나머지 10바이트는 버리게 된다. 동적 메모리 할당 속도를 위해 어느 정도 메모리 파편화가 생기게 된다.

 

kmalloc() 함수를 호출하여 동적 메모리를 100바이트 크기로 할당받으려고 한다. 이 때 커널은 메모리 할당을 요청한 사이즈보다 큰 "kmalloc-128" 슬랩 캐시에서 128바이트 만큼의 slub object를 할당해 준다. 이번에도 28바이트는 못 쓰게 된다. 

 

per-node 및 per-cpu 관리 지원

  • per-node
    - 노드별로 메모리 접근 속도가 다르므로 슬랩 page들을 노드별로 나눠 관리함

  • per-cpu
    - 빠른 슬랩 cache 할당을 위해 cpu 별로 나누어 관리함.
    - per-cpu 슬랩 캐시에는 partial 리스트와 1개의 page가 지정됨.

 

구조체

kmem_cache 구조체

struct kmem_cache {
        struct kmem_cache_cpu __percpu *cpu_slab;
        /* Used for retriving partial slabs etc */
        slab_flags_t flags;
        unsigned long min_partial;
        unsigned int size;      /* The size of an object including meta data */
        unsigned int object_size;/* The size of an object without meta data */
        unsigned int offset;    /* Free pointer offset. */
#ifdef CONFIG_SLUB_CPU_PARTIAL
        /* Number of per cpu partial objects to keep around */
        unsigned int cpu_partial;
#endif
        struct kmem_cache_order_objects oo;

        /* Allocation and freeing of slabs */
        struct kmem_cache_order_objects max;
        struct kmem_cache_order_objects min;
        gfp_t allocflags;       /* gfp flags to use on each alloc */
        int refcount;           /* Refcount for slab cache destroy */
        void (*ctor)(void *);
        unsigned int inuse;             /* Offset to metadata */
        unsigned int align;             /* Alignment */
        unsigned int red_left_pad;      /* Left redzone padding size */
        const char *name;       /* Name (only for display!) */
        struct list_head list;  /* List of slab caches */
#ifdef CONFIG_SYSFS
        struct kobject kobj;    /* For sysfs */
        struct work_struct kobj_remove_work;
#endif
#ifdef CONFIG_MEMCG
        struct memcg_cache_params memcg_params;
        /* for propagation, maximum size of a stored attr */
        unsigned int max_attr_size;
#ifdef CONFIG_SYSFS
        struct kset *memcg_kset;
#endif
#endif

#ifdef CONFIG_SLAB_FREELIST_HARDENED
        unsigned long random;
#endif

#ifdef CONFIG_NUMA
        /*
         * Defragmentation by allocating from a remote node.
         */
        unsigned int remote_node_defrag_ratio;
#endif

#ifdef CONFIG_SLAB_FREELIST_RANDOM
        unsigned int *random_seq;
#endif

#ifdef CONFIG_KASAN
        struct kasan_cache kasan_info;
#endif

        unsigned int useroffset;        /* Usercopy region offset */
        unsigned int usersize;          /* Usercopy region size */

        struct kmem_cache_node *node[MAX_NUMNODES];
};

슬랩 캐시를 관리한다.

 

kmem_cache_cpu 구조체

struct kmem_cache_cpu {
        void **freelist;        /* Pointer to next available object */
        unsigned long tid;      /* Globally unique transaction id */
        struct page *page;      /* The slab from which we are allocating */
#ifdef CONFIG_SLUB_CPU_PARTIAL
        struct page *partial;   /* Partially allocated frozen slabs */
#endif
#ifdef CONFIG_SLUB_STATS
        unsigned stat[NR_SLUB_STAT_ITEMS];
#endif
};

per-cpu로 관리되는 슬랩 캐시

**freelist 아래 page 멤버 중 할당 가능한 free object를 가리키는 포인터
tid 글로벌하게 유니크한 트랜잭션 id
*page 할당/해제에 사용 중인 슬랩 캐시 페이지
*partial 일부 object가 사용(alloc)된 frozen 슬랩 페이지 리스트
stat 슬랩 캐시 통계

슬랩 페이지의 fozen 상태 : 슬랩 페이지가 특정 cpu가 전용으로 사용할 수 있는 상태

 

슬랩 object가 할당되는 과정

kmalloc() 함수를 호출하면 동적 메모리를 할당할 수 있다. 

kmalloc()

static __always_inline void *kmalloc(size_t size, gfp_t flags)
{
        if (__builtin_constant_p(size)) {
#ifndef CONFIG_SLOB
                unsigned int index;
#endif
                if (size > KMALLOC_MAX_CACHE_SIZE)
                        return kmalloc_large(size, flags);
#ifndef CONFIG_SLOB
                index = kmalloc_index(size);

                if (!index)
                        return ZERO_SIZE_PTR;

                return kmem_cache_alloc_trace(
                                kmalloc_caches[kmalloc_type(flags)][index],
                                flags, size);
#endif
        }
        return __kmalloc(size, flags);
}

kmalloc_index() 함수를 호출해 요청한 크기에 해당하는 kmalloc_caches[] 전역 배열의 인덱스를 선택한다.

그 인덱스를 인자로 kmem_cache_alloc_trace() 함수를 호출한다

  • kmalloc_caches[index]: kmalloc 슬랩 캐시 인덱스인 index로 kmalloc_caches 배열 인덱스 값을 전달함. 타입은 struct kmem_cache 구조체임
  • flags: kmalloc() 함수 호출 시 플래그 
  • size: 메모리 할당 사이즈 

 

kmem_cache_alloc_trace()

void *kmem_cache_alloc_trace(struct kmem_cache *s, gfp_t gfpflags, size_t size)
{
	void *ret = slab_alloc(s, gfpflags, _RET_IP_);
	trace_kmalloc(_RET_IP_, ret, size, s->size, gfpflags);
	kasan_kmalloc(s, ret, size, gfpflags);
	return ret;
}

slab_alloc() 함수를 호출하여 포인터형 반환값을 ret에 저장하고 반환한다.

결론적으로, 이 함수는 슬랩 object를 할당한다.

 

slab_alloc()

static __always_inline void *slab_alloc(struct kmem_cache *s,
		gfp_t gfpflags, unsigned long addr)
{
	return slab_alloc_node(s, gfpflags, NUMA_NO_NODE, addr);
}	  

slab_alloc() 함수는 특별한 동작은 하지 않고, slab_alloc_node() 함수를 호출한 후 결과를 반환한다. 

 

slab_alloc_node()

1. kmem_cache_cpu 구조체로 관리되는 per-cpu 슬럽 캐시를 로딩

2. per-cpu 슬랩 캐시 필드 중 struct page 타입인 page에 접근 

3. 슬랩 오브젝트 할당

  3.1 kmem_cache_cpu의 freelist에 해제된 슬랩 객체가 있으면?

     ; 해당 슬랩 object를 최종적으로 반환함

  3.2 kmem_cache_cpu의 freelist에 NULL이 있으면?

     ; __slab_alloc() 함수를 호출하여 슬랩 page를 새로 할당받고 슬랩 object를 반환함 

 

 

 

 

 

*ref

더보기

m.blog.naver.com/PostView.nhn?blogId=loveall0926&logNo=220036029251&proxyReferer=https:%2F%2Fwww.google.com%2F

egloos.zum.com/rousalome/v/10002815

jake.dothome.co.kr/slub/

egloos.zum.com/rousalome/v/10002660