CVE-2014-5472: Improper Input Validation (CWE-20)
value: The parse_rock_ridge_inode_internal function in fs/isofs/rock.c in the Linux kernel through 3.16.1 allows local users to cause a denial of service (unkillable mount process) via a crafted iso9660 image with a self-referential CL entry.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
Published: 2014-09-01T01:55Z
Repository: https://github.com/torvalds/linux
Language: C
Files changed: fs/isofs/inode.c, fs/isofs/isofs.h, fs/isofs/rock.c
Commit date: 2014-08-19 18:29:30 +0200
Commit message: isofs: Fix unbounded recursion when processing relocated directories

We did not check relocated directory in any way when processing Rock
Ridge 'CL' tag. Thus a corrupted isofs image can possibly have a CL
entry pointing to another CL entry leading to possibly unbounded
recursion in kernel code and thus stack overflow or deadlocks (if there
is a loop created from CL entries).

Fix the problem by not allowing CL entry to point to a directory entry
with CL entry (such use makes no good sense anyway) and by checking
whether CL entry doesn't point to itself.

CC: stable@vger.kernel.org
Reported-by: Chris Evans <cevans@google.com>
Signed-off-by: Jan Kara <jack@suse.cz>

Diff: diff --git a/fs/isofs/inode.c b/fs/isofs/inode.c
index 4556ce1af5b04f..5ddaf8625d3b7a 100644
--- a/fs/isofs/inode.c
+++ b/fs/isofs/inode.c
@@ -59,11 +59,11 @@ static void isofs_put_super(struct super_block *sb)
 	kfree(sbi);
 	sb->s_fs_info = NULL;
 	return;
 }
 
-static int isofs_read_inode(struct inode *);
+static int isofs_read_inode(struct inode *, int relocated);
 static int isofs_statfs (struct dentry *, struct kstatfs *);
 
 static struct kmem_cache *isofs_inode_cachep;
 
 static struct inode *isofs_alloc_inode(struct super_block *sb)
@@ -1257,11 +1257,11 @@ static int isofs_read_level3_size(struct inode *inode)
 		"isofs_read_level3_size: inode=%lu\n",
 		__func__, inode->i_ino);
 	goto out;
 }
 
-static int isofs_read_inode(struct inode *inode)
+static int isofs_read_inode(struct inode *inode, int relocated)
 {
 	struct super_block *sb = inode->i_sb;
 	struct isofs_sb_info *sbi = ISOFS_SB(sb);
 	unsigned long bufsize = ISOFS_BUFFER_SIZE(inode);
 	unsigned long block;
@@ -1402,11 +1402,11 @@ static int isofs_read_inode(struct inode *inode)
 	 * Now test for possible Rock Ridge extensions which will override
 	 * some of these numbers in the inode structure.
 	 */
 
 	if (!high_sierra) {
-		parse_rock_ridge_inode(de, inode);
+		parse_rock_ridge_inode(de, inode, relocated);
 		/* if we want uid/gid set, override the rock ridge setting */
 		if (sbi->s_uid_set)
 			inode->i_uid = sbi->s_uid;
 		if (sbi->s_gid_set)
 			inode->i_gid = sbi->s_gid;
@@ -1481,13 +1481,14 @@ static int isofs_iget5_set(struct inode *ino, void *data)
 
 /* Store, in the inode's containing structure, the block and block
  * offset that point to the underlying meta-data for the inode.  The
  * code below is otherwise similar to the iget() code in
  * include/linux/fs.h */
-struct inode *isofs_iget(struct super_block *sb,
-			 unsigned long block,
-			 unsigned long offset)
+struct inode *__isofs_iget(struct super_block *sb,
+			   unsigned long block,
+			   unsigned long offset,
+			   int relocated)
 {
 	unsigned long hashval;
 	struct inode *inode;
 	struct isofs_iget5_callback_data data;
 	long ret;
@@ -1505,11 +1506,11 @@ struct inode *isofs_iget(struct super_block *sb,
 
 	if (!inode)
 		return ERR_PTR(-ENOMEM);
 
 	if (inode->i_state & I_NEW) {
-		ret = isofs_read_inode(inode);
+		ret = isofs_read_inode(inode, relocated);
 		if (ret < 0) {
 			iget_failed(inode);
 			inode = ERR_PTR(ret);
 		} else {
 			unlock_new_inode(inode);
diff --git a/fs/isofs/isofs.h b/fs/isofs/isofs.h
index 99167238518d61..0ac4c1f73fbd6c 100644
--- a/fs/isofs/isofs.h
+++ b/fs/isofs/isofs.h
@@ -105,24 +105,39 @@ static inline unsigned int isonum_733(char *p)
 }
 extern int iso_date(char *, int);
 
 struct inode;		/* To make gcc happy */
 
-extern int parse_rock_ridge_inode(struct iso_directory_record *, struct inode *);
+extern int parse_rock_ridge_inode(struct iso_directory_record *, struct inode *, int relocated);
 extern int get_rock_ridge_filename(struct iso_directory_record *, char *, struct inode *);
 extern int isofs_name_translate(struct iso_directory_record *, char *, struct inode *);
 
 int get_joliet_filename(struct iso_directory_record *, unsigned char *, struct inode *);
 int get_acorn_filename(struct iso_directory_record *, char *, struct inode *);
 
 extern struct dentry *isofs_lookup(struct inode *, struct dentry *, unsigned int flags);
 extern struct buffer_head *isofs_bread(struct inode *, sector_t);
 extern int isofs_get_blocks(struct inode *, sector_t, struct buffer_head **, unsigned long);
 
-extern struct inode *isofs_iget(struct super_block *sb,
-                                unsigned long block,
-                                unsigned long offset);
+struct inode *__isofs_iget(struct super_block *sb,
+			   unsigned long block,
+			   unsigned long offset,
+			   int relocated);
+
+static inline struct inode *isofs_iget(struct super_block *sb,
+				       unsigned long block,
+				       unsigned long offset)
+{
+	return __isofs_iget(sb, block, offset, 0);
+}
+
+static inline struct inode *isofs_iget_reloc(struct super_block *sb,
+					     unsigned long block,
+					     unsigned long offset)
+{
+	return __isofs_iget(sb, block, offset, 1);
+}
 
 /* Because the inode number is no longer relevant to finding the
  * underlying meta-data for an inode, we are free to choose a more
  * convenient 32-bit number as the inode number.  The inode numbering
  * scheme was recommended by Sergey Vlasov and Eric Lammerts. */
diff --git a/fs/isofs/rock.c b/fs/isofs/rock.c
index c0bf42472e408f..f488bbae541ac8 100644
--- a/fs/isofs/rock.c
+++ b/fs/isofs/rock.c
@@ -286,16 +286,20 @@ int get_rock_ridge_filename(struct iso_directory_record *de,
 eio:
 	ret = -EIO;
 	goto out;
 }
 
+#define RR_REGARD_XA 1
+#define RR_RELOC_DE 2
+
 static int
 parse_rock_ridge_inode_internal(struct iso_directory_record *de,
-				struct inode *inode, int regard_xa)
+				struct inode *inode, int flags)
 {
 	int symlink_len = 0;
 	int cnt, sig;
+	unsigned int reloc_block;
 	struct inode *reloc;
 	struct rock_ridge *rr;
 	int rootflag;
 	struct rock_state rs;
 	int ret = 0;
@@ -303,11 +307,11 @@ parse_rock_ridge_inode_internal(struct iso_directory_record *de,
 	if (!ISOFS_SB(inode->i_sb)->s_rock)
 		return 0;
 
 	init_rock_state(&rs, inode);
 	setup_rock_ridge(de, inode, &rs);
-	if (regard_xa) {
+	if (flags & RR_REGARD_XA) {
 		rs.chr += 14;
 		rs.len -= 14;
 		if (rs.len < 0)
 			rs.len = 0;
 	}
@@ -483,16 +487,26 @@ parse_rock_ridge_inode_internal(struct iso_directory_record *de,
 		case SIG('R', 'E'):
 			printk(KERN_WARNING "Attempt to read inode for "
 					"relocated directory\n");
 			goto out;
 		case SIG('C', 'L'):
-			ISOFS_I(inode)->i_first_extent =
-			    isonum_733(rr->u.CL.location);
-			reloc =
-			    isofs_iget(inode->i_sb,
-				       ISOFS_I(inode)->i_first_extent,
-				       0);
+			if (flags & RR_RELOC_DE) {
+				printk(KERN_ERR
+				       "ISOFS: Recursive directory relocation "
+				       "is not supported\n");
+				goto eio;
+			}
+			reloc_block = isonum_733(rr->u.CL.location);
+			if (reloc_block == ISOFS_I(inode)->i_iget5_block &&
+			    ISOFS_I(inode)->i_iget5_offset == 0) {
+				printk(KERN_ERR
+				       "ISOFS: Directory relocation points to "
+				       "itself\n");
+				goto eio;
+			}
+			ISOFS_I(inode)->i_first_extent = reloc_block;
+			reloc = isofs_iget_reloc(inode->i_sb, reloc_block, 0);
 			if (IS_ERR(reloc)) {
 				ret = PTR_ERR(reloc);
 				goto out;
 			}
 			inode->i_mode = reloc->i_mode;
@@ -635,21 +649,24 @@ static char *get_symlink_chunk(char *rpnt, struct rock_ridge *rr, char *plimit)
 		}
 	}
 	return rpnt;
 }
 
-int parse_rock_ridge_inode(struct iso_directory_record *de, struct inode *inode)
+int parse_rock_ridge_inode(struct iso_directory_record *de, struct inode *inode,
+			   int relocated)
 {
-	int result = parse_rock_ridge_inode_internal(de, inode, 0);
+	int flags = relocated ? RR_RELOC_DE : 0;
+	int result = parse_rock_ridge_inode_internal(de, inode, flags);
 
 	/*
 	 * if rockridge flag was reset and we didn't look for attributes
 	 * behind eventual XA attributes, have a look there
 	 */
 	if ((ISOFS_SB(inode->i_sb)->s_rock_offset == -1)
 	    && (ISOFS_SB(inode->i_sb)->s_rock == 2)) {
-		result = parse_rock_ridge_inode_internal(de, inode, 14);
+		result = parse_rock_ridge_inode_internal(de, inode,
+							 flags | RR_REGARD_XA);
 	}
 	return result;
 }
 
 /*

Vulnerable code: static int isofs_read_inode(struct inode *);
static int isofs_read_inode(struct inode *inode)
		parse_rock_ridge_inode(de, inode);
struct inode *isofs_iget(struct super_block *sb,
			 unsigned long block,
			 unsigned long offset)
		ret = isofs_read_inode(inode);
extern int parse_rock_ridge_inode(struct iso_directory_record *, struct inode *);
extern struct inode *isofs_iget(struct super_block *sb,
                                unsigned long block,
                                unsigned long offset);
				struct inode *inode, int regard_xa)
	if (regard_xa) {
			ISOFS_I(inode)->i_first_extent =
			    isonum_733(rr->u.CL.location);
			reloc =
			    isofs_iget(inode->i_sb,
				       ISOFS_I(inode)->i_first_extent,
				       0);
int parse_rock_ridge_inode(struct iso_directory_record *de, struct inode *inode)
	int result = parse_rock_ridge_inode_internal(de, inode, 0);
		result = parse_rock_ridge_inode_internal(de, inode, 14);
Fixed code: static int isofs_read_inode(struct inode *, int relocated);
static int isofs_read_inode(struct inode *inode, int relocated)
		parse_rock_ridge_inode(de, inode, relocated);
struct inode *__isofs_iget(struct super_block *sb,
			   unsigned long block,
			   unsigned long offset,
			   int relocated)
		ret = isofs_read_inode(inode, relocated);
extern int parse_rock_ridge_inode(struct iso_directory_record *, struct inode *, int relocated);
struct inode *__isofs_iget(struct super_block *sb,
			   unsigned long block,
			   unsigned long offset,
			   int relocated);

static inline struct inode *isofs_iget(struct super_block *sb,
				       unsigned long block,
				       unsigned long offset)
{
	return __isofs_iget(sb, block, offset, 0);
}

static inline struct inode *isofs_iget_reloc(struct super_block *sb,
					     unsigned long block,
					     unsigned long offset)
{
	return __isofs_iget(sb, block, offset, 1);
}
#define RR_REGARD_XA 1
#define RR_RELOC_DE 2

				struct inode *inode, int flags)
	unsigned int reloc_block;
	if (flags & RR_REGARD_XA) {
			if (flags & RR_RELOC_DE) {
				printk(KERN_ERR
				       "ISOFS: Recursive directory relocation "
				       "is not supported\n");
				goto eio;
			}
			reloc_block = isonum_733(rr->u.CL.location);
			if (reloc_block == ISOFS_I(inode)->i_iget5_block &&
			    ISOFS_I(inode)->i_iget5_offset == 0) {
				printk(KERN_ERR
				       "ISOFS: Directory relocation points to "
				       "itself\n");
				goto eio;
			}
			ISOFS_I(inode)->i_first_extent = reloc_block;
			reloc = isofs_iget_reloc(inode->i_sb, reloc_block, 0);
int parse_rock_ridge_inode(struct iso_directory_record *de, struct inode *inode,
			   int relocated)
	int flags = relocated ? RR_RELOC_DE : 0;
	int result = parse_rock_ridge_inode_internal(de, inode, flags);
		result = parse_rock_ridge_inode_internal(de, inode,
							 flags | RR_REGARD_XA);