CVE-2016-4913: Exposure of Sensitive Information to an Unauthorized Actor (CWE-200)
value: The get_rock_ridge_filename function in fs/isofs/rock.c in the Linux kernel before 4.5.5 mishandles NM (aka alternate name) entries containing \0 characters, which allows local users to obtain sensitive information from kernel memory or possibly have unspecified other impact via a crafted isofs filesystem.
CWE description: The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.
CVSS v3 base score: 7.8
Published: 2016-05-23T10:59Z
Repository: https://github.com/torvalds/linux
Language: C
Files changed: fs/isofs/rock.c
Commit date: 2016-05-07 22:52:39 -0400
Commit message: get_rock_ridge_filename(): handle malformed NM entries

Payloads of NM entries are not supposed to contain NUL.  When we run
into such, only the part prior to the first NUL goes into the
concatenation (i.e. the directory entry name being encoded by a bunch
of NM entries).  We do stop when the amount collected so far + the
claimed amount in the current NM entry exceed 254.  So far, so good,
but what we return as the total length is the sum of *claimed*
sizes, not the actual amount collected.  And that can grow pretty
large - not unlimited, since you'd need to put CE entries in
between to be able to get more than the maximum that could be
contained in one isofs directory entry / continuation chunk and
we are stop once we'd encountered 32 CEs, but you can get about 8Kb
easily.  And that's what will be passed to readdir callback as the
name length.  8Kb __copy_to_user() from a buffer allocated by
__get_free_page()

Cc: stable@vger.kernel.org # 0.98pl6+ (yes, really)
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

Diff: diff --git a/fs/isofs/rock.c b/fs/isofs/rock.c
index 5384ceb35b1cc..98b3eb7d8eaf6 100644
--- a/fs/isofs/rock.c
+++ b/fs/isofs/rock.c
@@ -201,10 +201,12 @@ int get_rock_ridge_filename(struct iso_directory_record *de,
 	struct rock_ridge *rr;
 	int sig;
 	int retnamlen = 0;
 	int truncate = 0;
 	int ret = 0;
+	char *p;
+	int len;
 
 	if (!ISOFS_SB(inode->i_sb)->s_rock)
 		return 0;
 	*retname = 0;
 
@@ -265,16 +267,21 @@ int get_rock_ridge_filename(struct iso_directory_record *de,
 			if (rr->u.NM.flags & ~1) {
 				printk("Unsupported NM flag settings (%d)\n",
 					rr->u.NM.flags);
 				break;
 			}
-			if ((strlen(retname) + rr->len - 5) >= 254) {
+			len = rr->len - 5;
+			if (retnamlen + len >= 254) {
 				truncate = 1;
 				break;
 			}
-			strncat(retname, rr->u.NM.name, rr->len - 5);
-			retnamlen += rr->len - 5;
+			p = memchr(rr->u.NM.name, '\0', len);
+			if (unlikely(p))
+				len = p - rr->u.NM.name;
+			memcpy(retname + retnamlen, rr->u.NM.name, len);
+			retnamlen += len;
+			retname[retnamlen] = '\0';
 			break;
 		case SIG('R', 'E'):
 			kfree(rs.buffer);
 			return -1;
 		default:

Vulnerable code: 			if ((strlen(retname) + rr->len - 5) >= 254) {
			strncat(retname, rr->u.NM.name, rr->len - 5);
			retnamlen += rr->len - 5;
Fixed code: 	char *p;
	int len;
			len = rr->len - 5;
			if (retnamlen + len >= 254) {
			p = memchr(rr->u.NM.name, '\0', len);
			if (unlikely(p))
				len = p - rr->u.NM.name;
			memcpy(retname + retnamlen, rr->u.NM.name, len);
			retnamlen += len;
			retname[retnamlen] = '\0';