Vulnerability: arch/arm/kernel/sys_oabi-compat.c in the Linux kernel before 4.4 allows local users to gain privileges via a crafted (1) F_OFD_GETLK, (2) F_OFD_SETLK, or (3) F_OFD_SETLKW command in an fcntl64 system call.
Vulnerability ID: CVE-2015-8966
CWE: CWE-264
Severity score: 10
Project: https://github.com/torvalds/linux
Published: 2016-12-08
File: arch/arm/kernel/sys_oabi-compat.c
Commit message: [PATCH] arm: fix handling of F_OFD_... in oabi_fcntl64()

Cc: stable@vger.kernel.org # 3.15+
Reviewed-by: Jeff Layton <jeff.layton@primarydata.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Commit author: Al Viro

Vulnerable version (before fix):
 struct oabi_flock64 { 	pid_t	l_pid; } __attribute__ ((packed,aligned(4)));  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); 	}  	ret = sys_fcntl64(fd, cmd, local_arg);  	switch (cmd) { 	case F_GETLK64: 		if (!ret) { 			user.l_type	= kernel.l_type; 			user.l_whence	= kernel.l_whence; 			user.l_start	= kernel.l_start; 			user.l_len	= kernel.l_len; 			user.l_pid	= kernel.l_pid; 			if (copy_to_user((struct oabi_flock64 __user *)arg, 					 &user, sizeof(user))) 				ret = -EFAULT; 		} 	case F_SETLK64: 	case F_SETLKW64: 		set_fs(fs); 	}  	return ret; }  struct oabi_epoll_event {

Fixed version (after fix):
 struct oabi_flock64 { 	pid_t	l_pid; } __attribute__ ((packed,aligned(4)));  static long do_locks(unsigned int fd, unsigned int cmd, 				 unsigned long arg) { 	struct flock64 kernel; 	struct oabi_flock64 user; 	mm_segment_t fs; 	long ret;  	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;  	fs = get_fs(); 	set_fs(KERNEL_DS); 	ret = sys_fcntl64(fd, cmd, (unsigned long)&kernel); 	set_fs(fs);  	if (!ret && (cmd == F_GETLK64 || cmd == F_OFD_GETLK)) { 		user.l_type	= kernel.l_type; 		user.l_whence	= kernel.l_whence; 		user.l_start	= kernel.l_start; 		user.l_len	= kernel.l_len; 		user.l_pid	= kernel.l_pid; 		if (copy_to_user((struct oabi_flock64 __user *)arg, 				 &user, sizeof(user))) 			ret = -EFAULT; 	} 	return ret; }  asmlinkage long sys_oabi_fcntl64(unsigned int fd, unsigned int cmd, 				 unsigned long arg) { 	switch (cmd) { 	case F_OFD_GETLK: 	case F_OFD_SETLK: 	case F_OFD_SETLKW: 	case F_GETLK64: 	case F_SETLK64: 	case F_SETLKW64: 		return do_locks(fd, cmd, arg);  	default: 		return sys_fcntl64(fd, cmd, arg); 	} }  struct oabi_epoll_event {

Patch:
patch: @@ -193,55 +193,56 @@ struct oabi_flock64 {
 	pid_t	l_pid;
 } __attribute__ ((packed,aligned(4)));
 
-asmlinkage long sys_oabi_fcntl64(unsigned int fd, unsigned int cmd,
+static long do_locks(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;
+	struct oabi_flock64 user;
+	mm_segment_t fs;
+	long ret;
+
+	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;
+
+	fs = get_fs();
+	set_fs(KERNEL_DS);
+	ret = sys_fcntl64(fd, cmd, (unsigned long)&kernel);
+	set_fs(fs);
+
+	if (!ret && (cmd == F_GETLK64 || cmd == F_OFD_GETLK)) {
+		user.l_type	= kernel.l_type;
+		user.l_whence	= kernel.l_whence;
+		user.l_start	= kernel.l_start;
+		user.l_len	= kernel.l_len;
+		user.l_pid	= kernel.l_pid;
+		if (copy_to_user((struct oabi_flock64 __user *)arg,
+				 &user, sizeof(user)))
+			ret = -EFAULT;
+	}
+	return ret;
+}
 
+asmlinkage long sys_oabi_fcntl64(unsigned int fd, unsigned int cmd,
+				 unsigned long arg)
+{
 	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);
-	}
-
-	ret = sys_fcntl64(fd, cmd, local_arg);
+		return do_locks(fd, cmd, arg);
 
-	switch (cmd) {
-	case F_GETLK64:
-		if (!ret) {
-			user.l_type	= kernel.l_type;
-			user.l_whence	= kernel.l_whence;
-			user.l_start	= kernel.l_start;
-			user.l_len	= kernel.l_len;
-			user.l_pid	= kernel.l_pid;
-			if (copy_to_user((struct oabi_flock64 __user *)arg,
-					 &user, sizeof(user)))
-				ret = -EFAULT;
-		}
-	case F_SETLK64:
-	case F_SETLKW64:
-		set_fs(fs);
+	default:
+		return sys_fcntl64(fd, cmd, arg);
 	}
-
-	return ret;
 }
 
 struct oabi_epoll_event {
path: arch/arm/kernel/sys_oabi-compat.c