Avoid rec_len overflow with 64KB block size
Subsystems (2)
Jan Kara
reviewercover Avoid rec_len overflow with 64KB block size
Hi,
In the following series I'm sending patches to ext2, ext3 and ext4
solving possible overflow of rec_len when using 64KB blocksize. Minming, would you add these to the patch queue? Thanks.
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Jan Kara
reviewerWith 64KB blocksize, a directory entry can have size 64KB which does not fit into 16 bits we have for entry lenght. So we store 0xffff instead and convert value when read from / written to disk.
fs/ext2/dir.c +31 -12
@@ -26,6 +26,24 @@typedef struct ext2_dir_entry_2 ext2_dirent ; +static inline unsigned ext2_rec_len_from_disk (__le16 dlen) +{ +unsigned len =le16_to_cpu (dlen); + +if (len ==EXT2_MAX_REC_LEN ) +return 1 <<16 ; +return len; +} + +static inline __le16 ext2_rec_len_to_disk (unsigned len) +{ +if (len == (1 <<16 )) +return cpu_to_le16 (EXT2_MAX_REC_LEN ); +else if (len > (1 <<16 )) +BUG (); +return cpu_to_le16 (len); +} +/* * ext2 uses block-sized chunks. Arguably, sector-sized ones would be * more robust, but we have what we have
@@ -95,7 +113,7 @@ static void ext2_check_page(struct page }for (offs =0 ; offs <= limit -EXT2_DIR_REC_LEN (1 ); offs += rec_len) { p = (ext2_dirent *)(kaddr + offs); - rec_len =le16_to_cpu (p->rec_len); + rec_len =ext2_rec_len_from_disk (p->rec_len);if (rec_len <EXT2_DIR_REC_LEN (1 )) goto Eshort;
@@ -193,7 +211,8 @@ static inline int ext2_match (int len, c*/ static inline ext2_dirent *ext2_next_entry (ext2_dirent *p) { -return (ext2_dirent *)((char *)p +le16_to_cpu (p->rec_len)); +return (ext2_dirent *)((char *)p + +ext2_rec_len_from_disk (p->rec_len)); } static inline unsigned
@@ -305,7 +324,7 @@ ext2_readdir (struct file * filp, void *return 0 ; } } - filp->f_pos +=le16_to_cpu (de->rec_len); + filp->f_pos +=ext2_rec_len_from_disk (de->rec_len); }ext2_put_page (page); }
@@ -413,7 +432,7 @@ void ext2_set_link(struct inode *dir, st struct page *page, struct inode *inode) { unsigned from = (char *) de - (char *) page_address(page); -unsigned to = from +le16_to_cpu (de->rec_len); +unsigned to = from +ext2_rec_len_from_disk (de->rec_len);int err;lock_page (page);
@@ -469,7 +488,7 @@ int ext2_add_link (struct dentry *dentry/* We hit i_size */ name_len =0 ; rec_len = chunk_size; - de->rec_len =cpu_to_le16 (chunk_size); + de->rec_len =ext2_rec_len_to_disk (chunk_size); de->inode =0 ; goto got_it; }
@@ -483,7 +502,7 @@ int ext2_add_link (struct dentry *dentryif (ext2_match (namelen, name, de)) goto out_unlock; name_len =EXT2_DIR_REC_LEN (de->name_len); - rec_len =le16_to_cpu (de->rec_len); + rec_len =ext2_rec_len_from_disk (de->rec_len);if (!de->inode && rec_len >= reclen) goto got_it;if (rec_len >= name_len + reclen)
@@ -504,8 +523,8 @@ got_it: goto out_unlock;if (de->inode) {ext2_dirent *de1 = (ext2_dirent *) ((char *) de + name_len); - de1->rec_len =cpu_to_le16 (rec_len - name_len); - de->rec_len =cpu_to_le16 (name_len); + de1->rec_len =ext2_rec_len_to_disk (rec_len - name_len); + de->rec_len =ext2_rec_len_to_disk (name_len); de = de1; } de->name_len = namelen;
@@ -536,7 +555,7 @@ int ext2_delete_entry (struct ext2_dir_estruct inode *inode = mapping->host;char *kaddr =page_address (page);unsigned from = ((char *)dir - kaddr) & ~(ext2_chunk_size (inode)-1 ); -unsigned to = ((char *)dir - kaddr) +le16_to_cpu (dir->rec_len); +unsigned to = ((char *)dir - kaddr) +ext2_rec_len_from_disk (dir->rec_len);ext2_dirent * pde =NULL ;ext2_dirent * de = (ext2_dirent *) (kaddr + from);int err;
@@ -557,7 +576,7 @@ int ext2_delete_entry (struct ext2_dir_e err = mapping->a_ops->prepare_write (NULL , page, from, to);BUG_ON (err);if (pde) - pde->rec_len =cpu_to_le16 (to-from); + pde->rec_len =ext2_rec_len_to_disk (to-from); dir->inode =0 ; err =ext2_commit_chunk (page, from, to); inode->i_ctime = inode->i_mtime =CURRENT_TIME_SEC ;
@@ -591,14 +610,14 @@ int ext2_make_empty(struct inode *inode,memset (kaddr,0 , chunk_size); de = (struct ext2_dir_entry_2 *)kaddr; de->name_len =1 ; - de->rec_len =cpu_to_le16 (EXT2_DIR_REC_LEN (1 )); + de->rec_len =ext2_rec_len_to_disk (EXT2_DIR_REC_LEN (1 ));memcpy (de->name,".\0\0" ,4 ); de->inode =cpu_to_le32 (inode->i_ino);ext2_set_de_type (de, inode); de = (struct ext2_dir_entry_2 *)(kaddr +EXT2_DIR_REC_LEN (1 )); de->name_len =2 ; - de->rec_len =cpu_to_le16 (chunk_size -EXT2_DIR_REC_LEN (1 )); + de->rec_len =ext2_rec_len_to_disk (chunk_size -EXT2_DIR_REC_LEN (1 )); de->inode =cpu_to_le32 (parent->i_ino);memcpy (de->name,"..\0" ,4 );ext2_set_de_type (de, inode);
include/linux/ext2_fs.h +1 -0
@@ -557,5 +557,6 @@ enum {#define EXT2_DIR_ROUND (EXT2_DIR_PAD - 1)#define EXT2_DIR_REC_LEN (name_len) (((name_len) + 8 + EXT2_DIR_ROUND) & \ ~EXT2_DIR_ROUND) +#define EXT2_MAX_REC_LEN ((1<<16)-1)#endif /* _LINUX_EXT2_FS_H */
Jan Kara
reviewerWith 64KB blocksize, a directory entry can have size 64KB which does not fit into 16 bits we have for entry lenght. So we store 0xffff instead and convert value when read from / written to disk. The patch also converts some places to use ext3_next_entry() when we are changing them anyway.
fs/ext3/dir.c +5 -5
@@ -69,7 +69,7 @@ int ext3_check_dir_entry (const char * f unsigned long offset) {const char * error_msg =NULL ; -const int rlen =le16_to_cpu (de->rec_len); +const int rlen =ext3_rec_len_from_disk (de->rec_len);if (rlen <EXT3_DIR_REC_LEN (1 )) error_msg ="rec_len is smaller than minimal" ;
@@ -177,10 +177,10 @@ revalidate:* least that it is non-zero. A * failure will be detected in the * dirent test below. */ -if (le16_to_cpu (de->rec_len) < +if (ext3_rec_len_from_disk (de->rec_len) <EXT3_DIR_REC_LEN (1 ))break ; - i +=le16_to_cpu (de->rec_len); + i +=ext3_rec_len_from_disk (de->rec_len); } offset = i; filp->f_pos = (filp->f_pos & ~(sb->s_blocksize -1 ))
@@ -201,7 +201,7 @@ revalidate: ret = stored; goto out; } - offset +=le16_to_cpu (de->rec_len); + offset +=ext3_rec_len_from_disk (de->rec_len);if (le32_to_cpu (de->inode)) {/* We might block in the next section * if the data destination is
@@ -223,7 +223,7 @@ revalidate: goto revalidate; stored ++; } - filp->f_pos +=le16_to_cpu (de->rec_len); + filp->f_pos +=ext3_rec_len_from_disk (de->rec_len); } offset =0 ;brelse (bh);
fs/ext3/namei.c +43 -47
227 lines not on this page · raw message
include/linux/ext3_fs.h +20 -0
@@ -660,6 +660,26 @@ struct ext3_dir_entry_2 {#define EXT3_DIR_ROUND (EXT3_DIR_PAD - 1)#define EXT3_DIR_REC_LEN (name_len) (((name_len) + 8 + EXT3_DIR_ROUND) & \ ~EXT3_DIR_ROUND) +#define EXT3_MAX_REC_LEN ((1<<16)-1) + +static inline unsigned ext3_rec_len_from_disk (__le16 dlen) +{ +unsigned len =le16_to_cpu (dlen); + +if (len ==EXT3_MAX_REC_LEN ) +return 1 <<16 ; +return len; +} + +static inline __le16 ext3_rec_len_to_disk (unsigned len) +{ +if (len == (1 <<16 )) +return cpu_to_le16 (EXT3_MAX_REC_LEN ); +else if (len > (1 <<16 )) +BUG (); +return cpu_to_le16 (len); +} +/* * Hash Tree Directory indexing * (c) Daniel Phillips, 2001
Jan Kara
reviewerWith 64KB blocksize, a directory entry can have size 64KB which does not fit into 16 bits we have for entry lenght. So we store 0xffff instead and convert value when read from / written to disk. The patch also converts some places to use ext4_next_entry() when we are changing them anyway.
fs/ext4/dir.c +6 -6
@@ -69,7 +69,7 @@ int ext4_check_dir_entry (const char * f unsigned long offset) {const char * error_msg =NULL ; -const int rlen =le16_to_cpu (de->rec_len); +const int rlen =ext4_rec_len_from_disk (de->rec_len);if (rlen <EXT4_DIR_REC_LEN (1 )) error_msg ="rec_len is smaller than minimal" ;
@@ -176,10 +176,10 @@ revalidate:* least that it is non-zero. A * failure will be detected in the * dirent test below. */ -if (le16_to_cpu (de->rec_len) < -EXT4_DIR_REC_LEN (1 )) +if (ext4_rec_len_from_disk (de->rec_len) + <EXT4_DIR_REC_LEN (1 ))break ; - i +=le16_to_cpu (de->rec_len); + i +=ext4_rec_len_from_disk (de->rec_len); } offset = i; filp->f_pos = (filp->f_pos & ~(sb->s_blocksize -1 ))
@@ -201,7 +201,7 @@ revalidate: ret = stored; goto out; } - offset +=le16_to_cpu (de->rec_len); + offset +=ext4_rec_len_from_disk (de->rec_len);if (le32_to_cpu (de->inode)) {/* We might block in the next section * if the data destination is
@@ -223,7 +223,7 @@ revalidate: goto revalidate; stored ++; } - filp->f_pos +=le16_to_cpu (de->rec_len); + filp->f_pos +=ext4_rec_len_from_disk (de->rec_len); } offset =0 ;brelse (bh);
fs/ext4/namei.c +36 -40
207 lines not on this page · raw message
include/linux/ext4_fs.h +20 -0
@@ -784,6 +784,26 @@ struct ext4_dir_entry_2 {#define EXT4_DIR_ROUND (EXT4_DIR_PAD - 1)#define EXT4_DIR_REC_LEN (name_len) (((name_len) + 8 + EXT4_DIR_ROUND) & \ ~EXT4_DIR_ROUND) +#define EXT4_MAX_REC_LEN ((1<<16)-1) + +static inline unsigned ext4_rec_len_from_disk (__le16 dlen) +{ +unsigned len =le16_to_cpu (dlen); + +if (len ==EXT4_MAX_REC_LEN ) +return 1 <<16 ; +return len; +} + +static inline __le16 ext4_rec_len_to_disk (unsigned len) +{ +if (len == (1 <<16 )) +return cpu_to_le16 (EXT4_MAX_REC_LEN ); +else if (len > (1 <<16 )) +BUG (); +return cpu_to_le16 (len); +} +/* * Hash Tree Directory indexing * (c) Daniel Phillips, 2001