summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fs/btrfs/extent_io.c31
-rw-r--r--fs/btrfs/extent_io.h5
-rw-r--r--fs/btrfs/inode.c23
3 files changed, 53 insertions, 6 deletions
diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c
index eadd8d205411..80e6aaf72e5a 100644
--- a/fs/btrfs/extent_io.c
+++ b/fs/btrfs/extent_io.c
@@ -6,6 +6,7 @@
#include <linux/mm.h>
#include <linux/pagemap.h>
#include <linux/page-flags.h>
+#include <linux/rmap.h>
#include <linux/sched/mm.h>
#include <linux/spinlock.h>
#include <linux/blkdev.h>
@@ -299,6 +300,25 @@ static noinline void unlock_delalloc_folio(const struct inode *inode,
PAGE_UNLOCK);
}
+#ifdef CONFIG_BTRFS_DEBUG
+/*
+ * Writeback must write-protect a folio when locking it for IO, before
+ * anything consumes its data (zeroing, inline copy, compression,
+ * checksumming). If this fails, then an mmap writer would be able to
+ * modify the data concurrently while we need it to be stable.
+ */
+void btrfs_check_folio_write_protected(struct folio *folio)
+{
+ if (folio_mkclean(folio)) {
+ const struct btrfs_inode *inode = BTRFS_I(folio->mapping->host);
+
+ DEBUG_WARN("writable mmap PTEs, root %llu ino %llu pos %llu order %u",
+ btrfs_root_id(inode->root), btrfs_ino(inode), folio_pos(folio),
+ folio_order(folio));
+ }
+}
+#endif
+
static noinline int lock_delalloc_folios(struct inode *inode,
struct folio *locked_folio,
u64 start, u64 end)
@@ -332,6 +352,8 @@ static noinline int lock_delalloc_folios(struct inode *inode,
folio_unlock(folio);
goto out;
}
+ /* Locked for writeback; revoke writable mmap PTEs before using the data. */
+ folio_mkclean(folio);
range_start = max_t(u64, folio_pos(folio), start);
range_len = min_t(u64, folio_next_pos(folio), end + 1) - range_start;
btrfs_folio_set_lock(fs_info, folio, range_start, range_len);
@@ -1893,6 +1915,13 @@ static noinline_for_stack int extent_writepage_io(struct btrfs_inode *inode,
ASSERT(end <= folio_end, "start=%llu len=%u folio_start=%llu folio_size=%zu",
start, len, folio_start, folio_size(folio));
+ /*
+ * We are about to checksum and write out the data, so it must not be
+ * mmap writeable, or we could corrupt the data and end up with invalid
+ * checksums.
+ */
+ btrfs_check_folio_write_protected(folio);
+
/* Truncate the submit bitmap to the current range. */
if (start > folio_start)
bitmap_clear(bio_ctrl->submit_bitmap, 0,
@@ -2703,6 +2732,8 @@ retry:
continue;
}
+ /* Locked for writeback; revoke writable mmap PTEs before using the data. */
+ folio_mkclean(folio);
ret = extent_writepage(folio, bio_ctrl);
if (ret < 0) {
done = true;
diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h
index 9896e15ddc40..869925337699 100644
--- a/fs/btrfs/extent_io.h
+++ b/fs/btrfs/extent_io.h
@@ -255,6 +255,11 @@ bool try_release_extent_mapping(struct folio *folio, gfp_t mask);
int try_release_extent_buffer(struct folio *folio);
int btrfs_read_folio(struct file *file, struct folio *folio);
+#ifdef CONFIG_BTRFS_DEBUG
+void btrfs_check_folio_write_protected(struct folio *folio);
+#else
+static inline void btrfs_check_folio_write_protected(struct folio *folio) { }
+#endif
void extent_write_locked_range(struct inode *inode, const struct folio *locked_folio,
u64 start, u64 end, struct writeback_control *wbc,
bool pages_dirty);
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index b0693065a0c7..2a66bcb59ecb 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -775,19 +775,28 @@ static inline void inode_should_defrag(struct btrfs_inode *inode,
static int extent_range_clear_dirty_for_io(struct btrfs_inode *inode, u64 start, u64 end)
{
+ pgoff_t index = start >> PAGE_SHIFT;
const pgoff_t end_index = end >> PAGE_SHIFT;
struct folio *folio;
int ret = 0;
- for (pgoff_t index = start >> PAGE_SHIFT; index <= end_index; index++) {
+ while (index <= end_index) {
folio = filemap_get_folio(inode->vfs_inode.i_mapping, index);
if (IS_ERR(folio)) {
if (!ret)
ret = PTR_ERR(folio);
+ index++;
continue;
}
+ /*
+ * We are about to compress the folio, so it must not be mmap
+ * writeable or we could corrupt the data as we attempt to
+ * compress it.
+ */
+ btrfs_check_folio_write_protected(folio);
btrfs_folio_clamp_clear_dirty(inode->root->fs_info, folio, start,
end + 1 - start);
+ index = folio_next_index(folio);
folio_put(folio);
}
return ret;
@@ -877,11 +886,6 @@ static void compress_file_range(struct btrfs_work *work)
inode_should_defrag(inode, start, end, end - start + 1, SZ_16K);
- /*
- * We need to call clear_page_dirty_for_io on each page in the range.
- * Otherwise applications with the file mmap'd can wander in and change
- * the page contents while we are compressing them.
- */
ret = extent_range_clear_dirty_for_io(inode, start, end);
/*
@@ -2317,6 +2321,13 @@ static int run_delalloc_inline(struct btrfs_inode *inode, struct folio *locked_f
int ret;
ASSERT(folio_pos(locked_folio) == 0);
+ /*
+ * If an mmap writer could modify the folio while we copy it into an
+ * inline extent we might see only part of their modification then
+ * wrongly mark it clean again after copying, losing that write. So the
+ * folio must be write protected here.
+ */
+ btrfs_check_folio_write_protected(locked_folio);
if (btrfs_inode_can_compress(inode) &&
inode_need_compress(inode, 0, blocksize, true)) {