diff options
author | Robert Doebbelin <robert@quobyte.com> | 2016-03-07 03:50:56 -0500 |
---|---|---|
committer | Miklos Szeredi <miklos@szeredi.hu> | 2016-03-14 10:02:50 -0400 |
commit | 7cabc61e01a0a8b663bd2b4c982aa53048218734 (patch) | |
tree | 474f0a6e0cde183d19ccb0c9b14d2358e1944012 /fs/fuse/file.c | |
parent | b562e44f507e863c6792946e4e1b1449fbbac85d (diff) |
fuse: do not use iocb after it may have been freed
There's a race in fuse_direct_IO(), whereby is_sync_kiocb() is called on an
iocb that could have been freed if async io has already completed. The fix
in this case is simple and obvious: cache the result before starting io.
It was discovered by KASan:
kernel: ==================================================================
kernel: BUG: KASan: use after free in fuse_direct_IO+0xb1a/0xcc0 at addr ffff88036c414390
Signed-off-by: Robert Doebbelin <robert@quobyte.com>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
Fixes: bcba24ccdc82 ("fuse: enable asynchronous processing direct IO")
Cc: <stable@vger.kernel.org> # 3.10+
Diffstat (limited to 'fs/fuse/file.c')
-rw-r--r-- | fs/fuse/file.c | 7 |
1 files changed, 4 insertions, 3 deletions
diff --git a/fs/fuse/file.c b/fs/fuse/file.c index b03d253ece15..34a23df361ca 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c | |||
@@ -2843,6 +2843,7 @@ fuse_direct_IO(struct kiocb *iocb, struct iov_iter *iter, loff_t offset) | |||
2843 | loff_t i_size; | 2843 | loff_t i_size; |
2844 | size_t count = iov_iter_count(iter); | 2844 | size_t count = iov_iter_count(iter); |
2845 | struct fuse_io_priv *io; | 2845 | struct fuse_io_priv *io; |
2846 | bool is_sync = is_sync_kiocb(iocb); | ||
2846 | 2847 | ||
2847 | pos = offset; | 2848 | pos = offset; |
2848 | inode = file->f_mapping->host; | 2849 | inode = file->f_mapping->host; |
@@ -2882,11 +2883,11 @@ fuse_direct_IO(struct kiocb *iocb, struct iov_iter *iter, loff_t offset) | |||
2882 | * to wait on real async I/O requests, so we must submit this request | 2883 | * to wait on real async I/O requests, so we must submit this request |
2883 | * synchronously. | 2884 | * synchronously. |
2884 | */ | 2885 | */ |
2885 | if (!is_sync_kiocb(iocb) && (offset + count > i_size) && | 2886 | if (!is_sync && (offset + count > i_size) && |
2886 | iov_iter_rw(iter) == WRITE) | 2887 | iov_iter_rw(iter) == WRITE) |
2887 | io->async = false; | 2888 | io->async = false; |
2888 | 2889 | ||
2889 | if (io->async && is_sync_kiocb(iocb)) | 2890 | if (io->async && is_sync) |
2890 | io->done = &wait; | 2891 | io->done = &wait; |
2891 | 2892 | ||
2892 | if (iov_iter_rw(iter) == WRITE) { | 2893 | if (iov_iter_rw(iter) == WRITE) { |
@@ -2900,7 +2901,7 @@ fuse_direct_IO(struct kiocb *iocb, struct iov_iter *iter, loff_t offset) | |||
2900 | fuse_aio_complete(io, ret < 0 ? ret : 0, -1); | 2901 | fuse_aio_complete(io, ret < 0 ? ret : 0, -1); |
2901 | 2902 | ||
2902 | /* we have a non-extending, async request, so return */ | 2903 | /* we have a non-extending, async request, so return */ |
2903 | if (!is_sync_kiocb(iocb)) | 2904 | if (!is_sync) |
2904 | return -EIOCBQUEUED; | 2905 | return -EIOCBQUEUED; |
2905 | 2906 | ||
2906 | wait_for_completion(&wait); | 2907 | wait_for_completion(&wait); |