Re: [PATCH] btrfs: do not leak -ERESTARTSYS from FITRIM to userspace

From: Qu Wenruo

Date: Wed Oct 28 2026 - 04:58:03 EST




在 2026/10/28 19:21, Luca Stefani 写道:
On Wed, 28 Oct 2026 at 07:57, Zhiwei Liang via B4 Relay
<devnull+zhiwei.liang.zliang.me@xxxxxxxxxx> wrote:

From: Zhiwei Liang <zhiwei.liang@xxxxxxxxx>

When a suspend starts during FITRIM, fstrim can fail with:

fstrim: /: FITRIM ioctl failed: Unknown error 512

On a Fedora 44 laptop with kernel 7.2.7, 3 of the last 21 weekly
fstrim.service runs were in progress when an automatic suspend started.
All three failed this way and left the filesystem untrimmed.

Since commit 69313850dce3 ("btrfs: add cancellation points to trim
loops"), the trim loops return -ERESTARTSYS when
btrfs_trim_interrupted() sees freezing(current), and FITRIM returns it
to userspace. Since commit bfb670b9183b ("btrfs: handle user interrupt
properly in btrfs_trim_fs()"), btrfs_trim_fs() returns it right away.

-ERESTARTSYS is only valid while a signal is pending, because only the
signal handling on syscall exit turns it into a restart. But
freeze_processes() first sets pm_freezing, which makes freezing() true
for every user task. Then it walks the task list and sends each task a
fake signal. The trim loops check btrfs_trim_interrupted() after every
extent. If a check falls in this window, the loop returns -ERESTARTSYS
with no signal pending. Nothing restarts the syscall. On that laptop,
with a few thousand threads, freezing user space took 2 to 5 ms in each
of the three suspends above, and all three runs hit this window.

Make btrfs_trim_interrupted() treat a freeze as an interruption only
once the fake signal has arrived, i.e. when
freezing(current) && signal_pending(current). freeze_processes() sends
that signal in the same call. The trim loop stops at the next check
after it, so suspend is not delayed.
Looking at how ext4 handles this scenario it seems to me they don't set
the error code to -ERESTARTSYS and (from a quick glance) return the
bytes discarded to the caller.
And like before they continue the discard (which will skip/fail again) until
there's nothing to be done.

I don't know what the intention of btrfs: handle user interrupt
properly in btrfs_trim_fs()
was, maybe we should just swallow the ERESTARTSYS case in those scenarios.

I agree on that btrfs is the exception, we should follow ext4/xfs, not rely on some tricky signal handling behavior for suspension.

I think the tokens of the author should be spent on unifying the btrfs fstrim suspension behavior instead.



The restarted syscall re-reads struct fstrim_range from user memory.
When the device phase is interrupted, btrfs_trim_fs() has already stored
the trimmed byte count in range->len, and btrfs_ioctl_fitrim() copies
the struct back unconditionally. The restart is then asked to trim
[start, start + trimmed) instead of the requested range, or fails with
-EINVAL when nothing was counted yet. Skip the copy_to_user() for
-ERESTARTSYS. In all other cases the partial count is still copied back.

Tested in a VM with pm_test=freezer. Without the patch, 18 of 20 freezes
during a trim leaked errno 512, and 10 of 10 restarts after a
device-phase interruption failed with -EINVAL. With the patch, all 30
runs restarted and trimmed the full amount.

Fixes: 69313850dce3 ("btrfs: add cancellation points to trim loops")
CC: stable@xxxxxxxxxxxxxxx # 5.15+
Signed-off-by: Zhiwei Liang <zhiwei.liang@xxxxxxxxx>
---
fs/btrfs/free-space-cache.h | 3 ++-
fs/btrfs/ioctl.c | 7 +++++++
2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/fs/btrfs/free-space-cache.h b/fs/btrfs/free-space-cache.h
index e22443598b..cba2f5fd6b 100644
--- a/fs/btrfs/free-space-cache.h
+++ b/fs/btrfs/free-space-cache.h
@@ -58,7 +58,8 @@ static inline bool btrfs_free_space_trimming_bitmap(

static inline bool btrfs_trim_interrupted(void)
{
- return fatal_signal_pending(current) || freezing(current);
+ return fatal_signal_pending(current) ||
+ (freezing(current) && signal_pending(current));
}

/*
diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
index ac070c745b..b02c59cbb0 100644
--- a/fs/btrfs/ioctl.c
+++ b/fs/btrfs/ioctl.c
@@ -529,6 +529,13 @@ static noinline int btrfs_ioctl_fitrim(struct btrfs_fs_info *fs_info,
range.minlen = max(range.minlen, minlen);
ret = btrfs_trim_fs(fs_info, &range);

+ /*
+ * A restart re-reads the range from user space, so do not clobber it
+ * with the partial len the rerun would otherwise take as its input.
+ */
+ if (ret == -ERESTARTSYS)
+ return ret;
+
if (copy_to_user(arg, &range, sizeof(range)))
return -EFAULT;


---
base-commit: e17e64baf63f7c85bec209ed73f14c1b2a7e39fb
change-id: 20261028-btrfs-fitrim-freezer-d4cba16e7e13

Best regards,
--
Zhiwei Liang <zhiwei.liang@xxxxxxxxx>