Commit 06a7c3c
fuse: hotfix truncate_pagecache() issue
The way how fuse calls truncate_pagecache() from fuse_change_attributes()
is completely wrong. Because, w/o i_mutex held, we never sure whether
'oldsize' and 'attr->size' are valid by the time of execution of
truncate_pagecache(inode, oldsize, attr->size). In fact, as soon as we
released fc->lock in the middle of fuse_change_attributes(), we completely
loose control of actions which may happen with given inode until we reach
truncate_pagecache. The list of potentially dangerous actions includes
mmap-ed reads and writes, ftruncate(2) and write(2) extending file size.
The typical outcome of doing truncate_pagecache() with outdated arguments
is data corruption from user point of view. This is (in some sense)
acceptable in cases when the issue is triggered by a change of the file on
the server (i.e. externally wrt fuse operation), but it is absolutely
intolerable in scenarios when a single fuse client modifies a file without
any external intervention. A real life case I discovered by fsx-linux
looked like this:
1. Shrinking ftruncate(2) comes to fuse_do_setattr(). The latter sends
FUSE_SETATTR to the server synchronously, but before getting fc->lock ...
2. fuse_dentry_revalidate() is asynchronously called. It sends FUSE_LOOKUP
to the server synchronously, then calls fuse_change_attributes(). The
latter updates i_size, releases fc->lock, but before comparing oldsize vs
attr->size..
3. fuse_do_setattr() from the first step proceeds by acquiring fc->lock and
updating attributes and i_size, but now oldsize is equal to
outarg.attr.size because i_size has just been updated (step 2). Hence,
fuse_do_setattr() returns w/o calling truncate_pagecache().
4. As soon as ftruncate(2) completes, the user extends file size by
write(2) making a hole in the middle of file, then reads data from the hole
either by read(2) or mmap-ed read. The user expects to get zero data from
the hole, but gets stale data because truncate_pagecache() is not executed
yet.
The scenario above illustrates one side of the problem: not truncating the
page cache even though we should. Another side corresponds to truncating
page cache too late, when the state of inode changed significantly.
Theoretically, the following is possible:
1. As in the previous scenario fuse_dentry_revalidate() discovered that
i_size changed (due to our own fuse_do_setattr()) and is going to call
truncate_pagecache() for some 'new_size' it believes valid right now. But
by the time that particular truncate_pagecache() is called ...
2. fuse_do_setattr() returns (either having called truncate_pagecache() or
not -- it doesn't matter).
3. The file is extended either by write(2) or ftruncate(2) or fallocate(2).
4. mmap-ed write makes a page in the extended region dirty.
The result will be the lost of data user wrote on the fourth step.
The patch is a hotfix resolving the issue in a simplistic way: let's skip
dangerous i_size update and truncate_pagecache if an operation changing
file size is in progress. This simplistic approach looks correct for the
cases w/o external changes. And to handle them properly, more sophisticated
and intrusive techniques (e.g. NFS-like one) would be required. I'd like to
postpone it until the issue is well discussed on the mailing list(s).
Changed in v2:
- improved patch description to cover both sides of the issue.
Signed-off-by: Maxim Patlasov <mpatlasov@parallels.com>
Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
Cc: stable@vger.kernel.org1 parent d331a41 commit 06a7c3c
4 files changed
+17
-3
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1590 | 1590 | | |
1591 | 1591 | | |
1592 | 1592 | | |
| 1593 | + | |
1593 | 1594 | | |
1594 | 1595 | | |
1595 | 1596 | | |
| |||
1617 | 1618 | | |
1618 | 1619 | | |
1619 | 1620 | | |
1620 | | - | |
| 1621 | + | |
1621 | 1622 | | |
| 1623 | + | |
| 1624 | + | |
1622 | 1625 | | |
1623 | 1626 | | |
1624 | 1627 | | |
| |||
1680 | 1683 | | |
1681 | 1684 | | |
1682 | 1685 | | |
| 1686 | + | |
1683 | 1687 | | |
1684 | 1688 | | |
1685 | 1689 | | |
1686 | 1690 | | |
1687 | 1691 | | |
1688 | 1692 | | |
| 1693 | + | |
1689 | 1694 | | |
1690 | 1695 | | |
1691 | 1696 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
629 | 629 | | |
630 | 630 | | |
631 | 631 | | |
632 | | - | |
| 632 | + | |
| 633 | + | |
633 | 634 | | |
634 | 635 | | |
635 | 636 | | |
| |||
1032 | 1033 | | |
1033 | 1034 | | |
1034 | 1035 | | |
| 1036 | + | |
1035 | 1037 | | |
1036 | 1038 | | |
1037 | 1039 | | |
1038 | 1040 | | |
1039 | 1041 | | |
1040 | 1042 | | |
| 1043 | + | |
| 1044 | + | |
| 1045 | + | |
1041 | 1046 | | |
1042 | 1047 | | |
1043 | 1048 | | |
| |||
1073 | 1078 | | |
1074 | 1079 | | |
1075 | 1080 | | |
| 1081 | + | |
1076 | 1082 | | |
1077 | 1083 | | |
1078 | 1084 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
115 | 115 | | |
116 | 116 | | |
117 | 117 | | |
| 118 | + | |
| 119 | + | |
118 | 120 | | |
119 | 121 | | |
120 | 122 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
201 | 201 | | |
202 | 202 | | |
203 | 203 | | |
204 | | - | |
| 204 | + | |
| 205 | + | |
205 | 206 | | |
206 | 207 | | |
207 | 208 | | |
| |||
0 commit comments