diff options
| author | Chris Mason <mason@kernel.org> | 2026-10-16 21:17:04 +0000 |
|---|---|---|
| committer | Amir Goldstein <amir73il@gmail.com> | 2026-10-29 13:22:58 +0100 |
| commit | eb644ed5c6d9afdb1554c41cef1f39d150fbc554 (patch) | |
| tree | f97725b6cf626ce66779295c50d8c17648c19b1a /scripts | |
| parent | 50912a1715b8a7c614b654011a0db750e372b43e (diff) | |
| download | linux-eb644ed5c6d9afdb1554c41cef1f39d150fbc554.tar.gz linux-eb644ed5c6d9afdb1554c41cef1f39d150fbc554.zip | |
ovl: read the nlink xattr of every indexed non-dir
With nfs_export=on, data written through a file handle to a lower file,
i.e. a file that exists only in a lower layer of the overlay, can be
lost on the next mount. A lookup of the file by name then returns -EIO.
Also, a file with one name reports st_nlink 2 once it has been copied to
the upper layer. After such a file is removed, its file handle can still
open it until the next mount.
Before a lower file is changed for the first time, overlayfs copies it
to the writable upper layer. This is called copy up.
The index directory is $workdir/index, where $workdir is the directory
given by the workdir= option. Each entry is named after the file handle
of a lower inode, so that overlayfs can get from the handle to the
copied up inode.
With nfs_export=on, copy up indexes every non-directory: it copies the
file to an entry in the index directory and then hard links that entry
into the upper directory. The index entry is an extra link to the upper
inode, so overlayfs stores the link count that users should see in the
overlay.nlink xattr.
A file can also be opened by handle, either by an NFS client or by a
process with CAP_DAC_READ_SEARCH calling open_by_handle_at(). If the
file is not in the dcache, its overlay dentry is disconnected, i.e. it
has no parent yet. Copy up then creates the index entry but no hard link
in the upper directory. The index entry has i_nlink 1 and holds the only
copy of the data written to the file.
ovl_get_nlink() reads the stored count. Its caller passes a default,
which the function returns if there is no stored count. If the lower
inode has i_nlink 1, the function returns the default without reading
the xattr. That was correct as long as only lower files with multiple
links were indexed.
On the next mount, ovl_verify_index() looks for orphans, i.e. index
entries of files whose last name was removed. For an index entry with
i_nlink 1, it passes a default of 0 to ovl_get_nlink() and treats a
result of 0 as an orphan. Here the lower inode also has i_nlink 1, so
the result is the default. ovl_indexdir_cleanup() then replaces the
entry with a whiteout, which in the index marks a file as deleted. The
written data is lost.
A lookup by name then returns -EIO, because a whiteout as the index
entry of a lower file that still exists is a bad index.
ovl_get_inode() gets the wrong count the same way. Its default is the
i_nlink of the upper inode, which includes the index entry, so a copied
up file with one name reports st_nlink 2.
When that name is removed, the count drops to 1 rather than 0.
ovl_nlink_end() replaces the index entry with a whiteout only at a count
of 0, so the entry stays, and the handle of the removed file still opens
it.
Reproduced by writing through open_by_handle_at() to an uncached lower
file with one link. After a remount the data was lost, and the name
returned EIO.
Fix this by adding an argument to ovl_get_nlink() that says whether the
caller found an index entry, and by skipping the xattr only if the
caller found none.
Known gaps:
- An index entry that a kernel without this patch left behind for a
removed file now stays, and the handle of that file keeps working. The
entry has i_nlink 1 and a stored count of 1, just like the entry of a
file that was only written by handle. Without this patch, the next
mount treated it as an orphan.
Fixes: fbd2d2074bde ("ovl: index all non-dir on copy up for NFS export")
Cc: stable@vger.kernel.org
Signed-off-by: Chris Mason <mason@kernel.org>
Signed-off-by: Amir Goldstein <amir73il@gmail.com>
Diffstat (limited to 'scripts')
0 files changed, 0 insertions, 0 deletions
