[PATCH v4 10/31] ntfs: report LZNT1 and WOF compressed stream errors

From: Baolin Liu

Date: Tue Oct 20 2026 - 04:52:25 EST


From: Dandan Wu <wudandan@xxxxxxxxxx>

Compressed I/O can detect malformed LZNT1 data or WOF metadata and
compressed data without reporting it. Report corruption as EFSCORRUPTED
and retain underlying read, mapping and allocation errors.

Save compression-buffer allocation errors before cleanup so ENOMEM does
not become EIO. Report failed mapping updates after compressed writes
and failed cluster release separately from the initiating error.

Signed-off-by: Dandan Wu <wudandan@xxxxxxxxxx>
---
Testing:
Fault injection of compression-buffer allocation failure confirms that
it emits no error event and a subsequent read can succeed.

fs/ntfs/compress.c | 30 +++++++++++++++++++++++-------
fs/ntfs/wof.c | 31 ++++++++++++++++++++++++++-----
2 files changed, 49 insertions(+), 12 deletions(-)

diff --git a/fs/ntfs/compress.c b/fs/ntfs/compress.c
index 82c7c7d29073..a82c05bd7625 100644
--- a/fs/ntfs/compress.c
+++ b/fs/ntfs/compress.c
@@ -585,11 +585,13 @@ int ntfs_read_compressed_block(struct folio *folio)
nr_cbs--;

mutex_lock(&ntfs_cb_lock);
- if (!ntfs_compression_buffer)
- if (allocate_compression_buffers()) {
+ if (!ntfs_compression_buffer) {
+ err = allocate_compression_buffers();
+ if (err) {
mutex_unlock(&ntfs_cb_lock);
goto err_out;
}
+ }


cb = ntfs_compression_buffer;
@@ -633,7 +635,8 @@ int ntfs_read_compressed_block(struct folio *folio)
* duration.
*/
up_read(&ni->runlist.lock);
- if (!ntfs_map_runlist(ni, vcn))
+ err = ntfs_map_runlist(ni, vcn);
+ if (!err)
goto lock_retry_remap;
mutex_unlock(&ntfs_cb_lock);
goto map_rl_err;
@@ -787,6 +790,8 @@ int ntfs_read_compressed_block(struct folio *folio)
ntfs_error(vol->sb,
"ntfs_decompress() failed in inode 0x%llx with error code %i. Skipping this compression block.",
ni->mft_no, -err);
+ err = -EFSCORRUPTED;
+ ntfs_report_file_metadata_error(VFS_I(ni), err);
/* Release the unfinished pages. */
for (; prev_cur_page < cur_page; prev_cur_page++) {
page = pages[prev_cur_page];
@@ -839,7 +844,9 @@ int ntfs_read_compressed_block(struct folio *folio)
goto err_out;

rl_err:
+ err = -EFSCORRUPTED;
up_read(&ni->runlist.lock);
+ ntfs_report_file_metadata_error(VFS_I(ni), -EFSCORRUPTED);
ntfs_error(vol->sb, "ntfs_rl_vcn_to_lcn() failed. Cannot read compression block.");
goto err_out;

@@ -859,7 +866,7 @@ int ntfs_read_compressed_block(struct folio *folio)
}
kfree(pages);
kfree(completed_pages);
- return -EIO;
+ return err;
}

/*
@@ -1443,6 +1450,9 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages,
up_write(&ni->runlist.lock);
ntfs_error(vol->sb, "Failed to merge runlists");
err = PTR_ERR(rl);
+ if (err != -ENOMEM)
+ err = -EFSCORRUPTED;
+ ntfs_report_file_metadata_error(VFS_I(ni), err);
goto free_rlc;
}

@@ -1453,12 +1463,18 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages,
err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
up_write(&ni->runlist.lock);
if (err)
- err = -EIO;
+ ntfs_report_file_metadata_error(VFS_I(ni), err);
goto out;

free_rlc:
- if (ntfs_cluster_free_from_rl(vol, rlc))
- ntfs_error(vol->sb, "Failed to free hot clusters.");
+ {
+ int free_err = ntfs_cluster_free_from_rl(vol, rlc);
+
+ if (free_err) {
+ ntfs_report_file_metadata_error(VFS_I(ni), free_err);
+ ntfs_error(vol->sb, "Failed to free hot clusters.");
+ }
+ }
kvfree(rlc);
out:
NInoSetFileNameDirty(ni);
diff --git a/fs/ntfs/wof.c b/fs/ntfs/wof.c
index e64f40835957..30b048bcac83 100644
--- a/fs/ntfs/wof.c
+++ b/fs/ntfs/wof.c
@@ -294,7 +294,7 @@ static int parse_wof_chunk_table(struct ntfs_inode *base_ni,
ret = ntfs_bdev_read_from_rl(ni->vol, &ni->runlist,
start_sector, sectors, buf);
if (ret)
- return -EIO;
+ return ret;
buf += sector_off;
} else {
struct ntfs_attr_search_ctx *ctx;
@@ -685,15 +685,21 @@ int ntfs_read_wof_compressed_block(struct folio *folio)
WOF_NAME_LEN);
if (IS_ERR(wof_inode)) {
err = PTR_ERR(wof_inode);
+ if (err == -ENOENT) {
+ err = -EFSCORRUPTED;
+ ntfs_report_file_metadata_error(VFS_I(ni),
+ -EFSCORRUPTED);
+ }
goto out;
}

wof_ni = NTFS_I(wof_inode);
if (wof_ni->initialized_size != wof_ni->data_size) {
+ ntfs_report_file_metadata_error(VFS_I(ni), -EFSCORRUPTED);
ntfs_error(vol->sb,
"WOF compressed stream is not fully initialized (init %lld, data %lld).",
wof_ni->initialized_size, wof_ni->data_size);
- err = -EIO;
+ err = -EFSCORRUPTED;
goto out_iput;
}
if (NInoNonResident(wof_ni) && !NInoFullyMapped(wof_ni)) {
@@ -725,21 +731,36 @@ int ntfs_read_wof_compressed_block(struct folio *folio)
err = parse_wof_chunk_table(ni, wof_ni, chunk_idx, chunk_count,
decomp_size, &chunk_offset,
&chunk_size, input, input_size);
- if (err)
+ if (err) {
+ if (err == -EINVAL || err == -EOVERFLOW || err == -ENOENT)
+ err = -EFSCORRUPTED;
+ if (err == -EIO || err == -EFSCORRUPTED)
+ ntfs_report_file_metadata_error(VFS_I(ni), err);
goto out_free_input;
+ }

err = ntfs_read_wof_chunk(vol, wof_ni, chunk_offset, chunk_size,
input, input_size, &chunk_mem);
- if (err)
+ if (err) {
+ if (err == -EINVAL || err == -EOVERFLOW || err == -ENOENT) {
+ err = -EFSCORRUPTED;
+ ntfs_report_file_metadata_error(VFS_I(ni), err);
+ }
goto out_free_input;
+ }

err = ntfs_wof_decompress_chunk(ws, vol, mapping, folio,
folio_start, folio_end,
chunk_idx * ws->comp_unit,
chunk_mem, chunk_size,
decomp_size);
- if (err)
+ if (err) {
+ if (err != -ENOMEM) {
+ err = -EFSCORRUPTED;
+ ntfs_report_file_metadata_error(VFS_I(ni), err);
+ }
goto out_free_input;
+ }
}

if (folio_end > i_size)
--
2.51.0