RE: [PATCH] exfat: handle wrong stream entry size in exfat_readdir()

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



> > > The compatibility issue between linux exfat and exfat of some camera
> > > company was reported from Florian. In their exfat, if the number of
> > > files exceeds any limit, the DataLength in stream entry of the
> > > directory is no longer updated. So some files created from camera
> > > does not show in linux exfat. because linux exfat doesn't allow that
> > > cpos becomes larger than DataLength
> > of stream entry. This patch check DataLength in stream entry only if
> > the type is ALLOC_NO_FAT_CHAIN and add the check ensure that dentry
> > offset does not exceed max dentries size(256 MB) to avoid the circular FAT chain issue.
> >
> > Instead of using fsd to handle this, shouldn't it be left to fsck?
> Yes, That's what I thought at first. And fsck.exfat in exfatprogs can detect it like this.
> 
> $ fsck.exfat /dev/sdb1
> exfatprogs version : 1.1.1
> ERROR: /DCIM/344_FUJI: more clusters are allocated. truncate to 524288 bytes. Truncate (y/N)? n
> 
> >
> > In the exfat specification says, the DataLength Field of the
> > directory-stream is the entire size of the associated allocation.
> > If the DataLength Field does not match the size in the FAT-chain, it means that it is corrupted.
> Yes. I have checked it.
> >
> > As you know, the FAT-chain structure is fragile.
> > At runtime, one way to detect a broken FAT-chain is to compare it with DataLength.
> > (Detailed verification is the role of fsck).
> > Ignoring DataLength during dir-scan is unsafe because we lose a way to detect a broken FAT-chain.
> >
> > I think fsd should check DataLength, and fsck should repair DataLength.
> But Windows fsck doesn’t detect it and it shows the all files normally without any missing ones.
> It means Windows exfat doesn't also check it in case type is ALLOC_FAT_CHAIN.

Okay, I get it.
Because if our driver aborts scanning, the files after that will not be visible.
If FAT-Chain is correct and DataLenth is incorrect,  this patch works safely.(as the case of Fuji)
However, if DataLenth is correct and FAT-Chain is incorrect, it is unsafe.
There is a risk of destroying other files/dirs.

The current implementation sets SB_RDONLY when it detects a directories FAT-Chain count and DataLenth contradiction in exfat_find_last_cluster( ).
It only works only on exfat_add_entry()/exfat_rename_file()/exfat_move_file().

Why don't we do a similar check for exfat_readdir()/exfat_find_dir_entry()?
Just do exfat_fs_error() if the dentry position exceeds DataLenth in the scan loop.
This will reduces the risk of destroying other files/dirs and prompts the user for fsck.


> Fixes: ca06197382bd ("exfat: add directory operations")
> Cc: stable@xxxxxxxxxxxxxxx # v5.9
> Reported-by: Florian Cramer <flrncrmr@xxxxxxxxx>
> Reviewed-by: Sungjong Seo <sj1557.seo@xxxxxxxxxxx>
> Signed-off-by: Namjae Jeon <namjae.jeon@xxxxxxxxxxx>
> ---
>  fs/exfat/dir.c | 8 +++++---
>  1 file changed, 5 insertions(+), 3 deletions(-)
> 
> diff --git a/fs/exfat/dir.c b/fs/exfat/dir.c index c4523648472a..f4e4d8d9894d 100644
> --- a/fs/exfat/dir.c
> +++ b/fs/exfat/dir.c
> @@ -63,7 +63,7 @@ static void exfat_get_uniname_from_ext_entry(struct super_block *sb,  static int
> exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_entry *dir_entry)  {
>  	int i, dentries_per_clu, dentries_per_clu_bits = 0, num_ext;
> -	unsigned int type, clu_offset;
> +	unsigned int type, clu_offset, max_dentries;
>  	sector_t sector;
>  	struct exfat_chain dir, clu;
>  	struct exfat_uni_name uni_name;
> @@ -86,6 +86,8 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent
> 
>  	dentries_per_clu = sbi->dentries_per_clu;
>  	dentries_per_clu_bits = ilog2(dentries_per_clu);
> +	max_dentries = (unsigned int)min_t(u64, MAX_EXFAT_DENTRIES,
> +					   (u64)sbi->num_clusters << dentries_per_clu_bits);
> 
>  	clu_offset = dentry >> dentries_per_clu_bits;
>  	exfat_chain_dup(&clu, &dir);
> @@ -109,7 +111,7 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent
>  		}
>  	}
> 
> -	while (clu.dir != EXFAT_EOF_CLUSTER) {
> +	while (clu.dir != EXFAT_EOF_CLUSTER && dentry < max_dentries) {
>  		i = dentry & (dentries_per_clu - 1);
> 
>  		for ( ; i < dentries_per_clu; i++, dentry++) { @@ -245,7 +247,7 @@ static int exfat_iterate(struct file *filp,
> struct dir_context *ctx)
>  	if (err)
>  		goto unlock;
>  get_new:
> -	if (cpos >= i_size_read(inode))
> +	if (ei->flags == ALLOC_NO_FAT_CHAIN && cpos >= i_size_read(inode))
>  		goto end_of_dir;

This check is no longer necessary.
In the case of ALLOC_NO_FAT_CHAIN, there is a check for i_size in exfat_readdir(), which is a double check.
In the past, it was possible to skip chain tracking in the case of ALLOC_FAT_CHAIN, but that effect is also gone.

> 
>  	err = exfat_readdir(inode, &cpos, &de);


BR
Kohada Tetsuhiro <Kohada.Tetsuhiro@xxxxxxxxxxxxxxxxxxxxxxxxxxx>




[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [Samba]     [Device Mapper]     [CEPH Development]

  Powered by Linux