Commit b2e4b218 authored by Hirofumi Ogawa's avatar Hirofumi Ogawa Committed by Linus Torvalds

[PATCH] Fix handling of wrong cluster chains (1/5)

Currently fatfs disregards most wrong cluster chains, and only continues
processing. 

This fixes it.  If a wrong cluster chain is detected, change filesystem
to the read only, and return the error code. 
parent 13d34dcb
......@@ -40,8 +40,10 @@ int default_fat_access(struct super_block *sb,int nr,int new_value)
unsigned char *p_first, *p_last;
int copy, first, last, next, b;
if ((unsigned) (nr-2) >= MSDOS_SB(sb)->clusters)
return 0;
if (nr < 2 || MSDOS_SB(sb)->clusters + 2 <= nr) {
fat_fs_panic(sb, "invalid access to FAT (entry 0x%08x)", nr);
return -EIO;
}
if (MSDOS_SB(sb)->fat_bits == 32) {
first = last = nr*4;
} else if (MSDOS_SB(sb)->fat_bits == 16) {
......@@ -52,16 +54,17 @@ int default_fat_access(struct super_block *sb,int nr,int new_value)
}
b = MSDOS_SB(sb)->fat_start + (first >> sb->s_blocksize_bits);
if (!(bh = fat_bread(sb, b))) {
printk("bread in fat_access failed\n");
return 0;
printk("FAT: bread(block %d) in fat_access failed\n", b);
return -EIO;
}
if ((first >> sb->s_blocksize_bits) == (last >> sb->s_blocksize_bits)) {
bh2 = bh;
} else {
if (!(bh2 = fat_bread(sb, b+1))) {
fat_brelse(sb, bh);
printk("2nd bread in fat_access failed\n");
return 0;
printk("FAT: bread(block %d) in fat_access failed\n",
b + 1);
return -EIO;
}
}
if (MSDOS_SB(sb)->fat_bits == 32) {
......@@ -69,23 +72,26 @@ int default_fat_access(struct super_block *sb,int nr,int new_value)
next = CF_LE_L(((__u32 *) bh->b_data)[(first &
(sb->s_blocksize - 1)) >> 2]);
/* Fscking Microsoft marketing department. Their "32" is 28. */
next &= 0xfffffff;
if (next >= 0xffffff7) next = -1;
next &= 0x0fffffff;
if (next >= BAD_FAT32) next = FAT_ENT_EOF;
PRINTK(("fat_bread: 0x%x, nr=0x%x, first=0x%x, next=0x%x\n", b, nr, first, next));
} else if (MSDOS_SB(sb)->fat_bits == 16) {
p_first = p_last = NULL; /* GCC needs that stuff */
next = CF_LE_W(((__u16 *) bh->b_data)[(first &
(sb->s_blocksize - 1)) >> 1]);
if (next >= 0xfff7) next = -1;
if (next >= BAD_FAT16) next = FAT_ENT_EOF;
} else {
p_first = &((__u8 *)bh->b_data)[first & (sb->s_blocksize - 1)];
p_last = &((__u8 *)bh2->b_data)[(first + 1) & (sb->s_blocksize - 1)];
if (nr & 1) next = ((*p_first >> 4) | (*p_last << 4)) & 0xfff;
else next = (*p_first+(*p_last << 8)) & 0xfff;
if (next >= 0xff7) next = -1;
if (next >= BAD_FAT12) next = FAT_ENT_EOF;
}
if (new_value != -1) {
if (next == FAT_ENT_EOF)
next = EOF_FAT(sb);
if (MSDOS_SB(sb)->fat_bits == 32) {
((__u32 *)bh->b_data)[(first & (sb->s_blocksize - 1)) >> 2]
= CT_LE_L(new_value);
......@@ -271,19 +277,30 @@ void fat_cache_inval_dev(struct super_block *sb)
}
int fat_get_cluster(struct inode *inode,int cluster)
static int fat_get_cluster(struct inode *inode, int cluster)
{
struct super_block *sb = inode->i_sb;
int nr,count;
if (!(nr = MSDOS_I(inode)->i_start)) return 0;
if (!cluster) return nr;
count = 0;
for (fat_cache_lookup(inode,cluster,&count,&nr); count < cluster;
count++) {
if ((nr = fat_access(inode->i_sb,nr,-1)) == -1) return 0;
if (!nr) return 0;
for (fat_cache_lookup(inode, cluster, &count, &nr);
count < cluster;
count++) {
nr = fat_access(sb, nr, -1);
if (nr == FAT_ENT_EOF) {
fat_fs_panic(sb, "%s: request beyond EOF (ino %lu)",
__FUNCTION__, inode->i_ino);
return -EIO;
} else if (nr == FAT_ENT_FREE) {
fat_fs_panic(sb, "%s: invalid cluster chain (ino %lu)",
__FUNCTION__, inode->i_ino);
return -EIO;
} else if (nr < 0)
return nr;
}
fat_cache_add(inode,cluster,nr);
fat_cache_add(inode, cluster, nr);
return nr;
}
......@@ -307,9 +324,11 @@ int default_fat_bmap(struct inode *inode,int sector)
cluster = sector / sbi->cluster_size;
offset = sector % sbi->cluster_size;
if (!(cluster = fat_get_cluster(inode, cluster)))
cluster = fat_get_cluster(inode, cluster);
if (cluster < 0)
return cluster;
else if (!cluster)
return 0;
return (cluster - 2) * sbi->cluster_size + sbi->data_start + offset;
}
......@@ -319,20 +338,25 @@ int default_fat_bmap(struct inode *inode,int sector)
int fat_free(struct inode *inode,int skip)
{
struct super_block *sb = inode->i_sb;
int nr,last;
if (!(nr = MSDOS_I(inode)->i_start)) return 0;
last = 0;
while (skip--) {
last = nr;
if ((nr = fat_access(inode->i_sb,nr,-1)) == -1) return 0;
if (!nr) {
printk("fat_free: skipped EOF\n");
nr = fat_access(sb, nr, -1);
if (nr == FAT_ENT_EOF)
return 0;
else if (nr == FAT_ENT_FREE) {
fat_fs_panic(sb, "%s: invalid cluster chain (ino %lu)",
__FUNCTION__, inode->i_ino);
return -EIO;
}
} else if (nr < 0)
return nr;
}
if (last) {
fat_access(inode->i_sb,last,EOF_FAT(inode->i_sb));
fat_access(sb, last, FAT_ENT_EOF);
fat_cache_inval_inode(inode);
} else {
fat_cache_inval_inode(inode);
......@@ -340,11 +364,17 @@ int fat_free(struct inode *inode,int skip)
MSDOS_I(inode)->i_logstart = 0;
mark_inode_dirty(inode);
}
lock_fat(inode->i_sb);
while (nr != -1) {
if (!(nr = fat_access(inode->i_sb,nr,0))) {
fat_fs_panic(inode->i_sb,"fat_free: deleting beyond EOF");
break;
lock_fat(sb);
while (nr != FAT_ENT_EOF) {
nr = fat_access(sb, nr, FAT_ENT_FREE);
if (nr < 0)
goto error;
else if (nr == FAT_ENT_FREE) {
fat_fs_panic(sb, "%s: deleting beyond EOF (ino %lu)",
__FUNCTION__, inode->i_ino);
nr = -EIO;
goto error;
}
if (MSDOS_SB(inode->i_sb)->free_clusters != -1) {
MSDOS_SB(inode->i_sb)->free_clusters++;
......@@ -354,6 +384,9 @@ int fat_free(struct inode *inode,int skip)
}
inode->i_blocks -= (1 << MSDOS_SB(inode->i_sb)->cluster_bits) / 512;
}
unlock_fat(inode->i_sb);
return 0;
nr = 0;
error:
unlock_fat(sb);
return nr;
}
......@@ -32,7 +32,6 @@ EXPORT_SYMBOL(fat_statfs);
EXPORT_SYMBOL(fat_write_inode);
EXPORT_SYMBOL(register_cvf_format);
EXPORT_SYMBOL(unregister_cvf_format);
EXPORT_SYMBOL(fat_get_cluster);
EXPORT_SYMBOL(fat_dir_ioctl);
EXPORT_SYMBOL(fat_add_entries);
EXPORT_SYMBOL(fat_dir_empty);
......
......@@ -46,6 +46,8 @@ int fat_get_block(struct inode *inode, sector_t iblock, struct buffer_head *bh_r
unsigned long phys;
phys = fat_bmap(inode, iblock);
if (phys < 0)
return phys;
if (phys) {
map_bh(bh_result, inode->i_sb, phys);
return 0;
......@@ -65,6 +67,8 @@ int fat_get_block(struct inode *inode, sector_t iblock, struct buffer_head *bh_r
}
MSDOS_I(inode)->mmu_private += sb->s_blocksize;
phys = fat_bmap(inode, iblock);
if (phys < 0)
return phys;
if (!phys)
BUG();
bh_result->b_state |= (1UL << BH_New);
......
......@@ -121,7 +121,7 @@ struct inode *fat_iget(struct super_block *sb, int i_pos)
return inode;
}
static void fat_fill_inode(struct inode *inode, struct msdos_dir_entry *de);
static int fat_fill_inode(struct inode *inode, struct msdos_dir_entry *de);
struct inode *fat_build_inode(struct super_block *sb,
struct msdos_dir_entry *de, int ino, int *res)
......@@ -135,10 +135,14 @@ struct inode *fat_build_inode(struct super_block *sb,
*res = -ENOMEM;
if (!inode)
goto out;
*res = 0;
inode->i_ino = iunique(sb, MSDOS_ROOT_INO);
inode->i_version = 0;
fat_fill_inode(inode, de);
*res = fat_fill_inode(inode, de);
if (*res < 0) {
iput(inode);
inode = NULL;
goto out;
}
fat_attach(inode, ino);
insert_inode_hash(inode);
out:
......@@ -358,22 +362,25 @@ static int parse_options(char *options, int *debug,
return ret;
}
static void fat_calc_dir_size(struct inode *inode)
static int fat_calc_dir_size(struct inode *inode)
{
struct super_block *sb = inode->i_sb;
int nr;
inode->i_size = 0;
if (MSDOS_I(inode)->i_start == 0)
return;
return 0;
nr = MSDOS_I(inode)->i_start;
do {
inode->i_size += 1 << MSDOS_SB(sb)->cluster_bits;
if (!(nr = fat_access(sb, nr, -1))) {
printk("FAT: Directory %ld: bad FAT\n",
inode->i_ino);
break;
nr = fat_access(sb, nr, -1);
if (nr < 0)
return nr;
else if (nr == FAT_ENT_FREE) {
fat_fs_panic(sb, "Directory %lu: invalid cluster chain",
inode->i_ino);
return -EIO;
}
if (inode->i_size > FAT_MAX_DIR_SIZE) {
fat_fs_panic(sb, "Directory %ld: "
......@@ -382,13 +389,16 @@ static void fat_calc_dir_size(struct inode *inode)
inode->i_size = FAT_MAX_DIR_SIZE;
break;
}
} while (nr != -1);
} while (nr != FAT_ENT_EOF);
return 0;
}
static void fat_read_root(struct inode *inode)
static int fat_read_root(struct inode *inode)
{
struct super_block *sb = inode->i_sb;
struct msdos_sb_info *sbi = MSDOS_SB(sb);
int error;
MSDOS_I(inode)->i_location = 0;
inode->i_uid = sbi->options.fs_uid;
......@@ -400,7 +410,9 @@ static void fat_read_root(struct inode *inode)
inode->i_fop = &fat_dir_operations;
if (sbi->fat_bits == 32) {
MSDOS_I(inode)->i_start = sbi->root_cluster;
fat_calc_dir_size(inode);
error = fat_calc_dir_size(inode);
if (error < 0)
return error;
} else {
MSDOS_I(inode)->i_start = 0;
inode->i_size = sbi->dir_entries * sizeof(struct msdos_dir_entry);
......@@ -415,6 +427,8 @@ static void fat_read_root(struct inode *inode)
inode->i_mtime = inode->i_atime = inode->i_ctime = 0;
MSDOS_I(inode)->i_ctime_ms = 0;
inode->i_nlink = fat_subdirs(inode)+2;
return 0;
}
/*
......@@ -573,7 +587,7 @@ static struct super_operations fat_sops = {
int fat_fill_super(struct super_block *sb, void *data, int silent,
struct inode_operations *fs_dir_inode_ops, int isvfat)
{
struct inode *root_inode;
struct inode *root_inode = NULL;
struct buffer_head *bh;
struct fat_boot_sector *b;
struct msdos_sb_info *sbi;
......@@ -802,18 +816,20 @@ int fat_fill_super(struct super_block *sb, void *data, int silent,
sbi->nls_io->charset);
}
error = -ENOMEM;
root_inode = new_inode(sb);
if (!root_inode)
goto out_fail;
root_inode->i_ino = MSDOS_ROOT_INO;
root_inode->i_version = 0;
fat_read_root(root_inode);
error = fat_read_root(root_inode);
if (error < 0)
goto out_fail;
error = -ENOMEM;
insert_inode_hash(root_inode);
sb->s_root = d_alloc_root(root_inode);
if (!sb->s_root) {
printk("FAT: get root inode failed\n");
iput(root_inode);
goto out_fail;
}
if(i >= 0) {
......@@ -826,6 +842,8 @@ int fat_fill_super(struct super_block *sb, void *data, int silent,
error = -EINVAL;
out_fail:
if (root_inode)
iput(root_inode);
if (sbi->nls_io)
unload_nls(sbi->nls_io);
if (sbi->nls_disk)
......@@ -855,17 +873,20 @@ int fat_statfs(struct super_block *sb,struct statfs *buf)
free = MSDOS_SB(sb)->free_clusters;
else {
free = 0;
for (nr = 2; nr < MSDOS_SB(sb)->clusters+2; nr++)
if (!fat_access(sb,nr,-1)) free++;
for (nr = 2; nr < MSDOS_SB(sb)->clusters + 2; nr++)
if (fat_access(sb, nr, -1) == FAT_ENT_FREE)
free++;
MSDOS_SB(sb)->free_clusters = free;
}
unlock_fat(sb);
buf->f_type = sb->s_magic;
buf->f_bsize = 1 << MSDOS_SB(sb)->cluster_bits;
buf->f_blocks = MSDOS_SB(sb)->clusters;
buf->f_bfree = free;
buf->f_bavail = free;
buf->f_namelen = MSDOS_SB(sb)->options.isvfat ? 260 : 12;
return 0;
}
......@@ -906,10 +927,11 @@ static struct address_space_operations fat_aops = {
};
/* doesn't deal with root inode */
static void fat_fill_inode(struct inode *inode, struct msdos_dir_entry *de)
static int fat_fill_inode(struct inode *inode, struct msdos_dir_entry *de)
{
struct super_block *sb = inode->i_sb;
struct msdos_sb_info *sbi = MSDOS_SB(sb);
int error;
MSDOS_I(inode)->i_location = 0;
inode->i_uid = sbi->options.fs_uid;
......@@ -930,6 +952,11 @@ static void fat_fill_inode(struct inode *inode, struct msdos_dir_entry *de)
(CF_LE_W(de->starthi) << 16);
}
MSDOS_I(inode)->i_logstart = MSDOS_I(inode)->i_start;
error = fat_calc_dir_size(inode);
if (error < 0)
return error;
MSDOS_I(inode)->mmu_private = inode->i_size;
inode->i_nlink = fat_subdirs(inode);
/* includes .., compensating for "self" */
#ifdef DEBUG
......@@ -938,8 +965,6 @@ static void fat_fill_inode(struct inode *inode, struct msdos_dir_entry *de)
inode->i_nlink = 1;
}
#endif
fat_calc_dir_size(inode);
MSDOS_I(inode)->mmu_private = inode->i_size;
} else { /* not a directory */
inode->i_generation |= 1;
inode->i_mode = MSDOS_MKMODE(de->attr,
......@@ -974,6 +999,8 @@ static void fat_fill_inode(struct inode *inode, struct msdos_dir_entry *de)
? date_dos2unix(CF_LE_W(de->ctime),CF_LE_W(de->cdate))
: inode->i_mtime;
MSDOS_I(inode)->i_ctime_ms = de->ctime_ms;
return 0;
}
void fat_write_inode(struct inode *inode, int wait)
......
......@@ -128,6 +128,41 @@ int fat_add_cluster(struct inode *inode)
int count, nr, limit, last, curr, file_cluster;
int cluster_bits = MSDOS_SB(sb)->cluster_bits;
/*
* We must locate the last cluster of the file to add this new
* one (nr) to the end of the link list (the FAT).
*
* In order to confirm that the cluster chain is valid, we
* find out EOF first.
*/
nr = -EIO;
last = file_cluster = 0;
if ((curr = MSDOS_I(inode)->i_start) != 0) {
int max_cluster = MSDOS_I(inode)->mmu_private >> cluster_bits;
fat_cache_lookup(inode, INT_MAX, &last, &curr);
file_cluster = last;
while (curr && curr != FAT_ENT_EOF) {
file_cluster++;
curr = fat_access(sb, last = curr, -1);
if (curr < 0)
return curr;
else if (curr == FAT_ENT_FREE) {
fat_fs_panic(sb, "%s: invalid cluster chain"
" (ino %lu)",
__FUNCTION__, inode->i_ino);
goto out;
}
if (file_cluster > max_cluster) {
fat_fs_panic(sb, "%s: bad cluster counts"
" (ino %lu)",
__FUNCTION__, inode->i_ino);
goto out;
}
}
}
/* find free FAT entry */
lock_fat(sb);
if (MSDOS_SB(sb)->free_clusters == 0) {
......@@ -135,10 +170,9 @@ int fat_add_cluster(struct inode *inode)
return -ENOSPC;
}
limit = MSDOS_SB(sb)->clusters;
nr = limit; /* to keep GCC happy */
for (count = 0; count < limit; count++) {
nr = ((count + MSDOS_SB(sb)->prev_free) % limit) + 2;
if (fat_access(sb, nr, -1) == 0)
if (fat_access(sb, nr, -1) == FAT_ENT_FREE)
break;
}
if (count >= limit) {
......@@ -148,43 +182,15 @@ int fat_add_cluster(struct inode *inode)
}
MSDOS_SB(sb)->prev_free = (count + MSDOS_SB(sb)->prev_free + 1) % limit;
fat_access(sb, nr, EOF_FAT(sb));
fat_access(sb, nr, FAT_ENT_EOF);
if (MSDOS_SB(sb)->free_clusters != -1)
MSDOS_SB(sb)->free_clusters--;
if (MSDOS_SB(sb)->fat_bits == 32)
fat_clusters_flush(sb);
unlock_fat(sb);
/* We must locate the last cluster of the file to add this
new one (nr) to the end of the link list (the FAT).
Here file_cluster will be the number of the last cluster of the
file (before we add nr).
last is the corresponding cluster number on the disk. We will
use last to plug the nr cluster. We will use file_cluster to
update the cache.
*/
last = file_cluster = 0;
if ((curr = MSDOS_I(inode)->i_start) != 0) {
int max_cluster = MSDOS_I(inode)->mmu_private >> cluster_bits;
fat_cache_lookup(inode, INT_MAX, &last, &curr);
file_cluster = last;
while (curr && curr != -1) {
file_cluster++;
if (!(curr = fat_access(sb, last = curr, -1))) {
fat_fs_panic(sb, "File without EOF");
return -EIO;
}
if (file_cluster > max_cluster) {
fat_fs_panic(sb,"inode %lu: bad cluster counts",
inode->i_ino);
return -EIO;
}
}
}
/* add new one to the last of the cluster chain */
if (last) {
fat_access(sb, last, nr);
fat_cache_add(inode, file_cluster, nr);
......@@ -200,6 +206,7 @@ int fat_add_cluster(struct inode *inode)
}
inode->i_blocks += (1 << cluster_bits) >> 9;
out:
return nr;
}
......@@ -330,33 +337,33 @@ int fat__get_entry(struct inode *dir, loff_t *pos,struct buffer_head **bh,
{
struct super_block *sb = dir->i_sb;
struct msdos_sb_info *sbi = MSDOS_SB(sb);
int sector, offset;
while (1) {
offset = *pos;
PRINTK (("get_entry offset %d\n",offset));
if (*bh)
fat_brelse(sb, *bh);
*bh = NULL;
if ((sector = fat_bmap(dir,offset >> sb->s_blocksize_bits)) == -1)
return -1;
PRINTK (("get_entry sector %d %p\n",sector,*bh));
PRINTK (("get_entry sector apres brelse\n"));
if (!sector)
return -1; /* beyond EOF */
*pos += sizeof(struct msdos_dir_entry);
if (!(*bh = fat_bread(sb, sector))) {
printk("Directory sread (sector 0x%x) failed\n",sector);
continue;
}
PRINTK (("get_entry apres sread\n"));
int sector, offset, iblock;
next:
offset = *pos;
if (*bh)
fat_brelse(sb, *bh);
*bh = NULL;
iblock = *pos >> sb->s_blocksize_bits;
sector = fat_bmap(dir, iblock);
if (sector <= 0)
return -1; /* beyond EOF or error */
*bh = fat_bread(sb, sector);
if (*bh == NULL) {
printk("FAT: Directory bread(block %d) failed\n", sector);
/* skip this block */
*pos = (iblock + 1) << sb->s_blocksize_bits;
goto next;
}
offset &= sb->s_blocksize - 1;
*de = (struct msdos_dir_entry *) ((*bh)->b_data + offset);
*ino = (sector << sbi->dir_per_block_bits) + (offset >> MSDOS_DIR_BITS);
offset &= sb->s_blocksize - 1;
*pos += sizeof(struct msdos_dir_entry);
*de = (struct msdos_dir_entry *)((*bh)->b_data + offset);
*ino = (sector << sbi->dir_per_block_bits) + (offset >> MSDOS_DIR_BITS);
return 0;
}
return 0;
}
......@@ -503,14 +510,18 @@ static int raw_scan_nonroot(struct super_block *sb,int start,const char *name,
start);
break;
}
if (!(start = fat_access(sb,start,-1))) {
fat_fs_panic(sb,"FAT error");
start = fat_access(sb, start, -1);
if (start < 0)
return start;
else if (start == FAT_ENT_FREE) {
fat_fs_panic(sb, "%s: invalid cluster chain",
__FUNCTION__);
break;
}
#ifdef DEBUG
printk("next start: %d\n",start);
#endif
} while (start != -1);
} while (start != FAT_ENT_EOF);
return -ENOENT;
}
......
......@@ -54,12 +54,24 @@
#define MSDOS_FAT12 4084 /* maximum number of clusters in a 12 bit FAT */
#define EOF_FAT12 0xFF8 /* standard EOF */
/* bad cluster mark */
#define BAD_FAT12 0xFF7
#define BAD_FAT16 0xFFF7
#define BAD_FAT32 0xFFFFFF7
#define BAD_FAT(s) (MSDOS_SB(s)->fat_bits == 32 ? BAD_FAT32 : \
MSDOS_SB(s)->fat_bits == 16 ? BAD_FAT16 : BAD_FAT12)
/* standard EOF */
#define EOF_FAT12 0xFF8
#define EOF_FAT16 0xFFF8
#define EOF_FAT32 0xFFFFFF8
#define EOF_FAT(s) (MSDOS_SB(s)->fat_bits == 32 ? EOF_FAT32 : \
MSDOS_SB(s)->fat_bits == 16 ? EOF_FAT16 : EOF_FAT12)
#define FAT_ENT_FREE (0)
#define FAT_ENT_BAD (BAD_FAT32)
#define FAT_ENT_EOF (EOF_FAT32)
#define FAT_FSINFO_SIG1 0x41615252
#define FAT_FSINFO_SIG2 0x61417272
#define IS_FSINFO(x) (CF_LE_L((x)->signature1) == FAT_FSINFO_SIG1 \
......@@ -237,7 +249,6 @@ extern void fat_cache_lookup(struct inode *inode, int cluster, int *f_clu,
extern void fat_cache_add(struct inode *inode, int f_clu, int d_clu);
extern void fat_cache_inval_inode(struct inode *inode);
extern void fat_cache_inval_dev(struct super_block *sb);
extern int fat_get_cluster(struct inode *inode, int cluster);
extern int fat_free(struct inode *inode, int skip);
/* fat/dir.c */
......
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