CVE-2014-5471: Resource Management Errors (CWE-399)
value: Stack consumption vulnerability in 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 (uncontrolled recursion, and system crash or reboot) via a crafted iso9660 image with a CL entry referring to a directory entry that has a CL entry.
CWE description: Weaknesses in this category are related to improper management of system resources.
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);