Vulnerability: Race condition in the sclp_ctl_ioctl_sccb function in drivers/s390/char/sclp_ctl.c in the Linux kernel before 4.6 allows local users to obtain sensitive information from kernel memory by changing a certain length value, aka a "double fetch" vulnerability.
Vulnerability ID: CVE-2016-6130
CWE: CWE-362
Severity score: 2.9
Project: https://github.com/torvalds/linux
Published: 2016-07-03
File: drivers/s390/char/sclp_ctl.c
Commit message: s390/sclp_ctl: fix potential information leak with /dev/sclp

The sclp_ctl_ioctl_sccb function uses two copy_from_user calls to
retrieve the sclp request from user space. The first copy_from_user
fetches the length of the request which is stored in the first two
bytes of the request. The second copy_from_user gets the complete
sclp request, but this copies the length field a second time.
A malicious user may have changed the length in the meantime.

Reported-by: Pengfei Wang <wpengfeinudt@gmail.com>
Reviewed-by: Michael Holzheu <holzheu@linux.vnet.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Commit author: Martin Schwidefsky

Vulnerable version (before fix):
 static int sclp_ctl_ioctl_sccb(void __user *user_area) { 	struct sclp_ctl_sccb ctl_sccb; 	struct sccb_header *sccb; 	int rc;  	if (copy_from_user(&ctl_sccb, user_area, sizeof(ctl_sccb))) static int sclp_ctl_ioctl_sccb(void __user *user_area) 	sccb = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA); 	if (!sccb) 		return -ENOMEM; 	if (copy_from_user(sccb, u64_to_uptr(ctl_sccb.sccb), sizeof(*sccb))) { 		rc = -EFAULT; 		goto out_free; 	} 	if (sccb->length > PAGE_SIZE || sccb->length < 8) 		return -EINVAL; 	if (copy_from_user(sccb, u64_to_uptr(ctl_sccb.sccb), sccb->length)) { 		rc = -EFAULT; 		goto out_free; 	} 	rc = sclp_sync_request(ctl_sccb.cmdw, sccb);

Fixed version (after fix):
 static int sclp_ctl_ioctl_sccb(void __user *user_area) { 	struct sclp_ctl_sccb ctl_sccb; 	struct sccb_header *sccb; 	unsigned long copied; 	int rc;  	if (copy_from_user(&ctl_sccb, user_area, sizeof(ctl_sccb))) static int sclp_ctl_ioctl_sccb(void __user *user_area) 	sccb = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA); 	if (!sccb) 		return -ENOMEM; 	copied = PAGE_SIZE - 		copy_from_user(sccb, u64_to_uptr(ctl_sccb.sccb), PAGE_SIZE); 	if (offsetof(struct sccb_header, length) + 	    sizeof(sccb->length) > copied || sccb->length > copied) { 		rc = -EFAULT; 		goto out_free; 	} 	if (sccb->length < 8) { 		rc = -EINVAL; 		goto out_free; 	} 	rc = sclp_sync_request(ctl_sccb.cmdw, sccb);

Patch:
patch: @@ -56,6 +56,7 @@ static int sclp_ctl_ioctl_sccb(void __user *user_area)
 {
 	struct sclp_ctl_sccb ctl_sccb;
 	struct sccb_header *sccb;
+	unsigned long copied;
 	int rc;
 
 	if (copy_from_user(&ctl_sccb, user_area, sizeof(ctl_sccb)))
@@ -65,14 +66,15 @@ static int sclp_ctl_ioctl_sccb(void __user *user_area)
 	sccb = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA);
 	if (!sccb)
 		return -ENOMEM;
-	if (copy_from_user(sccb, u64_to_uptr(ctl_sccb.sccb), sizeof(*sccb))) {
+	copied = PAGE_SIZE -
+		copy_from_user(sccb, u64_to_uptr(ctl_sccb.sccb), PAGE_SIZE);
+	if (offsetof(struct sccb_header, length) +
+	    sizeof(sccb->length) > copied || sccb->length > copied) {
 		rc = -EFAULT;
 		goto out_free;
 	}
-	if (sccb->length > PAGE_SIZE || sccb->length < 8)
-		return -EINVAL;
-	if (copy_from_user(sccb, u64_to_uptr(ctl_sccb.sccb), sccb->length)) {
-		rc = -EFAULT;
+	if (sccb->length < 8) {
+		rc = -EINVAL;
 		goto out_free;
 	}
 	rc = sclp_sync_request(ctl_sccb.cmdw, sccb);
path: drivers/s390/char/sclp_ctl.c