CVE-2015-8966: Permissions, Privileges, and Access Controls (CWE-264)
value: 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.
CWE description: Weaknesses in this category are related to the management of permissions, privileges, and other security features that are used to perform access control.
CVSS v3 base score: 7.8
Published: 2016-12-08T21:59Z
Repository: https://github.com/torvalds/linux
Language: C
Files changed: arch/arm/kernel/sys_oabi-compat.c
Commit date: 2015-12-29 13:04:21 -0500
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>

Diff: diff --git a/arch/arm/kernel/sys_oabi-compat.c b/arch/arm/kernel/sys_oabi-compat.c
index b83f3b7737fb9d..087acb569b63a4 100644
--- a/arch/arm/kernel/sys_oabi-compat.c
+++ b/arch/arm/kernel/sys_oabi-compat.c
@@ -191,59 +191,60 @@ struct oabi_flock64 {
 	loff_t	l_start;
 	loff_t	l_len;
 	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 {
 	__u32 events;
 	__u64 data;

Vulnerable code: asmlinkage long sys_oabi_fcntl64(unsigned int fd, unsigned int cmd,
	struct oabi_flock64 user;
	mm_segment_t fs = USER_DS; /* initialized to kill a warning */
	unsigned long local_arg = arg;
	int 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;
		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;
Fixed code: static long do_locks(unsigned int fd, unsigned int cmd,
	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)
{
		return do_locks(fd, cmd, arg);
	default:
		return sys_fcntl64(fd, cmd, arg);