[glibc 2.34] ptmalloc2 분석(8)
개요
이전 포스터에 이어서 glibc 2.34
버전에서 패치된 사항을 분석하고자 합니다. 새로운 개념이 생긴것은 아니지만 보호기법이 많이 추가되어서 포스터를 작성합니다.
- [glibc 2.23] malloc 분석(1)
- [glibc 2.26] malloc 분석(2)
- [glibc 2.27] malloc 분석(3)
- [glibc 2.29] malloc 분석(4)
- [glibc 2.30] malloc 분석(5)
- [glibc 2.32] malloc 분석(6)
- [glibc 2.33] malloc 분석(7)
hook 삭제
__libc_malloc
함수와 __libc_free
함수를 보면 다음과 같이 hook
이 삭제되어 __libc_malloc
함수는 아래와 같이 변경이 되었고 __libc_free
는 삭제만 되었습니다.
1
2
if (!__malloc_initialized)
ptmalloc_init ();
💡 따라서 해당 glibc 버전부터 hook overwrite가 불가능해집니다.
tcache key 암호화
glibc 2.29
버전부터 생겼던 tcache key
에서 변화가 생겼습니다. 우선 기존의 코드는 다음과 같습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* Caller must ensure that we know tc_idx is valid and there's room
for more chunks. */
static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
/* Mark this chunk as "in the tcache" so the test in _int_free will
detect a double free. */
e->key = tcache;
e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]);
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}
/* Caller must ensure that we know tc_idx is valid and there's
available chunks to remove. */
static __always_inline void *
tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
if (__glibc_unlikely (!aligned_OK (e)))
malloc_printerr ("malloc(): unaligned tcache chunk detected");
tcache->entries[tc_idx] = REVEAL_PTR (e->next);
--(tcache->counts[tc_idx]);
e->key = NULL;
return (void *) e;
}
기존에는 key
멤버에 그냥 tcache
를 할당하는 것을 확인할 수 있습니다. 하지만 glibc 2.34 버전부터 아래와 같이 변경되었습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/* Process-wide key to try and catch a double-free in the same thread. */
static uintptr_t tcache_key;
/* The value of tcache_key does not really have to be a cryptographically
secure random number. It only needs to be arbitrary enough so that it does
not collide with values present in applications. If a collision does happen
consistently enough, it could cause a degradation in performance since the
entire list is checked to check if the block indeed has been freed the
second time. The odds of this happening are exceedingly low though, about 1
in 2^wordsize. There is probably a higher chance of the performance
degradation being due to a double free where the first free happened in a
different thread; that's a case this check does not cover. */
static void
tcache_key_initialize (void)
{
if (__getrandom (&tcache_key, sizeof(tcache_key), GRND_NONBLOCK)
!= sizeof (tcache_key))
{
tcache_key = random_bits ();
#if __WORDSIZE == 64
tcache_key = (tcache_key << 32) | random_bits ();
#endif
}
}
/* Caller must ensure that we know tc_idx is valid and there's room
for more chunks. */
static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
/* Mark this chunk as "in the tcache" so the test in _int_free will
detect a double free. */
e->key = tcache_key;
e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]);
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}
/* Caller must ensure that we know tc_idx is valid and there's
available chunks to remove. */
static __always_inline void *
tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
if (__glibc_unlikely (!aligned_OK (e)))
malloc_printerr ("malloc(): unaligned tcache chunk detected");
tcache->entries[tc_idx] = REVEAL_PTR (e->next);
--(tcache->counts[tc_idx]);
e->key = 0;
return (void *) e;
}
코드는 길지만 변경된 사항은 key
에 할당하는 값이 tcache
가 아닌 tcache_key
로 변경되었습니다. 여기서 tcache_key
변수는 tcache_key_initialize
함수에서 생성하고 있는데 랜덤한 값을 할당하는 것을 확인할 수 있습니다.
Memory Tagging
마지막으로 Memory Tagging
에 대해서도 변경사항이 생겼습니다. 기존에는 아래와 같이 구현되어 있어서 아무 기능도 작동되지 않았습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifdef USE_MTAG
/* Default implementaions when memory tagging is supported, but disabled. */
static void *
__default_tag_region (void *ptr, size_t size)
{
return ptr;
}
static void *
__default_tag_nop (void *ptr)
{
return ptr;
}
하지만, glibc 2.34
버전부터 아래와 같이 패치되었습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#ifdef USE_MTAG
static bool mtag_enabled = false;
static int mtag_mmap_flags = 0;
#else
# define mtag_enabled false
# define mtag_mmap_flags 0
#endif
static __always_inline void *
tag_region (void *ptr, size_t size)
{
if (__glibc_unlikely (mtag_enabled))
return __libc_mtag_tag_region (ptr, size);
return ptr;
}
static __always_inline void *
tag_new_zero_region (void *ptr, size_t size)
{
if (__glibc_unlikely (mtag_enabled))
return __libc_mtag_tag_zero_region (__libc_mtag_new_tag (ptr), size);
return memset (ptr, 0, size);
}
/* Defined later. */
static void *
tag_new_usable (void *ptr);
static __always_inline void *
tag_at (void *ptr)
{
if (__glibc_unlikely (mtag_enabled))
return __libc_mtag_address_get_tag (ptr);
return ptr;
}
...
/* This is the size of the real usable data in the chunk. Not valid for
dumped heap chunks. */
#define memsize(p) \
(__MTAG_GRANULE_SIZE > SIZE_SZ && __glibc_unlikely (mtag_enabled) ? \
chunksize (p) - CHUNK_HDR_SZ : \
chunksize (p) - CHUNK_HDR_SZ + (chunk_is_mmapped (p) ? 0 : SIZE_SZ))
/* If memory tagging is enabled the layout changes to accommodate the granule
size, this is wasteful for small allocations so not done by default.
Both the chunk header and user data has to be granule aligned. */
_Static_assert (__MTAG_GRANULE_SIZE <= CHUNK_HDR_SZ,
"memory tagging is not supported with large granule.");
static __always_inline void *
tag_new_usable (void *ptr)
{
if (__glibc_unlikely (mtag_enabled) && ptr)
{
mchunkptr cp = mem2chunk(ptr);
ptr = __libc_mtag_tag_region (__libc_mtag_new_tag (ptr), memsize (cp));
}
return ptr;
}
위의 코드와 같이 Memory Tagging
을 지원하기 위해서 다양한 함수가 구현된 것을 확인할 수 있습니다.
결론
glibc 2.34
에서 패치된 내용은 전부 보호기법과 관련된 내용이 패치 된 것을 확인할 수 있습니다.