Commit d6621d0d authored by Jan Kara's avatar Jan Kara Committed by Ben Hutchings

isofs: Fix unbounded recursion when processing relocated directories

commit 410dd3cf upstream.

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.
Reported-by: default avatarChris Evans <cevans@google.com>
Signed-off-by: default avatarJan Kara <jack@suse.cz>
Signed-off-by: default avatarBen Hutchings <ben@decadent.org.uk>
parent 416b0d26
...@@ -68,7 +68,7 @@ static void isofs_put_super(struct super_block *sb) ...@@ -68,7 +68,7 @@ static void isofs_put_super(struct super_block *sb)
return; 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 int isofs_statfs (struct dentry *, struct kstatfs *);
static struct kmem_cache *isofs_inode_cachep; static struct kmem_cache *isofs_inode_cachep;
...@@ -1263,7 +1263,7 @@ static int isofs_read_level3_size(struct inode *inode) ...@@ -1263,7 +1263,7 @@ static int isofs_read_level3_size(struct inode *inode)
goto out; 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 super_block *sb = inode->i_sb;
struct isofs_sb_info *sbi = ISOFS_SB(sb); struct isofs_sb_info *sbi = ISOFS_SB(sb);
...@@ -1408,7 +1408,7 @@ static int isofs_read_inode(struct inode *inode) ...@@ -1408,7 +1408,7 @@ static int isofs_read_inode(struct inode *inode)
*/ */
if (!high_sierra) { 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 we want uid/gid set, override the rock ridge setting */
if (sbi->s_uid_set) if (sbi->s_uid_set)
inode->i_uid = sbi->s_uid; inode->i_uid = sbi->s_uid;
...@@ -1487,9 +1487,10 @@ static int isofs_iget5_set(struct inode *ino, void *data) ...@@ -1487,9 +1487,10 @@ static int isofs_iget5_set(struct inode *ino, void *data)
* offset that point to the underlying meta-data for the inode. The * offset that point to the underlying meta-data for the inode. The
* code below is otherwise similar to the iget() code in * code below is otherwise similar to the iget() code in
* include/linux/fs.h */ * include/linux/fs.h */
struct inode *isofs_iget(struct super_block *sb, struct inode *__isofs_iget(struct super_block *sb,
unsigned long block, unsigned long block,
unsigned long offset) unsigned long offset,
int relocated)
{ {
unsigned long hashval; unsigned long hashval;
struct inode *inode; struct inode *inode;
...@@ -1511,7 +1512,7 @@ struct inode *isofs_iget(struct super_block *sb, ...@@ -1511,7 +1512,7 @@ struct inode *isofs_iget(struct super_block *sb,
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
if (inode->i_state & I_NEW) { if (inode->i_state & I_NEW) {
ret = isofs_read_inode(inode); ret = isofs_read_inode(inode, relocated);
if (ret < 0) { if (ret < 0) {
iget_failed(inode); iget_failed(inode);
inode = ERR_PTR(ret); inode = ERR_PTR(ret);
......
...@@ -107,7 +107,7 @@ extern int iso_date(char *, int); ...@@ -107,7 +107,7 @@ extern int iso_date(char *, int);
struct inode; /* To make gcc happy */ 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 get_rock_ridge_filename(struct iso_directory_record *, char *, struct inode *);
extern int isofs_name_translate(struct iso_directory_record *, char *, struct inode *); extern int isofs_name_translate(struct iso_directory_record *, char *, struct inode *);
...@@ -118,9 +118,24 @@ extern struct dentry *isofs_lookup(struct inode *, struct dentry *, struct namei ...@@ -118,9 +118,24 @@ extern struct dentry *isofs_lookup(struct inode *, struct dentry *, struct namei
extern struct buffer_head *isofs_bread(struct inode *, sector_t); 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 int isofs_get_blocks(struct inode *, sector_t, struct buffer_head **, unsigned long);
extern struct inode *isofs_iget(struct super_block *sb, struct inode *__isofs_iget(struct super_block *sb,
unsigned long block, unsigned long block,
unsigned long offset); 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 /* Because the inode number is no longer relevant to finding the
* underlying meta-data for an inode, we are free to choose a more * underlying meta-data for an inode, we are free to choose a more
......
...@@ -288,12 +288,16 @@ int get_rock_ridge_filename(struct iso_directory_record *de, ...@@ -288,12 +288,16 @@ int get_rock_ridge_filename(struct iso_directory_record *de,
goto out; goto out;
} }
#define RR_REGARD_XA 1
#define RR_RELOC_DE 2
static int static int
parse_rock_ridge_inode_internal(struct iso_directory_record *de, 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 symlink_len = 0;
int cnt, sig; int cnt, sig;
unsigned int reloc_block;
struct inode *reloc; struct inode *reloc;
struct rock_ridge *rr; struct rock_ridge *rr;
int rootflag; int rootflag;
...@@ -305,7 +309,7 @@ parse_rock_ridge_inode_internal(struct iso_directory_record *de, ...@@ -305,7 +309,7 @@ parse_rock_ridge_inode_internal(struct iso_directory_record *de,
init_rock_state(&rs, inode); init_rock_state(&rs, inode);
setup_rock_ridge(de, inode, &rs); setup_rock_ridge(de, inode, &rs);
if (regard_xa) { if (flags & RR_REGARD_XA) {
rs.chr += 14; rs.chr += 14;
rs.len -= 14; rs.len -= 14;
if (rs.len < 0) if (rs.len < 0)
...@@ -485,12 +489,22 @@ parse_rock_ridge_inode_internal(struct iso_directory_record *de, ...@@ -485,12 +489,22 @@ parse_rock_ridge_inode_internal(struct iso_directory_record *de,
"relocated directory\n"); "relocated directory\n");
goto out; goto out;
case SIG('C', 'L'): case SIG('C', 'L'):
ISOFS_I(inode)->i_first_extent = if (flags & RR_RELOC_DE) {
isonum_733(rr->u.CL.location); printk(KERN_ERR
reloc = "ISOFS: Recursive directory relocation "
isofs_iget(inode->i_sb, "is not supported\n");
ISOFS_I(inode)->i_first_extent, goto eio;
0); }
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)) { if (IS_ERR(reloc)) {
ret = PTR_ERR(reloc); ret = PTR_ERR(reloc);
goto out; goto out;
...@@ -637,9 +651,11 @@ static char *get_symlink_chunk(char *rpnt, struct rock_ridge *rr, char *plimit) ...@@ -637,9 +651,11 @@ static char *get_symlink_chunk(char *rpnt, struct rock_ridge *rr, char *plimit)
return rpnt; 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 * if rockridge flag was reset and we didn't look for attributes
...@@ -647,7 +663,8 @@ int parse_rock_ridge_inode(struct iso_directory_record *de, struct inode *inode) ...@@ -647,7 +663,8 @@ int parse_rock_ridge_inode(struct iso_directory_record *de, struct inode *inode)
*/ */
if ((ISOFS_SB(inode->i_sb)->s_rock_offset == -1) if ((ISOFS_SB(inode->i_sb)->s_rock_offset == -1)
&& (ISOFS_SB(inode->i_sb)->s_rock == 2)) { && (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; return result;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment