daxfs: Discard overlay data on truncate - #16
Open
congwang-mk wants to merge 1 commit into
Open
Conversation
Truncating a file left its overlay pages in place. Nothing erased them from the per-inode xarray or the DAX hash, so extending the file again handed back the old contents instead of zeros: write 8192 bytes of 'A' truncate to 0 write 1 byte at offset 65536 read offset 0 -> 'AAAAAAAAAAAAAAAA' (must be zeros) POSIX requires the extended range to read as zeros, and for a filesystem built around a shared base image with per-container overlays, data surviving a truncate is a disclosure bug rather than only a correctness one. Zero the overlay pages from the new size up to the old end of file, including the tail of a partial page at the boundary. The pages are kept mapped rather than freed: dropping them would mean deleting keys from the open-addressed overlay hash, which has a single state bit and no room for tombstones, and for an inode with base image data behind it removing the overlay page would re-expose the base contents instead of zeros. Zeroing is correct in both cases and leaves the pages ready for a re-extend, which is the common truncate-then-rewrite pattern. The old size is captured before any field is overwritten, and taken as the larger of the cached i_size and the overlay entry, so pages another host wrote past our stale i_size are discarded too. Add a regression test covering the full-page case, the partial-page tail and that data below the truncation point survives. It fails without the fix and passes with it; the suite is 21/21 on a 40 CPU host. Signed-off-by: Cong Wang <cwang@multikernel.io>
congwang-mk
force-pushed
the
fix-truncate-stale-data
branch
from
August 16, 2026 03:24
97b939e to
02da78a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Truncating a file left its overlay pages in place. Nothing erased them from the per-inode xarray or the DAX overlay hash, so extending the file again handed back the old contents instead of zeros.
Reproduced on a 40 CPU host, empty-mode dma-buf mount:
The partial-page case fails the same way: truncate to 4 bytes and the remaining 4092 bytes of that page come back on re-extend.
POSIX requires the extended range to read as zeros. For a filesystem built around a shared base image with per-container overlays, data surviving a truncate is a disclosure bug rather than only a correctness one.
daxfs_setattr()calledtruncate_setsize()and updatedoie->size, and that was all.overlay_pool_free()is never reached from the DATA path except for the publish-race case atoverlay.c:599, so the pages were also leaked.Fix
Zero the overlay pages from the new size up to the old end of file, including the tail of a partial page at the boundary.
The pages are kept mapped rather than freed. Two reasons:
DAXFS_OVL_STATE()is a single bit with the key in the remaining 63, so there is no room for a tombstone state, and marking a bucketFREEwould truncate the probe chain for every key that probed past it. Adding tombstones is a format change.Keeping the pages also leaves them ready for a re-extend, which is the common truncate-then-rewrite pattern. The pool space is not reclaimed, which matches the existing behaviour for every other data page.
The old size is captured before any field is overwritten, and taken as the larger of the cached
i_sizeand the overlay entry's size, so pages another host wrote past our stalei_sizeare discarded too.Zeroing runs after
truncate_setsize(), so a concurrent reader sees the smalleri_sizebefore the tail starts changing rather than after.Testing
New regression test in
tests/test_overlay.shcovering three cases: the full-page case, the partial-page tail, and that data below the truncation point survives (so the test cannot pass by zeroing too much).Verified it actually catches the bug rather than passing vacuously:
Also re-checked after the change: 256 MiB random payload round-trips with a matching md5, and no new
dmesgwarnings.Cost
Truncating to zero now memsets the overlay allocation, so a 2 GiB file costs a 2 GiB memset, roughly 0.2 s at 10 GB/s. That is inherent to zeroing in place rather than freeing; reclaiming the pages instead would need the tombstone format change described above.