diff mbox series

[1/2] btrfs-progs: check: Detect file extent end overflow

Message ID 20200219070443.43189-1-wqu@suse.com (mailing list archive)
State New, archived
Headers show
Series [1/2] btrfs-progs: check: Detect file extent end overflow | expand

Commit Message

Qu Wenruo Feb. 19, 2020, 7:04 a.m. UTC
There is a report about tree-checker rejecting some leaves due to bad
EXTENT_DATA.

The offending EXTENT_DATA looks like:
	item 72 key (1359622 EXTENT_DATA 475136) itemoff 11140 itemsize 53
		generation 954719 type 1 (regular)
		extent data disk byte 0 nr 0
		extent data offset 0 nr 18446744073709486080 ram 18446744073709486080
		extent compression 0 (none)

Add such check to both original and lowmem mode to detect such problem.

Reported-by: Samir Benmendil <me@rmz.io>
Signed-off-by: Qu Wenruo <wqu@suse.com>
---
 check/main.c          | 4 ++++
 check/mode-common.h   | 7 +++++++
 check/mode-lowmem.c   | 7 +++++++
 check/mode-original.h | 1 +
 4 files changed, 19 insertions(+)

Comments

Nikolay Borisov Feb. 19, 2020, 9:19 a.m. UTC | #1
On 19.02.20 г. 9:04 ч., Qu Wenruo wrote:
> There is a report about tree-checker rejecting some leaves due to bad
> EXTENT_DATA.
> 
> The offending EXTENT_DATA looks like:
> 	item 72 key (1359622 EXTENT_DATA 475136) itemoff 11140 itemsize 53
> 		generation 954719 type 1 (regular)
> 		extent data disk byte 0 nr 0
> 		extent data offset 0 nr 18446744073709486080 ram 18446744073709486080
> 		extent compression 0 (none)
> 
> Add such check to both original and lowmem mode to detect such problem.
> 
> Reported-by: Samir Benmendil <me@rmz.io>
> Signed-off-by: Qu Wenruo <wqu@suse.com>
> ---
>  check/main.c          | 4 ++++
>  check/mode-common.h   | 7 +++++++
>  check/mode-lowmem.c   | 7 +++++++
>  check/mode-original.h | 1 +
>  4 files changed, 19 insertions(+)
> 
> diff --git a/check/main.c b/check/main.c
> index d02dd1636852..f71bf4f74129 100644
> --- a/check/main.c
> +++ b/check/main.c
> @@ -597,6 +597,8 @@ static void print_inode_error(struct btrfs_root *root, struct inode_record *rec)
>  		fprintf(stderr, ", odd file extent");
>  	if (errors & I_ERR_BAD_FILE_EXTENT)
>  		fprintf(stderr, ", bad file extent");
> +	if (errors & I_ERR_FILE_EXTENT_OVERFLOW)
> +		fprintf(stderr, ", file extent end overflow");
>  	if (errors & I_ERR_FILE_EXTENT_OVERLAP)
>  		fprintf(stderr, ", file extent overlap");
>  	if (errors & I_ERR_FILE_EXTENT_TOO_LARGE)
> @@ -1595,6 +1597,8 @@ static int process_file_extent(struct btrfs_root *root,
>  		num_bytes = btrfs_file_extent_num_bytes(eb, fi);
>  		disk_bytenr = btrfs_file_extent_disk_bytenr(eb, fi);
>  		extent_offset = btrfs_file_extent_offset(eb, fi);
> +		if (u64_add_overflow(key->offset, num_bytes))
> +			rec->errors |= I_ERR_FILE_EXTENT_OVERFLOW;
>  		if (num_bytes == 0 || (num_bytes & mask))
>  			rec->errors |= I_ERR_BAD_FILE_EXTENT;
>  		if (num_bytes + extent_offset >
> diff --git a/check/mode-common.h b/check/mode-common.h
> index edf9257edaf0..daa5741e1d67 100644
> --- a/check/mode-common.h
> +++ b/check/mode-common.h
> @@ -173,4 +173,11 @@ static inline u32 btrfs_type_to_imode(u8 type)
>  
>  	return imode_by_btrfs_type[(type)];
>  }
> +
> +static inline bool u64_add_overflow(u64 a, u64 b)

Rename this to check_add_overflow and use the generic version from the
kernel :

#define check_add_overflow(a, b, d) ({          \

        typeof(a) __a = (a);                    \

        typeof(b) __b = (b);                    \

        typeof(d) __d = (d);                    \

        (void) (&__a == &__b);                  \

        (void) (&__a == __d);                   \

        __builtin_add_overflow(__a, __b, __d);  \

})

> +{
> +	if (a > (u64)-1 - b)
> +		return true;
> +	return false;
> +}
>  #endif
> diff --git a/check/mode-lowmem.c b/check/mode-lowmem.c
> index 630fabf66919..d257a44f3086 100644
> --- a/check/mode-lowmem.c
> +++ b/check/mode-lowmem.c
> @@ -2085,6 +2085,13 @@ static int check_file_extent(struct btrfs_root *root, struct btrfs_path *path,
>  		err |= INVALID_GENERATION;
>  	}
>  
> +	/* Extent end shouldn't overflow */
> +	if (u64_add_overflow(fkey.offset, extent_num_bytes)) {
> +		error(
> +	"file extent end over flow, file offset %llu extent num bytes %llu",
> +			fkey.offset, extent_num_bytes);
> +		err |= FILE_EXTENT_ERROR;
> +	}
>  	/*
>  	 * Check EXTENT_DATA csum
>  	 *
> diff --git a/check/mode-original.h b/check/mode-original.h
> index b075a95c9757..07d741f4a1b5 100644
> --- a/check/mode-original.h
> +++ b/check/mode-original.h
> @@ -186,6 +186,7 @@ struct unaligned_extent_rec_t {
>  #define I_ERR_MISMATCH_DIR_HASH		(1 << 18)
>  #define I_ERR_INVALID_IMODE		(1 << 19)
>  #define I_ERR_INVALID_GEN		(1 << 20)
> +#define I_ERR_FILE_EXTENT_OVERFLOW	(1 << 21)
>  
>  struct inode_record {
>  	struct list_head backrefs;
>
Qu Wenruo Feb. 19, 2020, 11:37 a.m. UTC | #2
On 2020/2/19 下午5:19, Nikolay Borisov wrote:
>
>
> On 19.02.20 г. 9:04 ч., Qu Wenruo wrote:
>> There is a report about tree-checker rejecting some leaves due to bad
>> EXTENT_DATA.
>>
>> The offending EXTENT_DATA looks like:
>> 	item 72 key (1359622 EXTENT_DATA 475136) itemoff 11140 itemsize 53
>> 		generation 954719 type 1 (regular)
>> 		extent data disk byte 0 nr 0
>> 		extent data offset 0 nr 18446744073709486080 ram 18446744073709486080
>> 		extent compression 0 (none)
>>
>> Add such check to both original and lowmem mode to detect such problem.
>>
>> Reported-by: Samir Benmendil <me@rmz.io>
>> Signed-off-by: Qu Wenruo <wqu@suse.com>
>> ---
>>  check/main.c          | 4 ++++
>>  check/mode-common.h   | 7 +++++++
>>  check/mode-lowmem.c   | 7 +++++++
>>  check/mode-original.h | 1 +
>>  4 files changed, 19 insertions(+)
>>
>> diff --git a/check/main.c b/check/main.c
>> index d02dd1636852..f71bf4f74129 100644
>> --- a/check/main.c
>> +++ b/check/main.c
>> @@ -597,6 +597,8 @@ static void print_inode_error(struct btrfs_root *root, struct inode_record *rec)
>>  		fprintf(stderr, ", odd file extent");
>>  	if (errors & I_ERR_BAD_FILE_EXTENT)
>>  		fprintf(stderr, ", bad file extent");
>> +	if (errors & I_ERR_FILE_EXTENT_OVERFLOW)
>> +		fprintf(stderr, ", file extent end overflow");
>>  	if (errors & I_ERR_FILE_EXTENT_OVERLAP)
>>  		fprintf(stderr, ", file extent overlap");
>>  	if (errors & I_ERR_FILE_EXTENT_TOO_LARGE)
>> @@ -1595,6 +1597,8 @@ static int process_file_extent(struct btrfs_root *root,
>>  		num_bytes = btrfs_file_extent_num_bytes(eb, fi);
>>  		disk_bytenr = btrfs_file_extent_disk_bytenr(eb, fi);
>>  		extent_offset = btrfs_file_extent_offset(eb, fi);
>> +		if (u64_add_overflow(key->offset, num_bytes))
>> +			rec->errors |= I_ERR_FILE_EXTENT_OVERFLOW;
>>  		if (num_bytes == 0 || (num_bytes & mask))
>>  			rec->errors |= I_ERR_BAD_FILE_EXTENT;
>>  		if (num_bytes + extent_offset >
>> diff --git a/check/mode-common.h b/check/mode-common.h
>> index edf9257edaf0..daa5741e1d67 100644
>> --- a/check/mode-common.h
>> +++ b/check/mode-common.h
>> @@ -173,4 +173,11 @@ static inline u32 btrfs_type_to_imode(u8 type)
>>
>>  	return imode_by_btrfs_type[(type)];
>>  }
>> +
>> +static inline bool u64_add_overflow(u64 a, u64 b)
>
> Rename this to check_add_overflow and use the generic version from the
> kernel :

That's also my first idea.

But I'm not a fan of the 3rd parameter, and there is no other type other
than u64, so I hesitate to use the generic one.

However since you mentioned the kernel one, I guess it's time to
backport it to user space.

Thanks,
Qu

>
> #define check_add_overflow(a, b, d) ({          \
>
>         typeof(a) __a = (a);                    \
>
>         typeof(b) __b = (b);                    \
>
>         typeof(d) __d = (d);                    \
>
>         (void) (&__a == &__b);                  \
>
>         (void) (&__a == __d);                   \
>
>         __builtin_add_overflow(__a, __b, __d);  \
>
> })
>
>> +{
>> +	if (a > (u64)-1 - b)
>> +		return true;
>> +	return false;
>> +}
>>  #endif
>> diff --git a/check/mode-lowmem.c b/check/mode-lowmem.c
>> index 630fabf66919..d257a44f3086 100644
>> --- a/check/mode-lowmem.c
>> +++ b/check/mode-lowmem.c
>> @@ -2085,6 +2085,13 @@ static int check_file_extent(struct btrfs_root *root, struct btrfs_path *path,
>>  		err |= INVALID_GENERATION;
>>  	}
>>
>> +	/* Extent end shouldn't overflow */
>> +	if (u64_add_overflow(fkey.offset, extent_num_bytes)) {
>> +		error(
>> +	"file extent end over flow, file offset %llu extent num bytes %llu",
>> +			fkey.offset, extent_num_bytes);
>> +		err |= FILE_EXTENT_ERROR;
>> +	}
>>  	/*
>>  	 * Check EXTENT_DATA csum
>>  	 *
>> diff --git a/check/mode-original.h b/check/mode-original.h
>> index b075a95c9757..07d741f4a1b5 100644
>> --- a/check/mode-original.h
>> +++ b/check/mode-original.h
>> @@ -186,6 +186,7 @@ struct unaligned_extent_rec_t {
>>  #define I_ERR_MISMATCH_DIR_HASH		(1 << 18)
>>  #define I_ERR_INVALID_IMODE		(1 << 19)
>>  #define I_ERR_INVALID_GEN		(1 << 20)
>> +#define I_ERR_FILE_EXTENT_OVERFLOW	(1 << 21)
>>
>>  struct inode_record {
>>  	struct list_head backrefs;
>>
David Sterba Feb. 21, 2020, 1:40 p.m. UTC | #3
On Wed, Feb 19, 2020 at 07:37:02PM +0800, Qu Wenruo wrote:
> >> @@ -1595,6 +1597,8 @@ static int process_file_extent(struct btrfs_root *root,
> >>  		num_bytes = btrfs_file_extent_num_bytes(eb, fi);
> >>  		disk_bytenr = btrfs_file_extent_disk_bytenr(eb, fi);
> >>  		extent_offset = btrfs_file_extent_offset(eb, fi);
> >> +		if (u64_add_overflow(key->offset, num_bytes))
> >> +			rec->errors |= I_ERR_FILE_EXTENT_OVERFLOW;
> >>  		if (num_bytes == 0 || (num_bytes & mask))
> >>  			rec->errors |= I_ERR_BAD_FILE_EXTENT;
> >>  		if (num_bytes + extent_offset >
> >> diff --git a/check/mode-common.h b/check/mode-common.h
> >> index edf9257edaf0..daa5741e1d67 100644
> >> --- a/check/mode-common.h
> >> +++ b/check/mode-common.h
> >> @@ -173,4 +173,11 @@ static inline u32 btrfs_type_to_imode(u8 type)
> >>
> >>  	return imode_by_btrfs_type[(type)];
> >>  }
> >> +
> >> +static inline bool u64_add_overflow(u64 a, u64 b)
> >
> > Rename this to check_add_overflow and use the generic version from the
> > kernel :
> 
> That's also my first idea.
> 
> But I'm not a fan of the 3rd parameter, and there is no other type other
> than u64, so I hesitate to use the generic one.
> 
> However since you mentioned the kernel one, I guess it's time to
> backport it to user space.

Yes please, copying the support code from kernel is ok.
diff mbox series

Patch

diff --git a/check/main.c b/check/main.c
index d02dd1636852..f71bf4f74129 100644
--- a/check/main.c
+++ b/check/main.c
@@ -597,6 +597,8 @@  static void print_inode_error(struct btrfs_root *root, struct inode_record *rec)
 		fprintf(stderr, ", odd file extent");
 	if (errors & I_ERR_BAD_FILE_EXTENT)
 		fprintf(stderr, ", bad file extent");
+	if (errors & I_ERR_FILE_EXTENT_OVERFLOW)
+		fprintf(stderr, ", file extent end overflow");
 	if (errors & I_ERR_FILE_EXTENT_OVERLAP)
 		fprintf(stderr, ", file extent overlap");
 	if (errors & I_ERR_FILE_EXTENT_TOO_LARGE)
@@ -1595,6 +1597,8 @@  static int process_file_extent(struct btrfs_root *root,
 		num_bytes = btrfs_file_extent_num_bytes(eb, fi);
 		disk_bytenr = btrfs_file_extent_disk_bytenr(eb, fi);
 		extent_offset = btrfs_file_extent_offset(eb, fi);
+		if (u64_add_overflow(key->offset, num_bytes))
+			rec->errors |= I_ERR_FILE_EXTENT_OVERFLOW;
 		if (num_bytes == 0 || (num_bytes & mask))
 			rec->errors |= I_ERR_BAD_FILE_EXTENT;
 		if (num_bytes + extent_offset >
diff --git a/check/mode-common.h b/check/mode-common.h
index edf9257edaf0..daa5741e1d67 100644
--- a/check/mode-common.h
+++ b/check/mode-common.h
@@ -173,4 +173,11 @@  static inline u32 btrfs_type_to_imode(u8 type)
 
 	return imode_by_btrfs_type[(type)];
 }
+
+static inline bool u64_add_overflow(u64 a, u64 b)
+{
+	if (a > (u64)-1 - b)
+		return true;
+	return false;
+}
 #endif
diff --git a/check/mode-lowmem.c b/check/mode-lowmem.c
index 630fabf66919..d257a44f3086 100644
--- a/check/mode-lowmem.c
+++ b/check/mode-lowmem.c
@@ -2085,6 +2085,13 @@  static int check_file_extent(struct btrfs_root *root, struct btrfs_path *path,
 		err |= INVALID_GENERATION;
 	}
 
+	/* Extent end shouldn't overflow */
+	if (u64_add_overflow(fkey.offset, extent_num_bytes)) {
+		error(
+	"file extent end over flow, file offset %llu extent num bytes %llu",
+			fkey.offset, extent_num_bytes);
+		err |= FILE_EXTENT_ERROR;
+	}
 	/*
 	 * Check EXTENT_DATA csum
 	 *
diff --git a/check/mode-original.h b/check/mode-original.h
index b075a95c9757..07d741f4a1b5 100644
--- a/check/mode-original.h
+++ b/check/mode-original.h
@@ -186,6 +186,7 @@  struct unaligned_extent_rec_t {
 #define I_ERR_MISMATCH_DIR_HASH		(1 << 18)
 #define I_ERR_INVALID_IMODE		(1 << 19)
 #define I_ERR_INVALID_GEN		(1 << 20)
+#define I_ERR_FILE_EXTENT_OVERFLOW	(1 << 21)
 
 struct inode_record {
 	struct list_head backrefs;