fs: Make unload_nls() NULL pointer safe
[safe/jmp/linux-2.6] / fs / isofs / rock.c
index efefbcc..c2fb2dd 100644 (file)
 #include "isofs.h"
 #include "rock.h"
 
-/* These functions are designed to read the system areas of a directory record
+/*
+ * These functions are designed to read the system areas of a directory record
  * and extract relevant information.  There are different functions provided
  * depending upon what information we need at the time.  One function fills
  * out an inode structure, a second one extracts a filename, a third one
  * returns a symbolic link name, and a fourth one returns the extent number
- * for the file. */
+ * for the file.
+ */
 
 #define SIG(A,B) ((A) | ((B) << 8))    /* isonum_721() */
 
@@ -34,7 +36,7 @@ struct rock_state {
 
 /*
  * This is a way of ensuring that we have something in the system
- *  use fields that is compatible with Rock Ridge.  Return zero on success.
+ * use fields that is compatible with Rock Ridge.  Return zero on success.
  */
 
 static int check_sp(struct rock_ridge *rr, struct inode *inode)
@@ -79,9 +81,22 @@ static void init_rock_state(struct rock_state *rs, struct inode *inode)
 static int rock_continue(struct rock_state *rs)
 {
        int ret = 1;
+       int blocksize = 1 << rs->inode->i_blkbits;
+       const int min_de_size = offsetof(struct rock_ridge, u);
 
        kfree(rs->buffer);
        rs->buffer = NULL;
+
+       if ((unsigned)rs->cont_offset > blocksize - min_de_size ||
+           (unsigned)rs->cont_size > blocksize ||
+           (unsigned)(rs->cont_offset + rs->cont_size) > blocksize) {
+               printk(KERN_NOTICE "rock: corrupted directory entry. "
+                       "extent=%d, offset=%d, size=%d\n",
+                       rs->cont_extent, rs->cont_offset, rs->cont_size);
+               ret = -EIO;
+               goto out;
+       }
+
        if (rs->cont_extent) {
                struct buffer_head *bh;
 
@@ -111,7 +126,69 @@ out:
        return ret;
 }
 
-/* return length of name field; 0: not found, -1: to be ignored */
+/*
+ * We think there's a record of type `sig' at rs->chr.  Parse the signature
+ * and make sure that there's really room for a record of that type.
+ */
+static int rock_check_overflow(struct rock_state *rs, int sig)
+{
+       int len;
+
+       switch (sig) {
+       case SIG('S', 'P'):
+               len = sizeof(struct SU_SP_s);
+               break;
+       case SIG('C', 'E'):
+               len = sizeof(struct SU_CE_s);
+               break;
+       case SIG('E', 'R'):
+               len = sizeof(struct SU_ER_s);
+               break;
+       case SIG('R', 'R'):
+               len = sizeof(struct RR_RR_s);
+               break;
+       case SIG('P', 'X'):
+               len = sizeof(struct RR_PX_s);
+               break;
+       case SIG('P', 'N'):
+               len = sizeof(struct RR_PN_s);
+               break;
+       case SIG('S', 'L'):
+               len = sizeof(struct RR_SL_s);
+               break;
+       case SIG('N', 'M'):
+               len = sizeof(struct RR_NM_s);
+               break;
+       case SIG('C', 'L'):
+               len = sizeof(struct RR_CL_s);
+               break;
+       case SIG('P', 'L'):
+               len = sizeof(struct RR_PL_s);
+               break;
+       case SIG('T', 'F'):
+               len = sizeof(struct RR_TF_s);
+               break;
+       case SIG('Z', 'F'):
+               len = sizeof(struct RR_ZF_s);
+               break;
+       default:
+               len = 0;
+               break;
+       }
+       len += offsetof(struct rock_ridge, u);
+       if (len > rs->len) {
+               printk(KERN_NOTICE "rock: directory entry would overflow "
+                               "storage\n");
+               printk(KERN_NOTICE "rock: sig=0x%02x, size=%d, remaining=%d\n",
+                               sig, len, rs->len);
+               return -EIO;
+       }
+       return 0;
+}
+
+/*
+ * return length of name field; 0: not found, -1: to be ignored
+ */
 int get_rock_ridge_filename(struct iso_directory_record *de,
                            char *retname, struct inode *inode)
 {
@@ -132,13 +209,24 @@ repeat:
 
        while (rs.len > 2) { /* There may be one byte for padding somewhere */
                rr = (struct rock_ridge *)rs.chr;
+               /*
+                * Ignore rock ridge info if rr->len is out of range, but
+                * don't return -EIO because that would make the file
+                * invisible.
+                */
                if (rr->len < 3)
                        goto out;       /* Something got screwed up here */
                sig = isonum_721(rs.chr);
+               if (rock_check_overflow(&rs, sig))
+                       goto eio;
                rs.chr += rr->len;
                rs.len -= rr->len;
+               /*
+                * As above, just ignore the rock ridge info if rr->len
+                * is bogus.
+                */
                if (rs.len < 0)
-                       goto out;       /* corrupted isofs */
+                       goto out;       /* Something got screwed up here */
 
                switch (sig) {
                case SIG('R', 'R'):
@@ -196,6 +284,9 @@ repeat:
 out:
        kfree(rs.buffer);
        return ret;
+eio:
+       ret = -EIO;
+       goto out;
 }
 
 static int
@@ -225,13 +316,24 @@ parse_rock_ridge_inode_internal(struct iso_directory_record *de,
 repeat:
        while (rs.len > 2) { /* There may be one byte for padding somewhere */
                rr = (struct rock_ridge *)rs.chr;
+               /*
+                * Ignore rock ridge info if rr->len is out of range, but
+                * don't return -EIO because that would make the file
+                * invisible.
+                */
                if (rr->len < 3)
                        goto out;       /* Something got screwed up here */
                sig = isonum_721(rs.chr);
+               if (rock_check_overflow(&rs, sig))
+                       goto eio;
                rs.chr += rr->len;
                rs.len -= rr->len;
+               /*
+                * As above, just ignore the rock ridge info if rr->len
+                * is bogus.
+                */
                if (rs.len < 0)
-                       goto out;       /* corrupted isofs */
+                       goto out;       /* Something got screwed up here */
 
                switch (sig) {
 #ifndef CONFIG_ZISOFS          /* No flag for SF or ZF */
@@ -390,8 +492,10 @@ repeat:
                            isofs_iget(inode->i_sb,
                                       ISOFS_I(inode)->i_first_extent,
                                       0);
-                       if (!reloc)
+                       if (IS_ERR(reloc)) {
+                               ret = PTR_ERR(reloc);
                                goto out;
+                       }
                        inode->i_mode = reloc->i_mode;
                        inode->i_nlink = reloc->i_nlink;
                        inode->i_uid = reloc->i_uid;
@@ -462,6 +566,9 @@ repeat:
 out:
        kfree(rs.buffer);
        return ret;
+eio:
+       ret = -EIO;
+       goto out;
 }
 
 static char *get_symlink_chunk(char *rpnt, struct rock_ridge *rr, char *plimit)
@@ -535,8 +642,11 @@ static char *get_symlink_chunk(char *rpnt, struct rock_ridge *rr, char *plimit)
 int parse_rock_ridge_inode(struct iso_directory_record *de, struct inode *inode)
 {
        int result = parse_rock_ridge_inode_internal(de, inode, 0);
-       /* if rockridge flag was reset and we didn't look for attributes
-        * behind eventual XA attributes, have a look there */
+
+       /*
+        * 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);
@@ -544,9 +654,10 @@ int parse_rock_ridge_inode(struct iso_directory_record *de, struct inode *inode)
        return result;
 }
 
-/* readpage() for symlinks: reads symlink contents into the page and either
-   makes it uptodate and returns 0 or returns error (-EIO) */
-
+/*
+ * readpage() for symlinks: reads symlink contents into the page and either
+ * makes it uptodate and returns 0 or returns error (-EIO)
+ */
 static int rock_ridge_symlink_readpage(struct file *file, struct page *page)
 {
        struct inode *inode = page->mapping->host;
@@ -584,8 +695,10 @@ static int rock_ridge_symlink_readpage(struct file *file, struct page *page)
        if (offset + *pnt > bufsize)
                goto out_bad_span;
 
-       /* Now test for possible Rock Ridge extensions which will override
-          some of these numbers in the inode structure. */
+       /*
+        * Now test for possible Rock Ridge extensions which will override
+        * some of these numbers in the inode structure.
+        */
 
        setup_rock_ridge(raw_de, inode, &rs);
 
@@ -595,6 +708,8 @@ repeat:
                if (rr->len < 3)
                        goto out;       /* Something got screwed up here */
                sig = isonum_721(rs.chr);
+               if (rock_check_overflow(&rs, sig))
+                       goto out;
                rs.chr += rr->len;
                rs.len -= rr->len;
                if (rs.len < 0)
@@ -659,6 +774,6 @@ error:
        return -EIO;
 }
 
-struct address_space_operations isofs_symlink_aops = {
+const struct address_space_operations isofs_symlink_aops = {
        .readpage = rock_ridge_symlink_readpage
 };