| From b642b52d0b50f4d398cb4293f64992d0eed2e2ce Mon Sep 17 00:00:00 2001 |
| From: Ethan Lien <ethanlien@synology.com> |
| Date: Mon, 7 Mar 2022 18:00:04 +0800 |
| Subject: btrfs: fix qgroup reserve overflow the qgroup limit |
| MIME-Version: 1.0 |
| Content-Type: text/plain; charset=UTF-8 |
| Content-Transfer-Encoding: 8bit |
| |
| From: Ethan Lien <ethanlien@synology.com> |
| |
| commit b642b52d0b50f4d398cb4293f64992d0eed2e2ce upstream. |
| |
| We use extent_changeset->bytes_changed in qgroup_reserve_data() to record |
| how many bytes we set for EXTENT_QGROUP_RESERVED state. Currently the |
| bytes_changed is set as "unsigned int", and it will overflow if we try to |
| fallocate a range larger than 4GiB. The result is we reserve less bytes |
| and eventually break the qgroup limit. |
| |
| Unlike regular buffered/direct write, which we use one changeset for |
| each ordered extent, which can never be larger than 256M. For |
| fallocate, we use one changeset for the whole range, thus it no longer |
| respects the 256M per extent limit, and caused the problem. |
| |
| The following example test script reproduces the problem: |
| |
| $ cat qgroup-overflow.sh |
| #!/bin/bash |
| |
| DEV=/dev/sdj |
| MNT=/mnt/sdj |
| |
| mkfs.btrfs -f $DEV |
| mount $DEV $MNT |
| |
| # Set qgroup limit to 2GiB. |
| btrfs quota enable $MNT |
| btrfs qgroup limit 2G $MNT |
| |
| # Try to fallocate a 3GiB file. This should fail. |
| echo |
| echo "Try to fallocate a 3GiB file..." |
| fallocate -l 3G $MNT/3G.file |
| |
| # Try to fallocate a 5GiB file. |
| echo |
| echo "Try to fallocate a 5GiB file..." |
| fallocate -l 5G $MNT/5G.file |
| |
| # See we break the qgroup limit. |
| echo |
| sync |
| btrfs qgroup show -r $MNT |
| |
| umount $MNT |
| |
| When running the test: |
| |
| $ ./qgroup-overflow.sh |
| (...) |
| |
| Try to fallocate a 3GiB file... |
| fallocate: fallocate failed: Disk quota exceeded |
| |
| Try to fallocate a 5GiB file... |
| |
| qgroupid rfer excl max_rfer |
| -------- ---- ---- -------- |
| 0/5 5.00GiB 5.00GiB 2.00GiB |
| |
| Since we have no control of how bytes_changed is used, it's better to |
| set it to u64. |
| |
| CC: stable@vger.kernel.org # 4.14+ |
| Reviewed-by: Qu Wenruo <wqu@suse.com> |
| Signed-off-by: Ethan Lien <ethanlien@synology.com> |
| Reviewed-by: David Sterba <dsterba@suse.com> |
| Signed-off-by: David Sterba <dsterba@suse.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| fs/btrfs/extent_io.h | 2 +- |
| 1 file changed, 1 insertion(+), 1 deletion(-) |
| |
| --- a/fs/btrfs/extent_io.h |
| +++ b/fs/btrfs/extent_io.h |
| @@ -118,7 +118,7 @@ struct btrfs_bio_ctrl { |
| */ |
| struct extent_changeset { |
| /* How many bytes are set/cleared in this operation */ |
| - unsigned int bytes_changed; |
| + u64 bytes_changed; |
| |
| /* Changed ranges */ |
| struct ulist range_changed; |