summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
Diffstat (limited to 'fs')
-rw-r--r--fs/backing-file.c2
-rw-r--r--fs/btrfs/direct-io.c19
-rw-r--r--fs/btrfs/extent_io.c31
-rw-r--r--fs/btrfs/extent_io.h5
-rw-r--r--fs/btrfs/inode.c23
-rw-r--r--fs/btrfs/ioctl.c23
-rw-r--r--fs/btrfs/relocation.c31
-rw-r--r--fs/buffer.c3
-rw-r--r--fs/ceph/addr.c11
-rw-r--r--fs/ceph/caps.c74
-rw-r--r--fs/ceph/dir.c10
-rw-r--r--fs/ceph/export.c26
-rw-r--r--fs/ceph/mds_client.c73
-rw-r--r--fs/ceph/mds_client.h1
-rw-r--r--fs/ceph/mdsmap.c11
-rw-r--r--fs/ceph/super.h14
-rw-r--r--fs/ceph/xattr.c1
-rw-r--r--fs/debugfs/file.c3
-rw-r--r--fs/ecryptfs/crypto.c2
-rw-r--r--fs/ecryptfs/ecryptfs_kernel.h3
-rw-r--r--fs/ecryptfs/keystore.c45
-rw-r--r--fs/ecryptfs/messaging.c11
-rw-r--r--fs/ecryptfs/miscdev.c5
-rw-r--r--fs/ecryptfs/super.c7
-rw-r--r--fs/efivarfs/super.c30
-rw-r--r--fs/erofs/zutil.c2
-rw-r--r--fs/ext2/inode.c7
-rw-r--r--fs/ext2/xattr.c4
-rw-r--r--fs/fat/misc.c4
-rw-r--r--fs/fuse/dev.c8
-rw-r--r--fs/fuse/dev.h2
-rw-r--r--fs/fuse/dev_uring.c38
-rw-r--r--fs/fuse/dev_uring_i.h5
-rw-r--r--fs/fuse/inode.c4
-rw-r--r--fs/iomap/bio.c2
-rw-r--r--fs/iomap/direct-io.c1
-rw-r--r--fs/jbd2/checkpoint.c28
-rw-r--r--fs/lockd/clntproc.c3
-rw-r--r--fs/lockd/lockd.h2
-rw-r--r--fs/lockd/svc.c4
-rw-r--r--fs/lockd/svc4proc.c7
-rw-r--r--fs/lockd/svcproc.c4
-rw-r--r--fs/lockd/svcsubs.c109
-rw-r--r--fs/nfs/callback.c4
-rw-r--r--fs/nfs/nfs4client.c17
-rw-r--r--fs/nfs/pnfs.c3
-rw-r--r--fs/nfs_common/nfslocalio.c16
-rw-r--r--fs/nfsd/filecache.c143
-rw-r--r--fs/nfsd/flexfilelayoutxdr.c20
-rw-r--r--fs/nfsd/localio.c8
-rw-r--r--fs/nfsd/lockd.c6
-rw-r--r--fs/nfsd/netns.h35
-rw-r--r--fs/nfsd/nfs2acl.c52
-rw-r--r--fs/nfsd/nfs3acl.c17
-rw-r--r--fs/nfsd/nfs3proc.c40
-rw-r--r--fs/nfsd/nfs4callback.c113
-rw-r--r--fs/nfsd/nfs4layouts.c39
-rw-r--r--fs/nfsd/nfs4proc.c223
-rw-r--r--fs/nfsd/nfs4recover.c48
-rw-r--r--fs/nfsd/nfs4state.c395
-rw-r--r--fs/nfsd/nfs4xdr.c40
-rw-r--r--fs/nfsd/nfscache.c6
-rw-r--r--fs/nfsd/nfsctl.c156
-rw-r--r--fs/nfsd/nfsfh.c12
-rw-r--r--fs/nfsd/nfsproc.c7
-rw-r--r--fs/nfsd/nfssvc.c49
-rw-r--r--fs/nfsd/nfsxdr.c32
-rw-r--r--fs/nfsd/state.h4
-rw-r--r--fs/nfsd/trace.h18
-rw-r--r--fs/nfsd/vfs.c39
-rw-r--r--fs/nilfs2/bmap.c2
-rw-r--r--fs/nilfs2/bmap.h2
-rw-r--r--fs/nilfs2/btree.c39
-rw-r--r--fs/nilfs2/direct.c4
-rw-r--r--fs/notify/fanotify/fanotify.c3
-rw-r--r--fs/notify/fanotify/fanotify.h6
-rw-r--r--fs/notify/fanotify/fanotify_user.c17
-rw-r--r--fs/notify/inotify/inotify_user.c15
-rw-r--r--fs/ntfs/attrib.c24
-rw-r--r--fs/ntfs/lcnalloc.c7
-rw-r--r--fs/ntfs/runlist.c48
-rw-r--r--fs/ntfs/super.c12
-rw-r--r--fs/ntfs3/frecord.c9
-rw-r--r--fs/ntfs3/fslog.c34
-rw-r--r--fs/ocfs2/cluster/heartbeat.c148
-rw-r--r--fs/ocfs2/cluster/nodemanager.c6
-rw-r--r--fs/ocfs2/cluster/nodemanager.h1
-rw-r--r--fs/ocfs2/dir.c47
-rw-r--r--fs/ocfs2/dlm/dlmmaster.c6
-rw-r--r--fs/ocfs2/dlm/dlmrecovery.c9
-rw-r--r--fs/ocfs2/refcounttree.c47
-rw-r--r--fs/ocfs2/suballoc.c2
-rw-r--r--fs/ocfs2/xattr.c5
-rw-r--r--fs/orangefs/devorangefs-req.c1
-rw-r--r--fs/orangefs/orangefs-debugfs.c1
-rw-r--r--fs/overlayfs/dir.c1
-rw-r--r--fs/pidfs.c43
-rw-r--r--fs/smb/client/cifsfs.c15
-rw-r--r--fs/smb/client/cifsfs.h1
-rw-r--r--fs/smb/client/cifssmb.c6
-rw-r--r--fs/smb/client/dfs_cache.c33
-rw-r--r--fs/smb/client/file.c1
-rw-r--r--fs/smb/client/inode.c56
-rw-r--r--fs/smb/client/smb1misc.c3
-rw-r--r--fs/smb/client/smb1ops.c2
-rw-r--r--fs/smb/client/smb1transport.c35
-rw-r--r--fs/smb/client/smb2file.c5
-rw-r--r--fs/smb/client/smb2inode.c6
-rw-r--r--fs/smb/client/smb2ops.c9
-rw-r--r--fs/smb/client/trace.h3
-rw-r--r--fs/smb/server/vfs_cache.c2
-rw-r--r--fs/tracefs/event_inode.c7
-rw-r--r--fs/ubifs/auth.c2
-rw-r--r--fs/udf/inode.c2
-rw-r--r--fs/udf/partition.c2
115 files changed, 2220 insertions, 749 deletions
diff --git a/fs/backing-file.c b/fs/backing-file.c
index 080c99696cd0..cc101143f921 100644
--- a/fs/backing-file.c
+++ b/fs/backing-file.c
@@ -35,7 +35,7 @@ struct file *backing_file_open(const struct file *user_file, int flags,
const struct path *real_path,
const struct cred *cred)
{
- const struct path *user_path = &user_file->f_path;
+ const struct path *user_path = file_user_path(user_file);
struct file *f;
int error;
diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c
index 460326d34143..19a1259b3b2f 100644
--- a/fs/btrfs/direct-io.c
+++ b/fs/btrfs/direct-io.c
@@ -281,17 +281,24 @@ static int btrfs_get_blocks_direct_write(struct extent_map **map,
em2 = btrfs_create_dio_extent(BTRFS_I(inode), dio_data, start,
&file_extent, type);
btrfs_dec_nocow_writers(bg);
- if (type == BTRFS_ORDERED_PREALLOC) {
- btrfs_free_extent_map(em);
- *map = em2;
- em = em2;
- }
-
if (IS_ERR(em2)) {
ret = PTR_ERR(em2);
+ btrfs_free_extent_map(em);
+ *map = NULL;
goto out;
}
+ /*
+ * True NOCOW writes don't need to create a new extent map,
+ * while PREALLOC writes must replace the existing one.
+ */
+ if (em2) {
+ ASSERT(type == BTRFS_ORDERED_PREALLOC);
+ btrfs_free_extent_map(em);
+ *map = em2;
+ em = em2;
+ }
+
dio_data->nocow_done = true;
} else {
/* Our caller expects us to free the input extent map. */
diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c
index f032f0858f40..45c4d4a3b29b 100644
--- a/fs/btrfs/extent_io.c
+++ b/fs/btrfs/extent_io.c
@@ -6,6 +6,7 @@
#include <linux/mm.h>
#include <linux/pagemap.h>
#include <linux/page-flags.h>
+#include <linux/rmap.h>
#include <linux/sched/mm.h>
#include <linux/spinlock.h>
#include <linux/blkdev.h>
@@ -299,6 +300,25 @@ static noinline void unlock_delalloc_folio(const struct inode *inode,
PAGE_UNLOCK);
}
+#ifdef CONFIG_BTRFS_DEBUG
+/*
+ * Writeback must write-protect a folio when locking it for IO, before
+ * anything consumes its data (zeroing, inline copy, compression,
+ * checksumming). If this fails, then an mmap writer would be able to
+ * modify the data concurrently while we need it to be stable.
+ */
+void btrfs_check_folio_write_protected(struct folio *folio)
+{
+ if (folio_mkclean(folio)) {
+ const struct btrfs_inode *inode = BTRFS_I(folio->mapping->host);
+
+ DEBUG_WARN("writable mmap PTEs, root %llu ino %llu pos %llu order %u",
+ btrfs_root_id(inode->root), btrfs_ino(inode), folio_pos(folio),
+ folio_order(folio));
+ }
+}
+#endif
+
static noinline int lock_delalloc_folios(struct inode *inode,
struct folio *locked_folio,
u64 start, u64 end)
@@ -332,6 +352,8 @@ static noinline int lock_delalloc_folios(struct inode *inode,
folio_unlock(folio);
goto out;
}
+ /* Locked for writeback; revoke writable mmap PTEs before using the data. */
+ folio_mkclean(folio);
range_start = max_t(u64, folio_pos(folio), start);
range_len = min_t(u64, folio_next_pos(folio), end + 1) - range_start;
btrfs_folio_set_lock(fs_info, folio, range_start, range_len);
@@ -1893,6 +1915,13 @@ static noinline_for_stack int extent_writepage_io(struct btrfs_inode *inode,
ASSERT(end <= folio_end, "start=%llu len=%u folio_start=%llu folio_size=%zu",
start, len, folio_start, folio_size(folio));
+ /*
+ * We are about to checksum and write out the data, so it must not be
+ * mmap writeable, or we could corrupt the data and end up with invalid
+ * checksums.
+ */
+ btrfs_check_folio_write_protected(folio);
+
/* Truncate the submit bitmap to the current range. */
if (start > folio_start)
bitmap_clear(bio_ctrl->submit_bitmap, 0,
@@ -2703,6 +2732,8 @@ retry:
continue;
}
+ /* Locked for writeback; revoke writable mmap PTEs before using the data. */
+ folio_mkclean(folio);
ret = extent_writepage(folio, bio_ctrl);
if (ret < 0) {
done = true;
diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h
index 9896e15ddc40..869925337699 100644
--- a/fs/btrfs/extent_io.h
+++ b/fs/btrfs/extent_io.h
@@ -255,6 +255,11 @@ bool try_release_extent_mapping(struct folio *folio, gfp_t mask);
int try_release_extent_buffer(struct folio *folio);
int btrfs_read_folio(struct file *file, struct folio *folio);
+#ifdef CONFIG_BTRFS_DEBUG
+void btrfs_check_folio_write_protected(struct folio *folio);
+#else
+static inline void btrfs_check_folio_write_protected(struct folio *folio) { }
+#endif
void extent_write_locked_range(struct inode *inode, const struct folio *locked_folio,
u64 start, u64 end, struct writeback_control *wbc,
bool pages_dirty);
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 2534cd9284d5..39a01f9cf6b5 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -775,19 +775,28 @@ static inline void inode_should_defrag(struct btrfs_inode *inode,
static int extent_range_clear_dirty_for_io(struct btrfs_inode *inode, u64 start, u64 end)
{
+ pgoff_t index = start >> PAGE_SHIFT;
const pgoff_t end_index = end >> PAGE_SHIFT;
struct folio *folio;
int ret = 0;
- for (pgoff_t index = start >> PAGE_SHIFT; index <= end_index; index++) {
+ while (index <= end_index) {
folio = filemap_get_folio(inode->vfs_inode.i_mapping, index);
if (IS_ERR(folio)) {
if (!ret)
ret = PTR_ERR(folio);
+ index++;
continue;
}
+ /*
+ * We are about to compress the folio, so it must not be mmap
+ * writeable or we could corrupt the data as we attempt to
+ * compress it.
+ */
+ btrfs_check_folio_write_protected(folio);
btrfs_folio_clamp_clear_dirty(inode->root->fs_info, folio, start,
end + 1 - start);
+ index = folio_next_index(folio);
folio_put(folio);
}
return ret;
@@ -877,11 +886,6 @@ static void compress_file_range(struct btrfs_work *work)
inode_should_defrag(inode, start, end, end - start + 1, SZ_16K);
- /*
- * We need to call clear_page_dirty_for_io on each page in the range.
- * Otherwise applications with the file mmap'd can wander in and change
- * the page contents while we are compressing them.
- */
ret = extent_range_clear_dirty_for_io(inode, start, end);
/*
@@ -2317,6 +2321,13 @@ static int run_delalloc_inline(struct btrfs_inode *inode, struct folio *locked_f
int ret;
ASSERT(folio_pos(locked_folio) == 0);
+ /*
+ * If an mmap writer could modify the folio while we copy it into an
+ * inline extent we might see only part of their modification then
+ * wrongly mark it clean again after copying, losing that write. So the
+ * folio must be write protected here.
+ */
+ btrfs_check_folio_write_protected(locked_folio);
if (btrfs_inode_can_compress(inode) &&
inode_need_compress(inode, 0, blocksize, true)) {
diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
index baa645e98812..a537036303a4 100644
--- a/fs/btrfs/ioctl.c
+++ b/fs/btrfs/ioctl.c
@@ -356,14 +356,21 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap,
inode_flags |= BTRFS_INODE_NODATACOW;
}
} else {
- /*
- * Revert back under same assumptions as above
- */
- if (S_ISREG(inode->vfs_inode.i_mode)) {
- if (inode->vfs_inode.i_size == 0)
- inode_flags &= ~(BTRFS_INODE_NODATACOW |
- BTRFS_INODE_NODATASUM);
- } else {
+ /* We can only change NODATACOW for zero-sized regular file. */
+ if (S_ISREG(inode->vfs_inode.i_mode) && (inode->vfs_inode.i_size == 0)) {
+ inode_flags &= ~BTRFS_INODE_NODATACOW;
+ /*
+ * There is currently no way to change NODATASUM flag
+ * through fileattr API. If we unconditionally keep the
+ * current NODATASUM flag, chattr +C then chattr -C will
+ * keep the NODATASUM flag, and no way to remove that
+ * flag.
+ *
+ * So respect the current mount option for NODATASUM flag.
+ */
+ if (!btrfs_test_opt(fs_info, NODATASUM))
+ inode_flags &= ~BTRFS_INODE_NODATASUM;
+ } else if (!S_ISREG(inode->vfs_inode.i_mode)) {
inode_flags &= ~BTRFS_INODE_NODATACOW;
}
}
diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c
index fc5c14b5adad..4eca2b5e4a92 100644
--- a/fs/btrfs/relocation.c
+++ b/fs/btrfs/relocation.c
@@ -5555,6 +5555,24 @@ static noinline_for_stack int mark_garbage_root(struct btrfs_root *root)
return ret;
}
+static void release_recovered_fs_roots(struct list_head *roots, bool drop_reloc_refs)
+{
+ struct btrfs_root *root;
+ struct btrfs_root *next;
+
+ list_for_each_entry_safe(root, next, roots, reloc_dirty_list) {
+ list_del_init(&root->reloc_dirty_list);
+ if (drop_reloc_refs) {
+ struct btrfs_root *reloc_root = root->reloc_root;
+
+ ASSERT(reloc_root);
+ root->reloc_root = NULL;
+ btrfs_put_root(reloc_root);
+ }
+ btrfs_put_root(root);
+ }
+}
+
/*
* recover relocation interrupted by system crash.
*
@@ -5564,6 +5582,7 @@ static noinline_for_stack int mark_garbage_root(struct btrfs_root *root)
int btrfs_recover_relocation(struct btrfs_fs_info *fs_info)
{
LIST_HEAD(reloc_roots);
+ LIST_HEAD(recovered_roots);
struct btrfs_key key;
struct btrfs_root *fs_root;
struct btrfs_root *reloc_root;
@@ -5680,7 +5699,7 @@ int btrfs_recover_relocation(struct btrfs_fs_info *fs_info)
ret = PTR_ERR(fs_root);
list_add_tail(&reloc_root->root_list, &reloc_roots);
btrfs_end_transaction(trans);
- goto out_unset;
+ goto out_drop_reloc_refs;
}
ret = __add_reloc_root(reloc_root, rc);
@@ -5689,15 +5708,17 @@ int btrfs_recover_relocation(struct btrfs_fs_info *fs_info)
list_add_tail(&reloc_root->root_list, &reloc_roots);
btrfs_put_root(fs_root);
btrfs_end_transaction(trans);
- goto out_unset;
+ goto out_drop_reloc_refs;
}
+ ASSERT(list_empty(&fs_root->reloc_dirty_list));
fs_root->reloc_root = btrfs_grab_root(reloc_root);
- btrfs_put_root(fs_root);
+ list_add_tail(&fs_root->reloc_dirty_list, &recovered_roots);
}
ret = btrfs_commit_transaction(trans);
if (ret)
- goto out_unset;
+ goto out_drop_reloc_refs;
+ release_recovered_fs_roots(&recovered_roots, false);
merge_reloc_roots(rc);
@@ -5713,6 +5734,8 @@ out_clean:
ret2 = clean_dirty_subvols(rc);
if (ret2 < 0 && !ret)
ret = ret2;
+out_drop_reloc_refs:
+ release_recovered_fs_roots(&recovered_roots, true);
out_unset:
unset_reloc_control(rc);
reloc_chunk_end(fs_info);
diff --git a/fs/buffer.c b/fs/buffer.c
index 9af5f061a1f8..955ab07b34eb 100644
--- a/fs/buffer.c
+++ b/fs/buffer.c
@@ -2177,6 +2177,7 @@ void block_commit_write(struct folio *folio, size_t from, size_t to)
{
size_t block_start, block_end;
bool partial = false;
+ bool uptodate = folio_test_uptodate(folio);
unsigned blocksize;
struct buffer_head *bh, *head;
@@ -2199,6 +2200,8 @@ void block_commit_write(struct folio *folio, size_t from, size_t to)
clear_buffer_new(bh);
block_start = block_end;
+ if (uptodate && block_start >= to)
+ break;
bh = bh->b_this_page;
} while (bh != head);
diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c
index ecf33b66610c..e9f561bedf47 100644
--- a/fs/ceph/addr.c
+++ b/fs/ceph/addr.c
@@ -1426,6 +1426,16 @@ void ceph_shift_unused_folios_left(struct folio_batch *fbatch)
fbatch->nr = n;
}
+static void ceph_undo_wrbuffer_claim(struct inode *inode, struct folio *folio)
+{
+ struct ceph_snap_context *snapc = folio_detach_private(folio);
+
+ if (!snapc)
+ return;
+ ceph_put_wrbuffer_cap_refs(ceph_inode(inode), 1, snapc);
+ ceph_put_snap_context(snapc);
+}
+
static
int ceph_submit_write(struct address_space *mapping,
struct writeback_control *wbc,
@@ -1489,6 +1499,7 @@ new_request:
if (!page)
continue;
+ ceph_undo_wrbuffer_claim(inode, page_folio(page));
redirty_page_for_writepage(wbc, page);
unlock_page(page);
}
diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c
index d7283fb54cec..bb5ef0eacd8f 100644
--- a/fs/ceph/caps.c
+++ b/fs/ceph/caps.c
@@ -979,6 +979,27 @@ int __ceph_caps_revoking_other(struct ceph_inode_info *ci,
return 0;
}
+/*
+ * Return true if any cap of this inode holds caps which the MDS has
+ * revoked, but which we have not released yet.
+ */
+static bool __ceph_is_any_revoking(const struct ceph_inode_info *ci)
+{
+ const struct rb_node *p;
+
+ lockdep_assert_held(&ci->i_ceph_lock);
+
+ for (p = rb_first(&ci->i_caps); p; p = rb_next(p)) {
+ const struct ceph_cap *cap =
+ rb_entry(p, struct ceph_cap, ci_node);
+
+ if (cap->implemented & ~cap->issued)
+ return true;
+ }
+
+ return false;
+}
+
int __ceph_caps_used(struct ceph_inode_info *ci)
{
int used = 0;
@@ -1421,6 +1442,9 @@ static void __prep_cap(struct cap_msg_args *arg, struct ceph_cap *cap,
cap->implemented &= cap->issued | used;
cap->mds_wanted = want;
+ if ((ci->i_ceph_flags & CEPH_I_FLUSH_FORCE) != 0 && !__ceph_is_any_revoking(ci))
+ clear_bit(CEPH_I_FLUSH_FORCE_BIT, &ci->i_ceph_flags);
+
arg->session = cap->session;
arg->ino = ceph_vino(inode).ino;
arg->cid = cap->cap_id;
@@ -2038,6 +2062,14 @@ void ceph_check_caps(struct ceph_inode_info *ci, int flags)
if (ci->i_ceph_flags & CEPH_I_FLUSH)
flags |= CHECK_CAPS_FLUSH;
+ /*
+ * A revoke whose response was deferred (see handle_cap_grant()) must
+ * still be acknowledged. Replay the forced flush here so that even a
+ * check triggered by writeback/invalidation completion sends a cap
+ * message to the MDS.
+ */
+ if (ci->i_ceph_flags & CEPH_I_FLUSH_FORCE)
+ flags |= CHECK_CAPS_FLUSH_FORCE;
retry:
/* Caps wanted by virtue of active open files. */
file_wanted = __ceph_caps_file_wanted(ci);
@@ -2589,9 +2621,14 @@ static void __kick_flushing_caps(struct ceph_mds_client *mdsc,
}
}
- list_for_each_entry(cf, &ci->i_cap_flush_list, i_list) {
- if (cf->tid < first_tid)
+ cf = list_first_entry(&ci->i_cap_flush_list, struct ceph_cap_flush, i_list);
+ while (&cf->i_list != &ci->i_cap_flush_list) {
+ struct ceph_cap_flush *next;
+
+ if (cf->tid < first_tid) {
+ cf = list_next_entry(cf, i_list);
continue;
+ }
cap = ci->i_auth_cap;
if (!(cap && cap->session == session)) {
@@ -2601,6 +2638,7 @@ static void __kick_flushing_caps(struct ceph_mds_client *mdsc,
}
first_tid = cf->tid + 1;
+ next = list_next_entry(cf, i_list);
if (!cf->is_capsnap) {
struct cap_msg_args arg;
@@ -2641,6 +2679,7 @@ static void __kick_flushing_caps(struct ceph_mds_client *mdsc,
}
spin_lock(&ci->i_ceph_lock);
+ cf = next;
}
}
@@ -3757,13 +3796,30 @@ static void handle_cap_grant(struct inode *inode,
BUG_ON(cap->issued & ~cap->implemented);
/* don't let check_caps skip sending a response to MDS for revoke msgs */
- if (!revoke_wait && le32_to_cpu(grant->op) == CEPH_CAP_OP_REVOKE) {
- cap->mds_wanted = 0;
- flags |= CHECK_CAPS_FLUSH_FORCE;
- if (cap == ci->i_auth_cap)
- check_caps = 1; /* check auth cap only */
- else
- check_caps = 2; /* check all caps */
+ if (le32_to_cpu(grant->op) == CEPH_CAP_OP_REVOKE) {
+ if (revoke_wait) {
+ /*
+ * We can't ack the revoke yet: the response is deferred
+ * until the writeback or cache invalidation queued above
+ * completes. Set the CEPH_I_FLUSH_FORCE flag to remember
+ * that a forced cap message is owed so that deferred
+ * completion (ceph_put_wrbuffer_cap_refs() or the
+ * invalidate worker, both of which call ceph_check_caps())
+ * actually sends one, even if by then the revoked caps look
+ * unused, the inode is retaining caps, or the MDS has
+ * re-granted them. Without this, the cap message is never
+ * sent and the MDS hangs ("isn't responding to
+ * mclientcaps(revoke)").
+ */
+ set_bit(CEPH_I_FLUSH_FORCE_BIT, &ci->i_ceph_flags);
+ } else {
+ cap->mds_wanted = 0;
+ flags |= CHECK_CAPS_FLUSH_FORCE;
+ if (cap == ci->i_auth_cap)
+ check_caps = 1; /* check auth cap only */
+ else
+ check_caps = 2; /* check all caps */
+ }
}
if (extra_info->inline_version > 0 &&
diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
index ef9e92e362d3..40326517481c 100644
--- a/fs/ceph/dir.c
+++ b/fs/ceph/dir.c
@@ -1764,11 +1764,11 @@ static int __dir_lease_check(const struct dentry *dentry,
if (ret > 0) {
if (time_before(jiffies, di->time + lwc->dir_lease_ttl))
return STOP;
+ if (!lwc->expire_dir_lease)
+ return KEEP;
/* Move dentry to tail of dir lease list if we don't want
* to delete it. So dentries in the list are checked in a
* round robin manner */
- if (!lwc->expire_dir_lease)
- return TOUCH;
if (dentry->d_lockref.count > 0 ||
(di->flags & CEPH_DENTRY_REFERENCED))
return TOUCH;
@@ -1795,7 +1795,7 @@ int ceph_trim_dentries(struct ceph_mds_client *mdsc)
lwc.dir_lease = false;
lwc.nr_to_scan = CEPH_CAPS_PER_RELEASE * 2;
freed = __dentry_leases_walk(mdsc, &lwc);
- if (!lwc.nr_to_scan) /* more invalid leases */
+ if (freed > 0 && !lwc.nr_to_scan) /* more invalid leases */
return -EAGAIN;
if (lwc.nr_to_scan < CEPH_CAPS_PER_RELEASE)
@@ -1805,6 +1805,10 @@ int ceph_trim_dentries(struct ceph_mds_client *mdsc)
lwc.expire_dir_lease = freed < count;
lwc.dir_lease_ttl = mdsc->fsc->mount_options->caps_wanted_delay_max * HZ;
freed +=__dentry_leases_walk(mdsc, &lwc);
+ if (freed == 0 && count == 0)
+ /* no progress possible currently, retry futile */
+ return 0;
+
if (!lwc.nr_to_scan) /* more to check */
return -EAGAIN;
diff --git a/fs/ceph/export.c b/fs/ceph/export.c
index b2f2af104679..debb9634b9e3 100644
--- a/fs/ceph/export.c
+++ b/fs/ceph/export.c
@@ -442,6 +442,16 @@ static struct dentry *ceph_fh_to_parent(struct super_block *sb,
return dentry;
}
+static int ceph_export_copy_name(char *name, const char *src, u32 len)
+{
+ if (len > NAME_MAX)
+ return -ENAMETOOLONG;
+
+ memcpy(name, src, len);
+ name[len] = '\0';
+ return 0;
+}
+
static int __get_snap_name(struct dentry *parent, char *name,
struct dentry *child)
{
@@ -513,9 +523,8 @@ static int __get_snap_name(struct dentry *parent, char *name,
BUG_ON(!rde->inode.in);
if (ceph_snap(inode) ==
le64_to_cpu(rde->inode.in->snapid)) {
- memcpy(name, rde->name, rde->name_len);
- name[rde->name_len] = '\0';
- err = 0;
+ err = ceph_export_copy_name(name, rde->name,
+ rde->name_len);
goto out;
}
}
@@ -580,8 +589,8 @@ static int ceph_get_name(struct dentry *parent, char *name,
rinfo = &req->r_reply_info;
if (!IS_ENCRYPTED(dir)) {
- memcpy(name, rinfo->dname, rinfo->dname_len);
- name[rinfo->dname_len] = 0;
+ err = ceph_export_copy_name(name, rinfo->dname,
+ rinfo->dname_len);
} else {
struct fscrypt_str oname = FSTR_INIT(NULL, 0);
struct ceph_fname fname = { .dir = dir,
@@ -595,10 +604,9 @@ static int ceph_get_name(struct dentry *parent, char *name,
goto out;
err = ceph_fname_to_usr(&fname, NULL, &oname, NULL);
- if (!err) {
- memcpy(name, oname.name, oname.len);
- name[oname.len] = 0;
- }
+ if (!err)
+ err = ceph_export_copy_name(name, oname.name,
+ oname.len);
ceph_fname_free_buffer(dir, &oname);
}
out:
diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c
index 3c692ad02c85..ec886ca9b526 100644
--- a/fs/ceph/mds_client.c
+++ b/fs/ceph/mds_client.c
@@ -615,10 +615,36 @@ bad:
#define DELEGATED_INO_AVAILABLE xa_mk_value(1)
+static int ceph_insert_deleg_ino(struct ceph_mds_session *s, u64 ino)
+{
+ struct ceph_client *cl = s->s_mdsc->fsc->client;
+ int err;
+
+ /*
+ * Cap how many delegated inodes a single session may hold. This is
+ * the only place that grows the count, so atomic_add_unless() bounds
+ * it at exactly CEPH_MAX_DELEG_INOS; s_num_deleg_inos can never exceed
+ * that.
+ */
+ if (!atomic_add_unless(&s->s_num_deleg_inos, 1, CEPH_MAX_DELEG_INOS)) {
+ pr_warn_ratelimited_client(cl,
+ "MDS session already holds %d delegated inodes\n",
+ CEPH_MAX_DELEG_INOS);
+ return -EOVERFLOW;
+ }
+
+ err = xa_insert(&s->s_delegated_inos, ino, DELEGATED_INO_AVAILABLE,
+ GFP_KERNEL);
+ if (err)
+ atomic_dec(&s->s_num_deleg_inos);
+ return err;
+}
+
static int ceph_parse_deleg_inos(void **p, void *end,
struct ceph_mds_session *s)
{
struct ceph_client *cl = s->s_mdsc->fsc->client;
+ u64 msg_deleg_inos = 0;
u32 sets;
ceph_decode_32_safe(p, end, sets, bad);
@@ -636,16 +662,34 @@ static int ceph_parse_deleg_inos(void **p, void *end,
start, len);
continue;
}
+
+ /*
+ * Bound the number of inodes one reply may delegate.
+ * ceph_insert_deleg_ino() separately caps the per-session
+ * population, so this only has to stop one reply from spinning
+ * the insert loop under an attacker-controlled len.
+ */
+ if (len > (u64)CEPH_MAX_DELEG_INOS ||
+ msg_deleg_inos > (u64)CEPH_MAX_DELEG_INOS - len) {
+ pr_warn_ratelimited_client(cl,
+ "MDS reply delegates too many inodes (have %llu, +%llu, max %d)\n",
+ msg_deleg_inos, len, CEPH_MAX_DELEG_INOS);
+ return -EIO;
+ }
+ msg_deleg_inos += len;
+
while (len--) {
- int err = xa_insert(&s->s_delegated_inos, start++,
- DELEGATED_INO_AVAILABLE,
- GFP_KERNEL);
+ int err = ceph_insert_deleg_ino(s, start++);
+
if (!err) {
doutc(cl, "added delegated inode 0x%llx\n", start - 1);
} else if (err == -EBUSY) {
pr_warn_client(cl,
"MDS delegated inode 0x%llx more than once.\n",
start - 1);
+ } else if (err == -EOVERFLOW) {
+ /* ceph_insert_deleg_ino() already warned. */
+ return -EIO;
} else {
return err;
}
@@ -663,16 +707,17 @@ u64 ceph_get_deleg_ino(struct ceph_mds_session *s)
xa_for_each(&s->s_delegated_inos, ino, val) {
val = xa_erase(&s->s_delegated_inos, ino);
- if (val == DELEGATED_INO_AVAILABLE)
+ if (val == DELEGATED_INO_AVAILABLE) {
+ atomic_dec(&s->s_num_deleg_inos);
return ino;
+ }
}
return 0;
}
int ceph_restore_deleg_ino(struct ceph_mds_session *s, u64 ino)
{
- return xa_insert(&s->s_delegated_inos, ino, DELEGATED_INO_AVAILABLE,
- GFP_KERNEL);
+ return ceph_insert_deleg_ino(s, ino);
}
#else /* BITS_PER_LONG == 64 */
/*
@@ -1059,6 +1104,7 @@ static struct ceph_mds_session *register_session(struct ceph_mds_client *mdsc,
INIT_LIST_HEAD(&s->s_waiting);
INIT_LIST_HEAD(&s->s_unsafe);
xa_init(&s->s_delegated_inos);
+ atomic_set(&s->s_num_deleg_inos, 0);
INIT_LIST_HEAD(&s->s_cap_releases);
INIT_WORK(&s->s_cap_release_work, ceph_cap_release_work);
@@ -4441,7 +4487,9 @@ static void handle_session(struct ceph_mds_session *session,
pr_err_client(cl, "No memory for path\n");
goto fail;
}
- ceph_decode_copy(&p, cap_auths[i].match.path, _len);
+ ceph_decode_copy_safe(&p, end,
+ cap_auths[i].match.path,
+ _len, bad);
/* Remove the tailing '/' */
while (_len && cap_auths[i].match.path[_len - 1] == '/') {
@@ -4458,7 +4506,9 @@ static void handle_session(struct ceph_mds_session *session,
pr_err_client(cl, "No memory for fs_name\n");
goto fail;
}
- ceph_decode_copy(&p, cap_auths[i].match.fs_name, _len);
+ ceph_decode_copy_safe(&p, end,
+ cap_auths[i].match.fs_name,
+ _len, bad);
}
ceph_decode_8_safe(&p, end, cap_auths[i].match.root_squash, bad);
@@ -5106,6 +5156,7 @@ static int send_mds_reconnect(struct ceph_mds_client *mdsc,
/* Serialized by s_mutex against concurrent ceph_get_deleg_ino(). */
xa_destroy(&session->s_delegated_inos);
+ atomic_set(&session->s_num_deleg_inos, 0);
if (session->s_state == CEPH_MDS_SESSION_CLOSED ||
session->s_state == CEPH_MDS_SESSION_REJECTED) {
pr_info_client(cl, "mds%d skipping reconnect, session %s\n",
@@ -5834,9 +5885,11 @@ static void check_new_map(struct ceph_mds_client *mdsc,
ceph_mdsmap_get_addr(newmap, i),
sizeof(struct ceph_entity_addr))) {
/* just close it */
+ ceph_get_mds_session(s);
mutex_unlock(&mdsc->mutex);
mutex_lock(&s->s_mutex);
mutex_lock(&mdsc->mutex);
+ ceph_put_mds_session(s);
ceph_con_close(&s->s_con);
mutex_unlock(&s->s_mutex);
s->s_state = CEPH_MDS_SESSION_RESTARTING;
@@ -5851,6 +5904,7 @@ static void check_new_map(struct ceph_mds_client *mdsc,
newstate >= CEPH_MDS_STATE_RECONNECT) {
int rc;
+ ceph_get_mds_session(s);
mutex_unlock(&mdsc->mutex);
clear_bit(i, targets);
rc = send_mds_reconnect(mdsc, s);
@@ -5859,6 +5913,7 @@ static void check_new_map(struct ceph_mds_client *mdsc,
"mds%d reconnect failed: %d\n",
i, rc);
mutex_lock(&mdsc->mutex);
+ ceph_put_mds_session(s);
}
/*
@@ -5871,9 +5926,11 @@ static void check_new_map(struct ceph_mds_client *mdsc,
pr_info_client(cl, "mds%d recovery completed\n",
s->s_mds);
kick_requests(mdsc, i);
+ ceph_get_mds_session(s);
mutex_unlock(&mdsc->mutex);
mutex_lock(&s->s_mutex);
mutex_lock(&mdsc->mutex);
+ ceph_put_mds_session(s);
ceph_kick_flushing_caps(mdsc, s);
mutex_unlock(&s->s_mutex);
wake_up_session_caps(s, RECONNECT);
diff --git a/fs/ceph/mds_client.h b/fs/ceph/mds_client.h
index 0ece4c9e3529..3c62e3c3530b 100644
--- a/fs/ceph/mds_client.h
+++ b/fs/ceph/mds_client.h
@@ -300,6 +300,7 @@ struct ceph_mds_session {
struct list_head s_waiting; /* waiting requests */
struct list_head s_unsafe; /* unsafe requests */
struct xarray s_delegated_inos;
+ atomic_t s_num_deleg_inos;
};
/*
diff --git a/fs/ceph/mdsmap.c b/fs/ceph/mdsmap.c
index 450a4dc9662e..53079ef34c3a 100644
--- a/fs/ceph/mdsmap.c
+++ b/fs/ceph/mdsmap.c
@@ -3,6 +3,7 @@
#include <linux/bug.h>
#include <linux/err.h>
+#include <linux/overflow.h>
#include <linux/random.h>
#include <linux/slab.h>
#include <linux/types.h>
@@ -126,6 +127,7 @@ struct ceph_mdsmap *ceph_mdsmap_decode(struct ceph_mds_client *mdsc, void **p,
u8 mdsmap_v;
u16 mdsmap_ev;
u32 target;
+ size_t export_targets_len;
m = kzalloc_obj(*m, GFP_NOFS);
if (!m)
@@ -224,8 +226,11 @@ struct ceph_mdsmap *ceph_mdsmap_decode(struct ceph_mds_client *mdsc, void **p,
*p += namelen;
if (info_v >= 2) {
ceph_decode_32_safe(p, end, num_export_targets, bad);
+ export_targets_len = size_mul(num_export_targets,
+ sizeof(u32));
+ ceph_decode_need(p, end, export_targets_len, bad);
pexport_targets = *p;
- *p += num_export_targets * sizeof(u32);
+ *p += export_targets_len;
} else {
num_export_targets = 0;
}
@@ -264,6 +269,10 @@ struct ceph_mdsmap *ceph_mdsmap_decode(struct ceph_mds_client *mdsc, void **p,
goto nomem;
for (j = 0; j < num_export_targets; j++) {
target = ceph_decode_32(&pexport_targets);
+ if (target >= CEPH_MAX_MDS) {
+ err = -EIO;
+ goto corrupt;
+ }
info->export_targets[j] = target;
}
} else {
diff --git a/fs/ceph/super.h b/fs/ceph/super.h
index 1d6aab060780..38f86294182c 100644
--- a/fs/ceph/super.h
+++ b/fs/ceph/super.h
@@ -641,6 +641,15 @@ static inline int ceph_ino_compare(struct inode *inode, void *data)
#define CEPH_MDS_INO_LOG_OFFSET (2 * CEPH_MAX_MDS)
#define CEPH_INO_SYSTEM_BASE ((6*CEPH_MAX_MDS) + (CEPH_MAX_MDS * CEPH_NUM_STRAY))
+/*
+ * Upper bound on the number of delegated inodes a single MDS session may
+ * hold. The MDS normally hands out a small preallocation window (the
+ * userspace mds_client_prealloc_inos option defaults to 1000) and refills
+ * it as the client consumes entries. This leaves generous headroom while
+ * bounding the CPU and memory a malformed delegation interval can consume.
+ */
+#define CEPH_MAX_DELEG_INOS 8192
+
static inline bool ceph_vino_is_reserved(const struct ceph_vino vino)
{
if (vino.ino >= CEPH_INO_SYSTEM_BASE ||
@@ -687,6 +696,10 @@ static inline struct inode *ceph_find_inode(struct super_block *sb,
#define CEPH_I_ASYNC_CREATE_BIT (12) /* async create in flight for this */
#define CEPH_I_SHUTDOWN_BIT (13) /* inode is no longer usable */
#define CEPH_I_ASYNC_CHECK_CAPS_BIT (14) /* check caps after async creating finishes */
+#define CEPH_I_FLUSH_FORCE_BIT (15) /* a revoke's response was deferred;
+ * force a cap message to the MDS once
+ * the deferred work completes
+ */
#define CEPH_I_DIR_ORDERED (1 << CEPH_I_DIR_ORDERED_BIT)
#define CEPH_I_FLUSH (1 << CEPH_I_FLUSH_BIT)
@@ -699,6 +712,7 @@ static inline struct inode *ceph_find_inode(struct super_block *sb,
#define CEPH_I_ODIRECT (1 << CEPH_I_ODIRECT_BIT)
#define CEPH_I_ASYNC_CREATE (1 << CEPH_I_ASYNC_CREATE_BIT)
#define CEPH_I_SHUTDOWN (1 << CEPH_I_SHUTDOWN_BIT)
+#define CEPH_I_FLUSH_FORCE (1 << CEPH_I_FLUSH_FORCE_BIT)
/*
* Masks of ceph inode work.
diff --git a/fs/ceph/xattr.c b/fs/ceph/xattr.c
index 860fc8e1867d..cc4ffbbcb719 100644
--- a/fs/ceph/xattr.c
+++ b/fs/ceph/xattr.c
@@ -848,6 +848,7 @@ start:
name = p;
p += len;
ceph_decode_32_safe(&p, end, len, bad);
+ ceph_decode_need(&p, end, len, bad);
val = p;
p += len;
diff --git a/fs/debugfs/file.c b/fs/debugfs/file.c
index edd6aafbfbaa..08de6652a4f3 100644
--- a/fs/debugfs/file.c
+++ b/fs/debugfs/file.c
@@ -273,7 +273,8 @@ static int debugfs_locked_down(struct inode *inode,
(!real_fops ||
(!real_fops->unlocked_ioctl &&
!real_fops->compat_ioctl &&
- !real_fops->mmap)))
+ !real_fops->mmap &&
+ !real_fops->mmap_prepare)))
return 0;
if (security_locked_down(LOCKDOWN_DEBUGFS))
diff --git a/fs/ecryptfs/crypto.c b/fs/ecryptfs/crypto.c
index 74b02b55e3f6..e67119b6029c 100644
--- a/fs/ecryptfs/crypto.c
+++ b/fs/ecryptfs/crypto.c
@@ -1197,7 +1197,7 @@ static int ecryptfs_read_headers_virt(char *page_virt,
} else
set_default_header_data(crypt_stat);
rc = ecryptfs_parse_packet_set(crypt_stat, (page_virt + offset),
- ecryptfs_dentry);
+ PAGE_SIZE - offset, ecryptfs_dentry);
out:
return rc;
}
diff --git a/fs/ecryptfs/ecryptfs_kernel.h b/fs/ecryptfs/ecryptfs_kernel.h
index f4f56a92bd56..7d2488a10b17 100644
--- a/fs/ecryptfs/ecryptfs_kernel.h
+++ b/fs/ecryptfs/ecryptfs_kernel.h
@@ -580,7 +580,8 @@ int ecryptfs_generate_key_packet_set(char *dest_base,
size_t *len, size_t max);
int
ecryptfs_parse_packet_set(struct ecryptfs_crypt_stat *crypt_stat,
- unsigned char *src, struct dentry *ecryptfs_dentry);
+ unsigned char *src, size_t src_size,
+ struct dentry *ecryptfs_dentry);
int ecryptfs_truncate(struct dentry *dentry, loff_t new_length);
ssize_t
ecryptfs_getxattr_lower(struct dentry *lower_dentry, struct inode *lower_inode,
diff --git a/fs/ecryptfs/keystore.c b/fs/ecryptfs/keystore.c
index ebebc9551f1f..51651314b7a6 100644
--- a/fs/ecryptfs/keystore.c
+++ b/fs/ecryptfs/keystore.c
@@ -894,6 +894,12 @@ ecryptfs_parse_tag_70_packet(char **filename, size_t *filename_size,
"rc = [%d]\n", __func__, rc);
goto out;
}
+ if (s->parsed_tag_70_packet_size < (ECRYPTFS_SIG_SIZE + 2)) {
+ ecryptfs_printk(KERN_WARNING, "Invalid packet size [%zd]\n",
+ s->parsed_tag_70_packet_size);
+ rc = -EINVAL;
+ goto out;
+ }
s->block_aligned_filename_size = (s->parsed_tag_70_packet_size
- ECRYPTFS_SIG_SIZE - 1);
if ((1 + s->packet_size_len + s->parsed_tag_70_packet_size)
@@ -1384,10 +1390,20 @@ parse_tag_3_packet(struct ecryptfs_crypt_stat *crypt_stat,
}
(*new_auth_tok)->session_key.encrypted_key_size =
(body_size - (ECRYPTFS_SALT_SIZE + 5));
+ /*
+ * Although encrypted_key_size is copied into the
+ * encrypted_key[ECRYPTFS_MAX_ENCRYPTED_KEY_BYTES] buffer here,
+ * it later bounds operations on a smaller buffer:
+ * decrypt_passphrase_encrypted_session_key() sets decrypted_key_size =
+ * encrypted_key_size and decrypts into
+ * decrypted_key[ECRYPTFS_MAX_KEY_BYTES], then memcpy's into
+ * crypt_stat->key[ECRYPTFS_MAX_KEY_BYTES]. Limit to
+ * ECRYPTFS_MAX_KEY_BYTES to protect those smaller buffers.
+ */
if ((*new_auth_tok)->session_key.encrypted_key_size
- > ECRYPTFS_MAX_ENCRYPTED_KEY_BYTES) {
+ > ECRYPTFS_MAX_KEY_BYTES) {
printk(KERN_WARNING "Tag 3 packet contains key larger "
- "than ECRYPTFS_MAX_ENCRYPTED_KEY_BYTES\n");
+ "than ECRYPTFS_MAX_KEY_BYTES\n");
rc = -EINVAL;
goto out_free;
}
@@ -1537,7 +1553,7 @@ parse_tag_11_packet(unsigned char *data, unsigned char *contents,
}
(*packet_size) += length_size;
(*tag_11_contents_size) = (body_size - 14);
- if (unlikely((*packet_size) + body_size + 1 > max_packet_size)) {
+ if (unlikely((*packet_size) + body_size > max_packet_size)) {
printk(KERN_ERR "Packet size exceeds max\n");
rc = -EINVAL;
goto out;
@@ -1704,6 +1720,7 @@ out:
* ecryptfs_parse_packet_set
* @crypt_stat: The cryptographic context
* @src: Virtual address of region of memory containing the packets
+ * @src_size: Size of the packet set buffer
* @ecryptfs_dentry: The eCryptfs dentry associated with the packet set
*
* Get crypt_stat to have the file's session key if the requisite key
@@ -1714,7 +1731,7 @@ out:
* conditions.
*/
int ecryptfs_parse_packet_set(struct ecryptfs_crypt_stat *crypt_stat,
- unsigned char *src,
+ unsigned char *src, size_t src_size,
struct dentry *ecryptfs_dentry)
{
size_t i = 0;
@@ -1736,7 +1753,11 @@ int ecryptfs_parse_packet_set(struct ecryptfs_crypt_stat *crypt_stat,
* added the our &auth_tok_list */
next_packet_is_auth_tok_packet = 1;
while (next_packet_is_auth_tok_packet) {
- size_t max_packet_size = ((PAGE_SIZE - 8) - i);
+ size_t max_packet_size;
+
+ if (i >= src_size)
+ break;
+ max_packet_size = src_size - i;
switch (src[i]) {
case ECRYPTFS_TAG_3_PACKET_TYPE:
@@ -1751,12 +1772,16 @@ int ecryptfs_parse_packet_set(struct ecryptfs_crypt_stat *crypt_stat,
goto out_wipe_list;
}
i += packet_size;
+ if (i > src_size) {
+ rc = -EIO;
+ goto out_wipe_list;
+ }
rc = parse_tag_11_packet((unsigned char *)&src[i],
sig_tmp_space,
ECRYPTFS_SIG_SIZE,
&tag_11_contents_size,
&tag_11_packet_size,
- max_packet_size);
+ src_size - i);
if (rc) {
ecryptfs_printk(KERN_ERR, "No valid "
"(ecryptfs-specific) literal "
@@ -1768,6 +1793,10 @@ int ecryptfs_parse_packet_set(struct ecryptfs_crypt_stat *crypt_stat,
goto out_wipe_list;
}
i += tag_11_packet_size;
+ if (i > src_size) {
+ rc = -EIO;
+ goto out_wipe_list;
+ }
if (ECRYPTFS_SIG_SIZE != tag_11_contents_size) {
ecryptfs_printk(KERN_ERR, "Expected "
"signature of size [%d]; "
@@ -1793,6 +1822,10 @@ int ecryptfs_parse_packet_set(struct ecryptfs_crypt_stat *crypt_stat,
goto out_wipe_list;
}
i += packet_size;
+ if (i > src_size) {
+ rc = -EIO;
+ goto out_wipe_list;
+ }
crypt_stat->flags |= ECRYPTFS_ENCRYPTED;
break;
case ECRYPTFS_TAG_11_PACKET_TYPE:
diff --git a/fs/ecryptfs/messaging.c b/fs/ecryptfs/messaging.c
index 03c60f0850ca..73c7b8215e66 100644
--- a/fs/ecryptfs/messaging.c
+++ b/fs/ecryptfs/messaging.c
@@ -166,6 +166,7 @@ int ecryptfs_exorcise_daemon(struct ecryptfs_daemon *daemon)
mutex_unlock(&daemon->mux);
goto out;
}
+ mutex_lock(&ecryptfs_msg_ctx_lists_mux);
list_for_each_entry_safe(msg_ctx, msg_ctx_tmp,
&daemon->msg_ctx_out_queue, daemon_out_list) {
list_del(&msg_ctx->daemon_out_list);
@@ -174,6 +175,7 @@ int ecryptfs_exorcise_daemon(struct ecryptfs_daemon *daemon)
"the out queue of a dying daemon\n", __func__);
ecryptfs_msg_ctx_alloc_to_free(msg_ctx);
}
+ mutex_unlock(&ecryptfs_msg_ctx_lists_mux);
hlist_del(&daemon->euid_chain);
mutex_unlock(&daemon->mux);
kfree_sensitive(daemon);
@@ -284,9 +286,16 @@ ecryptfs_send_message_locked(char *data, int data_len, u8 msg_type,
mutex_unlock(&ecryptfs_msg_ctx_lists_mux);
rc = ecryptfs_send_miscdev(data, data_len, *msg_ctx, msg_type, 0,
daemon);
- if (rc)
+ if (rc) {
printk(KERN_ERR "%s: Error attempting to send message to "
"userspace daemon; rc = [%d]\n", __func__, rc);
+ mutex_lock(&ecryptfs_msg_ctx_lists_mux);
+ mutex_lock(&(*msg_ctx)->mux);
+ ecryptfs_msg_ctx_alloc_to_free(*msg_ctx);
+ mutex_unlock(&(*msg_ctx)->mux);
+ mutex_unlock(&ecryptfs_msg_ctx_lists_mux);
+ *msg_ctx = NULL;
+ }
out:
return rc;
}
diff --git a/fs/ecryptfs/miscdev.c b/fs/ecryptfs/miscdev.c
index 5a7d08149922..68804399a5df 100644
--- a/fs/ecryptfs/miscdev.c
+++ b/fs/ecryptfs/miscdev.c
@@ -360,7 +360,7 @@ ecryptfs_miscdev_write(struct file *file, const char __user *buf,
u32 seq;
size_t packet_size, packet_size_length;
char *data;
- unsigned char packet_size_peek[ECRYPTFS_MAX_PKT_LEN_SIZE];
+ unsigned char packet_size_peek[ECRYPTFS_MAX_PKT_LEN_SIZE] = { };
ssize_t rc;
if (count == 0) {
@@ -376,7 +376,8 @@ ecryptfs_miscdev_write(struct file *file, const char __user *buf,
}
if (copy_from_user(packet_size_peek, &buf[PKT_LEN_OFFSET],
- sizeof(packet_size_peek))) {
+ min_t(size_t, count - PKT_LEN_OFFSET,
+ sizeof(packet_size_peek)))) {
printk(KERN_WARNING "%s: Error while inspecting packet size\n",
__func__);
return -EFAULT;
diff --git a/fs/ecryptfs/super.c b/fs/ecryptfs/super.c
index 3bc21d677564..686b2b4a9cb5 100644
--- a/fs/ecryptfs/super.c
+++ b/fs/ecryptfs/super.c
@@ -150,6 +150,13 @@ static int ecryptfs_show_options(struct seq_file *m, struct dentry *root)
if (mount_crypt_stat->global_default_cipher_key_size)
seq_printf(m, ",ecryptfs_key_bytes=%zd",
mount_crypt_stat->global_default_cipher_key_size);
+ if (mount_crypt_stat->flags & ECRYPTFS_GLOBAL_ENCRYPT_FILENAMES) {
+ seq_printf(m, ",ecryptfs_fn_cipher=%s",
+ mount_crypt_stat->global_default_fn_cipher_name);
+ if (mount_crypt_stat->global_default_fn_cipher_key_bytes)
+ seq_printf(m, ",ecryptfs_fn_key_bytes=%zd",
+ mount_crypt_stat->global_default_fn_cipher_key_bytes);
+ }
if (mount_crypt_stat->flags & ECRYPTFS_PLAINTEXT_PASSTHROUGH_ENABLED)
seq_printf(m, ",ecryptfs_passthrough");
if (mount_crypt_stat->flags & ECRYPTFS_XATTR_METADATA_ENABLED)
diff --git a/fs/efivarfs/super.c b/fs/efivarfs/super.c
index 733c19571f1c..8d33f11db2a1 100644
--- a/fs/efivarfs/super.c
+++ b/fs/efivarfs/super.c
@@ -89,12 +89,30 @@ static int efivarfs_statfs(struct dentry *dentry, struct kstatfs *buf)
/* Some UEFI firmware does not implement QueryVariableInfo() */
storage_space = remaining_space = 0;
if (efi_rt_services_supported(EFI_RT_SUPPORTED_QUERY_VARIABLE_INFO)) {
- status = efivar_query_variable_info(attr, &storage_space,
- &remaining_space,
- &max_variable_size);
- if (status != EFI_SUCCESS && status != EFI_UNSUPPORTED)
- pr_warn_ratelimited("query_variable_info() failed: 0x%lx\n",
- status);
+ static DEFINE_RATELIMIT_STATE(_rs, 2 * HZ, 5);
+ static u64 storage, remaining;
+ static DEFINE_SPINLOCK(lock);
+
+ if (!__ratelimit(&_rs)) {
+ ratelimit_set_flags(&_rs, RATELIMIT_MSG_ON_RELEASE);
+
+ spin_lock(&lock);
+ storage_space = storage;
+ remaining_space = remaining;
+ spin_unlock(&lock);
+ } else {
+ status = efivar_query_variable_info(attr, &storage_space,
+ &remaining_space,
+ &max_variable_size);
+ if (status != EFI_SUCCESS && status != EFI_UNSUPPORTED)
+ pr_warn("query_variable_info() failed: 0x%lx\n",
+ status);
+
+ spin_lock(&lock);
+ storage = storage_space;
+ remaining = remaining_space;
+ spin_unlock(&lock);
+ }
}
/*
diff --git a/fs/erofs/zutil.c b/fs/erofs/zutil.c
index a0ce9226bc02..6b9bfa2ef3b6 100644
--- a/fs/erofs/zutil.c
+++ b/fs/erofs/zutil.c
@@ -79,6 +79,8 @@ int z_erofs_gbuf_growsize(unsigned int nrpages)
for (i = 0; i < z_erofs_gbuf_count; ++i) {
gbuf = &z_erofs_gbufpool[i];
+ if (gbuf->nrpages >= nrpages)
+ continue;
tmp_pages = kzalloc_objs(*tmp_pages, nrpages);
if (!tmp_pages)
goto out;
diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c
index 29808629cce5..269b1c9fba5f 100644
--- a/fs/ext2/inode.c
+++ b/fs/ext2/inode.c
@@ -1258,12 +1258,9 @@ static int ext2_setsize(struct inode *inode, loff_t newsize)
filemap_invalidate_unlock(inode->i_mapping);
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
- if (inode_needs_sync(inode)) {
- mmb_sync(&EXT2_I(inode)->i_metadata_bhs);
+ mark_inode_dirty(inode);
+ if (inode_needs_sync(inode))
sync_inode_metadata(inode, 1);
- } else {
- mark_inode_dirty(inode);
- }
return 0;
}
diff --git a/fs/ext2/xattr.c b/fs/ext2/xattr.c
index e55d16abf422..be63f89402a3 100644
--- a/fs/ext2/xattr.c
+++ b/fs/ext2/xattr.c
@@ -777,6 +777,7 @@ ext2_xattr_set2(struct inode *inode, struct buffer_head *old_bh,
/* Update the inode. */
EXT2_I(inode)->i_file_acl = new_bh ? new_bh->b_blocknr : 0;
inode_set_ctime_current(inode);
+ mark_inode_dirty(inode);
if (IS_SYNC(inode)) {
error = sync_inode_metadata(inode, 1);
/* In case sync failed due to ENOSPC the inode was actually
@@ -789,8 +790,7 @@ ext2_xattr_set2(struct inode *inode, struct buffer_head *old_bh,
}
goto cleanup;
}
- } else
- mark_inode_dirty(inode);
+ }
error = 0;
if (old_bh && old_bh != new_bh) {
diff --git a/fs/fat/misc.c b/fs/fat/misc.c
index 3027ef53af21..2a0fea26a99a 100644
--- a/fs/fat/misc.c
+++ b/fs/fat/misc.c
@@ -133,7 +133,11 @@ int fat_chain_add(struct inode *inode, int new_dclus, int nr_cluster)
ret = fat_ent_read(inode, &fatent, last);
if (ret >= 0) {
int wait = inode_needs_sync(inode);
+ int old = ret;
+
ret = fat_ent_write(inode, &fatent, new_dclus, wait);
+ if (ret < 0)
+ fat_ent_write(inode, &fatent, old, wait);
fatent_brelse(&fatent);
}
if (ret < 0)
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 95df51880db2..e7da62a7293d 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -75,6 +75,9 @@ void fuse_chan_set_initialized(struct fuse_chan *fch, struct fuse_chan_param *pa
fch->minor = param->minor;
fch->max_write = param->max_write;
fch->max_pages = param->max_pages;
+
+ if (param->io_uring_enabled)
+ fuse_uring_conn_init(fch);
}
/* Make sure stores before this are seen on another CPU */
@@ -415,11 +418,6 @@ void fuse_chan_set_fc(struct fuse_chan *fch, struct fuse_conn *fc)
fch->conn = fc;
}
-void fuse_chan_io_uring_enable(struct fuse_chan *fch)
-{
- fch->io_uring = 1;
-}
-
void fuse_pqueue_init(struct fuse_pqueue *fpq)
{
spin_lock_init(&fpq->lock);
diff --git a/fs/fuse/dev.h b/fs/fuse/dev.h
index aed69fd14c41..8d25378c0918 100644
--- a/fs/fuse/dev.h
+++ b/fs/fuse/dev.h
@@ -22,6 +22,7 @@ struct fuse_chan_param {
unsigned int minor;
unsigned int max_write;
unsigned int max_pages;
+ bool io_uring_enabled;
};
struct fuse_chan *fuse_chan_new(void);
@@ -34,7 +35,6 @@ void fuse_chan_max_background_set(struct fuse_chan *fch, unsigned int val);
unsigned int fuse_chan_num_waiting(struct fuse_chan *fch);
void fuse_chan_set_fc(struct fuse_chan *fch, struct fuse_conn *fc);
void fuse_chan_set_initialized(struct fuse_chan *fch, struct fuse_chan_param *param);
-void fuse_chan_io_uring_enable(struct fuse_chan *fch);
ssize_t fuse_chan_send(struct fuse_chan *fch, struct fuse_args *args);
int fuse_chan_send_bg(struct fuse_chan *fch, struct fuse_args *args, gfp_t gfp_flags);
int fuse_chan_send_notify_reply(struct fuse_chan *fch, struct fuse_args *args, u64 unique);
diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
index 6872266bae1b..82da15a9cc78 100644
--- a/fs/fuse/dev_uring.c
+++ b/fs/fuse/dev_uring.c
@@ -238,7 +238,6 @@ static struct fuse_ring *fuse_uring_create(struct fuse_chan *fch)
{
struct fuse_ring *ring;
size_t nr_queues = num_possible_cpus();
- struct fuse_ring *res = NULL;
size_t max_payload_size;
ring = kzalloc_obj(*ring, GFP_KERNEL_ACCOUNT);
@@ -258,12 +257,6 @@ static struct fuse_ring *fuse_uring_create(struct fuse_chan *fch)
spin_unlock(&fch->lock);
goto out_err;
}
- if (fch->ring) {
- /* race, another thread created the ring in the meantime */
- spin_unlock(&fch->lock);
- res = fch->ring;
- goto out_err;
- }
init_waitqueue_head(&ring->stop_waitq);
@@ -278,7 +271,13 @@ static struct fuse_ring *fuse_uring_create(struct fuse_chan *fch)
out_err:
kfree(ring->queues);
kfree(ring);
- return res;
+ return NULL;
+}
+
+void fuse_uring_conn_init(struct fuse_chan *fch)
+{
+ if (fuse_uring_create(fch))
+ fch->io_uring = 1;
}
static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring,
@@ -746,6 +745,7 @@ static int fuse_uring_copy_to_ring(struct fuse_ring_ent *ent,
{
struct fuse_ring_queue *queue = ent->queue;
struct fuse_ring *ring = queue->ring;
+ struct fuse_in_header in_header;
int err;
err = -EIO;
@@ -767,8 +767,9 @@ static int fuse_uring_copy_to_ring(struct fuse_ring_ent *ent,
}
/* copy fuse_in_header */
- return copy_header_to_ring(ent, FUSE_URING_HEADER_IN_OUT, &req->in.h,
- sizeof(req->in.h));
+ in_header = req->in.h;
+ return copy_header_to_ring(ent, FUSE_URING_HEADER_IN_OUT, &in_header,
+ sizeof(in_header));
}
static int fuse_uring_prepare_send(struct fuse_ring_ent *ent,
@@ -873,11 +874,13 @@ static void fuse_uring_commit(struct fuse_ring_ent *ent, struct fuse_req *req,
unsigned int issue_flags)
{
struct fuse_ring *ring = ent->queue->ring;
+ struct fuse_out_header out_header;
ssize_t err = -EFAULT;
- if (copy_header_from_ring(ent, FUSE_URING_HEADER_IN_OUT, &req->out.h,
- sizeof(req->out.h)))
+ if (copy_header_from_ring(ent, FUSE_URING_HEADER_IN_OUT, &out_header,
+ sizeof(out_header)))
goto out;
+ req->out.h = out_header;
err = fuse_uring_out_header_has_err(&req->out.h, req);
if (err) {
@@ -1178,15 +1181,10 @@ static int fuse_uring_register(struct io_uring_cmd *cmd,
struct fuse_ring *ring = smp_load_acquire(&fch->ring);
struct fuse_ring_queue *queue;
struct fuse_ring_ent *ent;
- int err;
unsigned int qid = READ_ONCE(cmd_req->qid);
- err = -ENOMEM;
- if (!ring) {
- ring = fuse_uring_create(fch);
- if (!ring)
- return err;
- }
+ if (!ring)
+ return -EINVAL;
if (qid >= ring->nr_queues) {
pr_info_ratelimited("fuse: Invalid ring qid %u\n", qid);
@@ -1197,7 +1195,7 @@ static int fuse_uring_register(struct io_uring_cmd *cmd,
if (!queue) {
queue = fuse_uring_create_queue(ring, qid);
if (!queue)
- return err;
+ return -ENOMEM;
}
/*
diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h
index 55f8d04e4b0b..d721a4fc0215 100644
--- a/fs/fuse/dev_uring_i.h
+++ b/fs/fuse/dev_uring_i.h
@@ -135,6 +135,7 @@ struct fuse_ring {
bool ready;
};
+void fuse_uring_conn_init(struct fuse_chan *fch);
void fuse_uring_stop_queues(struct fuse_ring *ring);
void fuse_uring_abort_end_requests(struct fuse_ring *ring);
int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags);
@@ -174,6 +175,10 @@ static inline bool fuse_uring_ready(struct fuse_chan *fch)
#else /* CONFIG_FUSE_IO_URING */
+static inline void fuse_uring_conn_init(struct fuse_chan *fch)
+{
+}
+
static inline void fuse_uring_abort(struct fuse_chan *fch)
{
}
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index d975073c6029..ce4891a04a37 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -1272,6 +1272,7 @@ static void process_init_reply(struct fuse_args *args, int error)
struct fuse_mount *fm = ia->fm;
struct fuse_conn *fc = fm->fc;
struct fuse_init_out *arg = &ia->out;
+ bool io_uring_enabled = false;
bool ok = true;
if (error || arg->major != FUSE_KERNEL_VERSION)
@@ -1402,7 +1403,7 @@ static void process_init_reply(struct fuse_args *args, int error)
ok = false;
}
if (flags & FUSE_OVER_IO_URING && fuse_uring_enabled())
- fuse_chan_io_uring_enable(fc->chan);
+ io_uring_enabled = true;
if (flags & FUSE_REQUEST_TIMEOUT)
timeout = arg->request_timeout;
@@ -1432,6 +1433,7 @@ static void process_init_reply(struct fuse_args *args, int error)
.minor = fc->minor,
.max_write = fc->max_write,
.max_pages = fc->max_pages,
+ .io_uring_enabled = io_uring_enabled,
};
fuse_chan_set_initialized(fc->chan, &cp);
}
diff --git a/fs/iomap/bio.c b/fs/iomap/bio.c
index dc8ac7e370a5..30ef78a66b4f 100644
--- a/fs/iomap/bio.c
+++ b/fs/iomap/bio.c
@@ -179,7 +179,7 @@ int iomap_bio_read_folio_range_sync(const struct iomap_iter *iter,
if (srcmap->flags & IOMAP_F_INTEGRITY)
fs_bio_integrity_alloc(&bio);
error = submit_bio_wait(&bio);
- if (srcmap->flags & IOMAP_F_INTEGRITY) {
+ if (bio_integrity(&bio)) {
if (!error)
error = fs_bio_integrity_verify(&bio, sector, len);
fs_bio_integrity_free(&bio);
diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c
index e2cd5f92babe..01daed391856 100644
--- a/fs/iomap/direct-io.c
+++ b/fs/iomap/direct-io.c
@@ -358,6 +358,7 @@ static ssize_t iomap_dio_bio_iter_one(struct iomap_iter *iter,
iomap_max_bio_size(&iter->iomap), alignment);
else
ret = bio_iov_iter_get_pages(bio, dio->submit.iter,
+ bdev_dma_alignment(bio->bi_bdev),
alignment - 1);
if (unlikely(ret))
goto out_put_bio;
diff --git a/fs/jbd2/checkpoint.c b/fs/jbd2/checkpoint.c
index 1508e2f54462..513273712010 100644
--- a/fs/jbd2/checkpoint.c
+++ b/fs/jbd2/checkpoint.c
@@ -358,15 +358,16 @@ int jbd2_cleanup_journal_tail(journal_t *journal)
/*
* journal_shrink_one_cp_list
*
- * Find all the written-back checkpoint buffers in the given list
- * and try to release them. If the whole transaction is released, set
- * the 'released' parameter. Return the number of released checkpointed
- * buffers.
+ * Find written-back checkpoint buffers in the given list and try to release
+ * them. If 'nr_to_scan' is set, scan at most that many buffers. If the whole
+ * transaction is released, set the 'released' parameter. Return the number of
+ * released checkpointed buffers.
*
* Called with j_list_lock held.
*/
static unsigned long journal_shrink_one_cp_list(struct journal_head *jh,
enum jbd2_shrink_type type,
+ unsigned long *nr_to_scan,
bool *released)
{
struct journal_head *last_jh;
@@ -375,13 +376,15 @@ static unsigned long journal_shrink_one_cp_list(struct journal_head *jh,
int ret;
*released = false;
- if (!jh)
+ if (!jh || (nr_to_scan && !*nr_to_scan))
return 0;
last_jh = jh->b_cpprev;
do {
jh = next_jh;
next_jh = jh->b_cpnext;
+ if (nr_to_scan)
+ (*nr_to_scan)--;
if (type == JBD2_SHRINK_DESTROY) {
ret = __jbd2_journal_remove_checkpoint(jh);
@@ -389,7 +392,7 @@ static unsigned long journal_shrink_one_cp_list(struct journal_head *jh,
ret = jbd2_journal_try_remove_checkpoint(jh);
if (ret < 0) {
if (type == JBD2_SHRINK_BUSY_SKIP)
- continue;
+ goto next;
break;
}
}
@@ -400,9 +403,10 @@ static unsigned long journal_shrink_one_cp_list(struct journal_head *jh,
break;
}
+next:
if (need_resched())
break;
- } while (jh != last_jh);
+ } while (jh != last_jh && (!nr_to_scan || *nr_to_scan));
return nr_freed;
}
@@ -424,7 +428,6 @@ unsigned long jbd2_journal_shrink_checkpoint_list(journal_t *journal,
tid_t first_tid = 0, last_tid = 0, next_tid = 0;
tid_t tid = 0;
unsigned long nr_freed = 0;
- unsigned long freed;
bool first_set = false;
again:
@@ -457,10 +460,9 @@ again:
next_transaction = transaction->t_cpnext;
tid = transaction->t_tid;
- freed = journal_shrink_one_cp_list(transaction->t_checkpoint_list,
- JBD2_SHRINK_BUSY_SKIP, &released);
- nr_freed += freed;
- (*nr_to_scan) -= min(*nr_to_scan, freed);
+ nr_freed += journal_shrink_one_cp_list(transaction->t_checkpoint_list,
+ JBD2_SHRINK_BUSY_SKIP,
+ nr_to_scan, &released);
if (*nr_to_scan == 0)
break;
if (need_resched() || spin_needbreak(&journal->j_list_lock))
@@ -516,7 +518,7 @@ void __jbd2_journal_clean_checkpoint_list(journal_t *journal,
transaction = next_transaction;
next_transaction = transaction->t_cpnext;
journal_shrink_one_cp_list(transaction->t_checkpoint_list,
- type, &released);
+ type, NULL, &released);
/*
* This function only frees up some memory if possible so we
* dont have an obligation to finish processing. Bail out if
diff --git a/fs/lockd/clntproc.c b/fs/lockd/clntproc.c
index f06faf577cea..f8018bfe9c64 100644
--- a/fs/lockd/clntproc.c
+++ b/fs/lockd/clntproc.c
@@ -487,9 +487,12 @@ static const struct file_lock_operations nlmclnt_lock_ops = {
static void nlmclnt_locks_init_private(struct file_lock *fl, struct nlm_host *host)
{
fl->fl_u.nfs_fl.state = 0;
+ fl->fl_ops = NULL;
fl->fl_u.nfs_fl.owner = nlmclnt_find_lockowner(host,
fl->c.flc_owner);
INIT_LIST_HEAD(&fl->fl_u.nfs_fl.list);
+ if (!fl->fl_u.nfs_fl.owner)
+ return;
fl->fl_ops = &nlmclnt_lock_ops;
}
diff --git a/fs/lockd/lockd.h b/fs/lockd/lockd.h
index e418a50c4180..14cc952fe81a 100644
--- a/fs/lockd/lockd.h
+++ b/fs/lockd/lockd.h
@@ -314,7 +314,7 @@ void nsm_release(struct nsm_handle *nsm);
* This is used in garbage collection and resource reclaim
* A return value != 0 means destroy the lock/block/share
*/
-typedef int (*nlm_host_match_fn_t)(void *cur, struct nlm_host *ref);
+typedef int (*nlm_host_match_fn_t)(void *owner, struct nlm_host *ref);
/*
* Server-side lock handling
diff --git a/fs/lockd/svc.c b/fs/lockd/svc.c
index 490551369ef2..ee90e743064a 100644
--- a/fs/lockd/svc.c
+++ b/fs/lockd/svc.c
@@ -47,7 +47,7 @@
static struct svc_program nlmsvc_program;
-const struct nlmsvc_binding *nlmsvc_ops;
+const struct nlmsvc_binding __rcu *nlmsvc_ops;
EXPORT_SYMBOL_GPL(nlmsvc_ops);
static DEFINE_MUTEX(nlmsvc_mutex);
@@ -142,7 +142,7 @@ lockd(void *vrqstp)
nlmsvc_retry_blocked(rqstp);
svc_recv(rqstp, 0);
}
- if (nlmsvc_ops)
+ if (rcu_access_pointer(nlmsvc_ops))
nlmsvc_invalidate_all();
nlm_shutdown_hosts();
cancel_delayed_work_sync(&ln->grace_period_end);
diff --git a/fs/lockd/svc4proc.c b/fs/lockd/svc4proc.c
index 78e675470c4b..b73004a7987e 100644
--- a/fs/lockd/svc4proc.c
+++ b/fs/lockd/svc4proc.c
@@ -128,7 +128,7 @@ nlm4svc_lookup_host(struct svc_rqst *rqstp, string caller, bool monitored)
{
struct nlm_host *host;
- if (!nlmsvc_ops)
+ if (!rcu_access_pointer(nlmsvc_ops))
return NULL;
host = nlmsvc_lookup_host(rqstp, caller.data, caller.len);
if (!host)
@@ -872,7 +872,8 @@ static __be32 nlm4svc_proc_granted_msg(struct svc_rqst *rqstp)
struct nlm4_testargs_wrapper *argp = rqstp->rq_argp;
struct nlm_host *host;
- host = nlm4svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false);
+ host = nlmsvc_lookup_host(rqstp, argp->xdrgen.alock.caller_name.data,
+ argp->xdrgen.alock.caller_name.len);
if (!host)
return rpc_system_err;
@@ -894,7 +895,7 @@ static __be32 nlm4svc_proc_granted_res(struct svc_rqst *rqstp)
{
struct nlm4_res_wrapper *argp = rqstp->rq_argp;
- if (!nlmsvc_ops)
+ if (!rcu_access_pointer(nlmsvc_ops))
return rpc_success;
if (nlm4_netobj_to_cookie(&argp->cookie, &argp->xdrgen.cookie))
diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c
index 386a881b520f..d410b8c69893 100644
--- a/fs/lockd/svcproc.c
+++ b/fs/lockd/svcproc.c
@@ -133,7 +133,7 @@ nlm3svc_lookup_host(struct svc_rqst *rqstp, string caller, bool monitored)
{
struct nlm_host *host;
- if (!nlmsvc_ops)
+ if (!rcu_access_pointer(nlmsvc_ops))
return NULL;
host = nlmsvc_lookup_host(rqstp, caller.data, caller.len);
if (!host)
@@ -924,7 +924,7 @@ static __be32 nlmsvc_proc_granted_res(struct svc_rqst *rqstp)
{
struct nlm_res_wrapper *argp = rqstp->rq_argp;
- if (!nlmsvc_ops)
+ if (!rcu_access_pointer(nlmsvc_ops))
return rpc_success;
if (nlm_netobj_to_cookie(&argp->cookie, &argp->xdrgen.cookie))
diff --git a/fs/lockd/svcsubs.c b/fs/lockd/svcsubs.c
index a0d1a6fbf61e..ffab76278bb2 100644
--- a/fs/lockd/svcsubs.c
+++ b/fs/lockd/svcsubs.c
@@ -90,22 +90,35 @@ int lock_to_openmode(struct file_lock *lock)
static __be32 nlm_do_fopen(struct svc_rqst *rqstp,
struct nlm_file *file, int mode)
{
+ const struct nlmsvc_binding *ops;
__be32 nlmerr = nlm__int__failed;
__be32 deferred = 0;
int error;
int m;
+ rcu_read_lock();
+ ops = rcu_dereference(nlmsvc_ops);
+ if (!ops || !try_module_get(ops->owner)) {
+ rcu_read_unlock();
+ return nlm__int__failed;
+ }
+ rcu_read_unlock();
+
for (m = O_RDONLY; m <= O_WRONLY; m++) {
struct file **fp = &file->f_file[m];
if (mode != O_RDWR && mode != m)
continue;
- if (*fp)
+ if (*fp) {
+ module_put(ops->owner);
return nlm_granted;
+ }
- error = nlmsvc_ops->fopen(rqstp, &file->f_handle, fp, m);
- if (!error)
+ error = ops->fopen(rqstp, &file->f_handle, fp, m);
+ if (!error) {
+ module_put(ops->owner);
return nlm_granted;
+ }
dprintk("lockd: open failed (errno %d)\n", error);
switch (error) {
@@ -122,6 +135,7 @@ static __be32 nlm_do_fopen(struct svc_rqst *rqstp,
}
}
+ module_put(ops->owner);
return deferred ? deferred : nlmerr;
}
@@ -186,6 +200,33 @@ out_free:
}
/*
+ * Release the struct file references held by a nlm_file.
+ */
+static void nlm_release_files(struct nlm_file *file)
+{
+ const struct nlmsvc_binding *ops;
+ bool have_ops;
+
+ rcu_read_lock();
+ ops = rcu_dereference(nlmsvc_ops);
+ have_ops = ops && try_module_get(ops->owner);
+ rcu_read_unlock();
+
+ if (have_ops) {
+ if (file->f_file[O_RDONLY])
+ ops->fclose(file->f_file[O_RDONLY]);
+ if (file->f_file[O_WRONLY])
+ ops->fclose(file->f_file[O_WRONLY]);
+ module_put(ops->owner);
+ } else {
+ if (file->f_file[O_RDONLY])
+ fput(file->f_file[O_RDONLY]);
+ if (file->f_file[O_WRONLY])
+ fput(file->f_file[O_WRONLY]);
+ }
+}
+
+/*
* Delete a file after having released all locks, blocks and shares
*/
static inline void
@@ -194,10 +235,7 @@ nlm_delete_file(struct nlm_file *file)
nlm_debug_print_file("closing file", file);
if (!hlist_unhashed(&file->f_list)) {
hlist_del(&file->f_list);
- if (file->f_file[O_RDONLY])
- nlmsvc_ops->fclose(file->f_file[O_RDONLY]);
- if (file->f_file[O_WRONLY])
- nlmsvc_ops->fclose(file->f_file[O_WRONLY]);
+ nlm_release_files(file);
kfree(file);
} else {
printk(KERN_WARNING "lockd: attempt to release unknown file!\n");
@@ -312,12 +350,10 @@ nlm_file_inuse(struct nlm_file *file)
return 0;
}
-static void nlm_close_files(struct nlm_file *file)
+static void nlm_file_release(struct nlm_file *file)
{
- if (file->f_file[O_RDONLY])
- nlmsvc_ops->fclose(file->f_file[O_RDONLY]);
- if (file->f_file[O_WRONLY])
- nlmsvc_ops->fclose(file->f_file[O_WRONLY]);
+ if (!nlm_file_inuse(file))
+ nlm_delete_file(file);
}
/*
@@ -327,32 +363,41 @@ static int
nlm_traverse_files(void *data, nlm_host_match_fn_t match,
int (*is_failover_file)(void *data, struct nlm_file *file))
{
- struct hlist_node *next;
- struct nlm_file *file;
+ struct nlm_file *file, *next;
int i, ret = 0;
mutex_lock(&nlm_file_mutex);
for (i = 0; i < FILE_NRHASH; i++) {
- hlist_for_each_entry_safe(file, next, &nlm_files[i], f_list) {
- if (is_failover_file && !is_failover_file(data, file))
- continue;
+ file = hlist_entry_safe(nlm_files[i].first,
+ struct nlm_file, f_list);
+ if (file)
file->f_count++;
- mutex_unlock(&nlm_file_mutex);
-
- /* Traverse locks, blocks and shares of this file
- * and update file->f_locks count */
- if (nlm_inspect_file(data, file, match))
- ret = 1;
+ while (file) {
+ /*
+ * Pin the next neighbour before we drop the mutex
+ * for nlm_inspect_file(); a concurrent
+ * nlm_release_file() under the same mutex would
+ * otherwise be free to unlink and kfree it during
+ * the unlock window, leaving us to dereference a
+ * freed slab when we walked to next afterwards.
+ */
+ next = hlist_entry_safe(file->f_list.next,
+ struct nlm_file, f_list);
+ if (next)
+ next->f_count++;
+
+ if (!is_failover_file || is_failover_file(data, file)) {
+ mutex_unlock(&nlm_file_mutex);
+
+ if (nlm_inspect_file(data, file, match))
+ ret = 1;
+
+ mutex_lock(&nlm_file_mutex);
+ }
- mutex_lock(&nlm_file_mutex);
file->f_count--;
- /* No more references to this file. Let go of it. */
- if (list_empty(&file->f_blocks) && !file->f_locks
- && !file->f_shares && !file->f_count) {
- hlist_del(&file->f_list);
- nlm_close_files(file);
- kfree(file);
- }
+ nlm_file_release(file);
+ file = next;
}
}
mutex_unlock(&nlm_file_mutex);
@@ -512,7 +557,7 @@ EXPORT_SYMBOL_GPL(nlmsvc_unlock_all_by_sb);
static int
nlmsvc_match_ip(void *datap, struct nlm_host *host)
{
- return rpc_cmp_addr(nlm_srcaddr(host), datap);
+ return rpc_cmp_addr(nlm_srcaddr(datap), (struct sockaddr *)host);
}
/**
diff --git a/fs/nfs/callback.c b/fs/nfs/callback.c
index ff4e9fd38e83..bc282b744f34 100644
--- a/fs/nfs/callback.c
+++ b/fs/nfs/callback.c
@@ -231,8 +231,9 @@ int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt)
cb_info->users++;
err_net:
if (!cb_info->users) {
+ xprt_svc_shutdown_bc(xprt);
svc_set_num_threads(cb_info->serv, 0, 0);
- svc_destroy(&cb_info->serv);
+ xprt_svc_destroy_nullify_bc(xprt, &cb_info->serv);
}
err_create:
mutex_unlock(&nfs_callback_mutex);
@@ -254,6 +255,7 @@ void nfs_callback_down(int minorversion, struct net *net, struct rpc_xprt *xprt)
mutex_lock(&nfs_callback_mutex);
serv = cb_info->serv;
+ xprt_svc_shutdown_bc(xprt);
nfs_callback_down_net(minorversion, serv, net);
cb_info->users--;
if (cb_info->users == 0) {
diff --git a/fs/nfs/nfs4client.c b/fs/nfs/nfs4client.c
index 71c271a1700a..819e435c3a42 100644
--- a/fs/nfs/nfs4client.c
+++ b/fs/nfs/nfs4client.c
@@ -915,20 +915,22 @@ static int nfs4_server_common_setup(struct nfs_server *server,
return error;
/* data servers support only a subset of NFSv4.1 */
- if (is_ds_only_client(server->nfs_client))
- return -EPROTONOSUPPORT;
+ if (is_ds_only_client(server->nfs_client)) {
+ error = -EPROTONOSUPPORT;
+ goto out_free_delegation_hash;
+ }
/* We must ensure the session is initialised first */
error = nfs4_init_session(server->nfs_client);
if (error < 0)
- return error;
+ goto out_free_delegation_hash;
nfs_server_set_init_caps(server);
/* Probe the root fh to retrieve its FSID and filehandle */
error = nfs4_get_rootfh(server, mntfh, auth_probe);
if (error < 0)
- return error;
+ goto out_free_delegation_hash;
dprintk("Server FSID: %llx:%llx\n",
(unsigned long long) server->fsid.major,
@@ -937,7 +939,7 @@ static int nfs4_server_common_setup(struct nfs_server *server,
error = nfs_probe_server(server, mntfh);
if (error < 0)
- return error;
+ goto out_free_delegation_hash;
nfs4_session_limit_rwsize(server);
nfs4_session_limit_xasize(server);
@@ -949,6 +951,11 @@ static int nfs4_server_common_setup(struct nfs_server *server,
server->mount_time = jiffies;
server->destroy = nfs4_destroy_server;
return 0;
+
+out_free_delegation_hash:
+ kfree(server->delegation_hash_table);
+ server->delegation_hash_table = NULL;
+ return error;
}
/*
diff --git a/fs/nfs/pnfs.c b/fs/nfs/pnfs.c
index 7715e2bd5871..b1a30a16287f 100644
--- a/fs/nfs/pnfs.c
+++ b/fs/nfs/pnfs.c
@@ -1389,7 +1389,7 @@ pnfs_layout_need_return(struct pnfs_layout_hdr *lo)
return false;
return pnfs_mark_layout_stateid_return(lo, &lo->plh_return_segs,
lo->plh_return_iomode,
- lo->plh_return_seq) != EBUSY;
+ lo->plh_return_seq) != -EBUSY;
}
static void pnfs_layoutreturn_before_put_layout_hdr(struct pnfs_layout_hdr *lo)
@@ -2643,6 +2643,7 @@ out_forget:
spin_unlock(&ino->i_lock);
lseg->pls_layout = lo;
NFS_SERVER(ino)->pnfs_curr_ld->free_lseg(lseg);
+ pnfs_free_lseg_list(&free_me);
return ERR_PTR(-EAGAIN);
}
diff --git a/fs/nfs_common/nfslocalio.c b/fs/nfs_common/nfslocalio.c
index dd715cdb6c04..85aa03a7b020 100644
--- a/fs/nfs_common/nfslocalio.c
+++ b/fs/nfs_common/nfslocalio.c
@@ -292,8 +292,22 @@ struct nfsd_file *nfs_open_local_fh(nfs_uuid_t *uuid,
localio = nfs_to->nfsd_open_local_fh(net, uuid->dom, rpc_clnt, cred,
nfs_fh, pnf, fmode);
if (!IS_ERR(localio) && nfs_uuid_add_file(uuid, nfl) < 0) {
- /* Delete the cached file when racing with nfs_uuid_put() */
+ /*
+ * Delete the cached file when racing with nfs_uuid_put().
+ * Since nfl->nfs_uuid was never published via
+ * rcu_assign_pointer(), nfs_close_local_fh() will early-return
+ * and cannot clean up after us. Drop the slot's file ref and
+ * its paired net ref, then drop the caller-owned nfsd_file ref
+ * (+1) and the entry-time nfsd_net ref carried via nf->nf_net,
+ * and return -ENXIO so the caller never dereferences the
+ * now-cleared localio.
+ */
+ struct nfsd_file __rcu *tmp =
+ (struct nfsd_file __force __rcu *)localio;
+
nfs_to_nfsd_file_put_local(pnf);
+ nfs_to_nfsd_file_put_local(&tmp);
+ localio = ERR_PTR(-ENXIO);
}
nfs_to_nfsd_net_put(net);
diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c
index 24511c3208db..1ea2bfd51825 100644
--- a/fs/nfsd/filecache.c
+++ b/fs/nfsd/filecache.c
@@ -55,6 +55,17 @@
/* We only care about NFSD_MAY_READ/WRITE for this cache */
#define NFSD_FILE_MAY_MASK (NFSD_MAY_READ|NFSD_MAY_WRITE|NFSD_MAY_LOCALIO)
+/* If the shrinker runs between calls to list_lru_walk_node() in
+ * nfsd_file_gc(), the "remaining" count will be wrong. This could
+ * result in premature freeing of some files. This may not matter much
+ * but is easy to fix with this spinlock which temporarily disables
+ * the shrinker.
+ *
+ * It also serializes callers of nfsd_file_dispose_list_delayed()
+ * against per-net shutdown.
+ */
+static DEFINE_SPINLOCK(nfsd_gc_lock);
+
static DEFINE_PER_CPU(unsigned long, nfsd_file_cache_hits);
static DEFINE_PER_CPU(unsigned long, nfsd_file_acquisitions);
static DEFINE_PER_CPU(unsigned long, nfsd_file_allocations);
@@ -62,11 +73,6 @@ static DEFINE_PER_CPU(unsigned long, nfsd_file_releases);
static DEFINE_PER_CPU(unsigned long, nfsd_file_total_age);
static DEFINE_PER_CPU(unsigned long, nfsd_file_evictions);
-struct nfsd_fcache_disposal {
- spinlock_t lock;
- struct list_head freeme;
-};
-
static struct kmem_cache *nfsd_file_slab;
static struct kmem_cache *nfsd_file_mark_slab;
static struct list_lru nfsd_file_lru;
@@ -327,8 +333,11 @@ static void nfsd_file_lru_add(struct nfsd_file *nf)
refcount_inc(&nf->nf_ref);
if (list_lru_add_obj(&nfsd_file_lru, &nf->nf_lru))
trace_nfsd_file_lru_add(nf);
- else
- WARN_ON(1);
+ else {
+ refcount_dec(&nf->nf_ref);
+ WARN_ON_ONCE(1);
+ return;
+ }
nfsd_file_schedule_laundrette();
}
@@ -419,25 +428,31 @@ nfsd_file_dispose_list(struct list_head *dispose)
}
/**
- * nfsd_file_dispose_list_delayed - move list of dead files to net's freeme list
+ * nfsd_file_dispose_list_delayed - queue dead files for nfsd thread disposal
* @dispose: list of nfsd_files to be disposed
*
- * Transfers each file to the "freeme" list for its nfsd_net, to eventually
- * be disposed of by the per-net garbage collector.
+ * Transfers each file to the dispose list in its nfsd_net and wakes an nfsd
+ * thread to do the actual close. This keeps the cost of fput() in the nfsd
+ * threads rather than in the shrinker or GC worker.
+ *
+ * All callers must hold nfsd_gc_lock, so that nfsd_file_cache_shutdown_net()
+ * can synchronize against them before draining the per-net dispose list.
+ * This guarantees nf_net is still live when we call net_generic().
*/
static void
nfsd_file_dispose_list_delayed(struct list_head *dispose)
{
- while(!list_empty(dispose)) {
+ lockdep_assert_held(&nfsd_gc_lock);
+
+ while (!list_empty(dispose)) {
struct nfsd_file *nf = list_first_entry(dispose,
struct nfsd_file, nf_gc);
struct nfsd_net *nn = net_generic(nf->nf_net, nfsd_net_id);
- struct nfsd_fcache_disposal *l = nn->fcache_disposal;
struct svc_serv *serv;
- spin_lock(&l->lock);
- list_move_tail(&nf->nf_gc, &l->freeme);
- spin_unlock(&l->lock);
+ spin_lock(&nn->fcache_dispose_lock);
+ list_move_tail(&nf->nf_gc, &nn->fcache_dispose_list);
+ spin_unlock(&nn->fcache_dispose_lock);
/*
* The filecache laundrette is shut down after the
@@ -461,21 +476,28 @@ nfsd_file_dispose_list_delayed(struct list_head *dispose)
*/
void nfsd_file_net_dispose(struct nfsd_net *nn)
{
- struct nfsd_fcache_disposal *l = nn->fcache_disposal;
-
- if (!list_empty(&l->freeme)) {
+ if (!list_empty(&nn->fcache_dispose_list)) {
LIST_HEAD(dispose);
int i;
- spin_lock(&l->lock);
- for (i = 0; i < 8 && !list_empty(&l->freeme); i++)
- list_move(l->freeme.next, &dispose);
- spin_unlock(&l->lock);
- if (!list_empty(&l->freeme))
- /* Wake up another thread to share the work
+ spin_lock(&nn->fcache_dispose_lock);
+ for (i = 0; i < 8 && !list_empty(&nn->fcache_dispose_list); i++)
+ list_move(nn->fcache_dispose_list.next, &dispose);
+ spin_unlock(&nn->fcache_dispose_lock);
+ if (!list_empty(&nn->fcache_dispose_list)) {
+ /*
+ * Wake up another thread to share the work
* *before* doing any actual disposing.
+ *
+ * The filecache laundrette is shut down after
+ * the nn->nfsd_serv pointer is cleared, but
+ * before the svc_serv is freed.
*/
- svc_wake_up(nn->nfsd_serv);
+ struct svc_serv *serv = nn->nfsd_serv;
+
+ if (serv)
+ svc_wake_up(serv);
+ }
nfsd_file_dispose_list(&dispose);
}
}
@@ -552,13 +574,6 @@ nfsd_file_gc_cb(struct list_head *item, struct list_lru_one *lru,
return nfsd_file_lru_cb(item, lru, arg);
}
-/* If the shrinker runs between calls to list_lru_walk_node() in
- * nfsd_file_gc(), the "remaining" count will be wrong. This could
- * result in premature freeing of some files. This may not matter much
- * but is easy to fix with this spinlock which temporarily disables
- * the shrinker.
- */
-static DEFINE_SPINLOCK(nfsd_gc_lock);
static void
nfsd_file_gc(void)
{
@@ -581,9 +596,9 @@ nfsd_file_gc(void)
remaining = 0;
}
}
+ nfsd_file_dispose_list_delayed(&dispose);
spin_unlock(&nfsd_gc_lock);
trace_nfsd_file_gc_removed(ret, list_lru_count(&nfsd_file_lru));
- nfsd_file_dispose_list_delayed(&dispose);
}
static void
@@ -611,9 +626,9 @@ nfsd_file_lru_scan(struct shrinker *s, struct shrink_control *sc)
ret = list_lru_shrink_walk(&nfsd_file_lru, sc,
nfsd_file_lru_cb, &dispose);
+ nfsd_file_dispose_list_delayed(&dispose);
spin_unlock(&nfsd_gc_lock);
trace_nfsd_file_shrinker_removed(ret, list_lru_count(&nfsd_file_lru));
- nfsd_file_dispose_list_delayed(&dispose);
return ret;
}
@@ -686,11 +701,11 @@ nfsd_file_queue_for_close(struct inode *inode, struct list_head *dispose)
}
/**
- * nfsd_file_close_inode - attempt a delayed close of a nfsd_file
+ * nfsd_file_close_inode - attempt a deferred close of a nfsd_file
* @inode: inode of the file to attempt to remove
*
* Close out any open nfsd_files that can be reaped for @inode. The
- * actual freeing is deferred to the dispose_list_delayed infrastructure.
+ * actual freeing is deferred to the nfsd service threads.
*
* This is used by the fsnotify callbacks and setlease notifier.
*/
@@ -699,8 +714,10 @@ nfsd_file_close_inode(struct inode *inode)
{
LIST_HEAD(dispose);
+ spin_lock(&nfsd_gc_lock);
nfsd_file_queue_for_close(inode, &dispose);
nfsd_file_dispose_list_delayed(&dispose);
+ spin_unlock(&nfsd_gc_lock);
}
/**
@@ -940,42 +957,14 @@ __nfsd_file_cache_purge(struct net *net)
nfsd_file_dispose_list(&dispose);
}
-static struct nfsd_fcache_disposal *
-nfsd_alloc_fcache_disposal(void)
-{
- struct nfsd_fcache_disposal *l;
-
- l = kmalloc_obj(*l);
- if (!l)
- return NULL;
- spin_lock_init(&l->lock);
- INIT_LIST_HEAD(&l->freeme);
- return l;
-}
-
-static void
-nfsd_free_fcache_disposal(struct nfsd_fcache_disposal *l)
-{
- nfsd_file_dispose_list(&l->freeme);
- kfree(l);
-}
-
-static void
-nfsd_free_fcache_disposal_net(struct net *net)
-{
- struct nfsd_net *nn = net_generic(net, nfsd_net_id);
- struct nfsd_fcache_disposal *l = nn->fcache_disposal;
-
- nfsd_free_fcache_disposal(l);
-}
-
int
nfsd_file_cache_start_net(struct net *net)
{
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
- nn->fcache_disposal = nfsd_alloc_fcache_disposal();
- return nn->fcache_disposal ? 0 : -ENOMEM;
+ spin_lock_init(&nn->fcache_dispose_lock);
+ INIT_LIST_HEAD(&nn->fcache_dispose_list);
+ return 0;
}
/**
@@ -994,8 +983,18 @@ nfsd_file_cache_purge(struct net *net)
void
nfsd_file_cache_shutdown_net(struct net *net)
{
+ struct nfsd_net *nn = net_generic(net, nfsd_net_id);
+
nfsd_file_cache_purge(net);
- nfsd_free_fcache_disposal_net(net);
+ /*
+ * Ensure any in-progress shrinker, GC, or fsnotify/lease callback
+ * (all of which hold nfsd_gc_lock while calling
+ * nfsd_file_dispose_list_delayed()) has fully completed before
+ * draining the per-net dispose list.
+ */
+ spin_lock(&nfsd_gc_lock);
+ spin_unlock(&nfsd_gc_lock);
+ nfsd_file_dispose_list(&nn->fcache_dispose_list);
}
void
@@ -1227,7 +1226,7 @@ open_file:
nf->nf_mark = nfsd_file_mark_find_or_create(inode);
if (type != S_IFREG || nf->nf_mark) {
- if (file) {
+ if (file && (file->f_mode & FMODE_OPENED)) {
get_file(file);
nf->nf_file = file;
status = nfs_ok;
@@ -1374,12 +1373,12 @@ nfsd_file_acquire_local(struct net *net, struct svc_cred *cred,
* @rqstp: the RPC transaction being executed
* @fhp: the NFS filehandle of the file just created
* @may_flags: NFSD_MAY_ settings for the file
- * @file: cached, already-open file (may be NULL)
+ * @file: cached, already-open file (may be NULL or not yet opened)
* @pnf: OUT: new or found "struct nfsd_file" object
*
* Acquire a nfsd_file object that is not GC'ed. If one doesn't already exist,
- * and @file is non-NULL, use it to instantiate a new nfsd_file instead of
- * opening a new one.
+ * and @file has FMODE_OPENED set, use it to instantiate a new nfsd_file
+ * instead of opening a new one.
*
* Return values:
* %nfs_ok - @pnf points to an nfsd_file with its reference
diff --git a/fs/nfsd/flexfilelayoutxdr.c b/fs/nfsd/flexfilelayoutxdr.c
index f9f7e38cba13..374e52d3064a 100644
--- a/fs/nfsd/flexfilelayoutxdr.c
+++ b/fs/nfsd/flexfilelayoutxdr.c
@@ -30,19 +30,24 @@ nfsd4_ff_encode_layoutget(struct xdr_stream *xdr,
struct ff_idmap uid;
struct ff_idmap gid;
- fh_len = 4 + fl->fh.size;
+ fh_len = 4 + xdr_align_size(fl->fh.size);
uid.len = sprintf(uid.buf, "%u", from_kuid(&init_user_ns, fl->uid));
gid.len = sprintf(gid.buf, "%u", from_kgid(&init_user_ns, fl->gid));
- /* 8 + len for recording the length, name, and padding */
- ds_len = 20 + sizeof(stateid_opaque_t) + 4 + fh_len +
- 8 + uid.len + 8 + gid.len;
+ /* data server entry: deviceid + efficiency + stateid + fh list +
+ * user + group + flags + stats_collect_hint
+ */
+ ds_len = 16 + 4 + 4 + sizeof(stateid_opaque_t) + 4 + fh_len +
+ 4 + xdr_align_size(uid.len) +
+ 4 + xdr_align_size(gid.len) +
+ 4 + 4;
+ /* mirror: ds_count + ds */
mirror_len = 4 + ds_len;
- /* The layout segment */
- len = 20 + mirror_len;
+ /* stripe_unit + mirror_count + mirror */
+ len = 12 + mirror_len;
p = xdr_reserve_space(xdr, sizeof(__be32) + len);
if (!p)
@@ -94,7 +99,8 @@ nfsd4_ff_encode_getdeviceinfo(struct xdr_stream *xdr,
}
/* len + padding for two strings */
- addr_len = 16 + da->netaddr.netid_len + da->netaddr.addr_len;
+ addr_len = 8 + xdr_align_size(da->netaddr.netid_len) +
+ xdr_align_size(da->netaddr.addr_len);
ver_len = 20;
len = 4 + ver_len + 4 + addr_len;
diff --git a/fs/nfsd/localio.c b/fs/nfsd/localio.c
index be710d809a3b..c3eb0557b3e1 100644
--- a/fs/nfsd/localio.c
+++ b/fs/nfsd/localio.c
@@ -97,11 +97,15 @@ nfsd_open_local_fh(struct net *net, struct auth_domain *dom,
}
nfsd_file_get(localio);
again:
+ rcu_read_lock();
new = unrcu_pointer(cmpxchg(pnf, NULL, RCU_INITIALIZER(localio)));
if (new) {
/* Some other thread installed an nfsd_file */
- if (nfsd_file_get(new) == NULL)
+ if (nfsd_file_get(new) == NULL) {
+ rcu_read_unlock();
goto again;
+ }
+ rcu_read_unlock();
/*
* Drop the ref we were going to install (both file and
* net) and the one we were going to return (only file).
@@ -110,6 +114,8 @@ nfsd_open_local_fh(struct net *net, struct auth_domain *dom,
nfsd_net_put(net);
nfsd_file_put(localio);
localio = new;
+ } else {
+ rcu_read_unlock();
}
} else
nfsd_net_put(net);
diff --git a/fs/nfsd/lockd.c b/fs/nfsd/lockd.c
index 6fe1325815e0..72a5b499839d 100644
--- a/fs/nfsd/lockd.c
+++ b/fs/nfsd/lockd.c
@@ -92,6 +92,7 @@ nlm_fclose(struct file *filp)
}
static const struct nlmsvc_binding nfsd_nlm_ops = {
+ .owner = THIS_MODULE,
.fopen = nlm_fopen, /* open file for locking */
.fclose = nlm_fclose, /* close file */
};
@@ -100,11 +101,12 @@ void
nfsd_lockd_init(void)
{
dprintk("nfsd: initializing lockd\n");
- nlmsvc_ops = &nfsd_nlm_ops;
+ rcu_assign_pointer(nlmsvc_ops, &nfsd_nlm_ops);
}
void
nfsd_lockd_shutdown(void)
{
- nlmsvc_ops = NULL;
+ RCU_INIT_POINTER(nlmsvc_ops, NULL);
+ synchronize_rcu();
}
diff --git a/fs/nfsd/netns.h b/fs/nfsd/netns.h
index 27da1a3edacb..a7bd7b67fa4f 100644
--- a/fs/nfsd/netns.h
+++ b/fs/nfsd/netns.h
@@ -28,6 +28,16 @@ struct cld_net;
struct nfsd_net_cb;
struct nfsd4_client_tracking_ops;
+enum nfsd_net_flag {
+ NFSD_NET_GRACE_ENDED,
+ NFSD_NET_GRACE_END_FORCED,
+ NFSD_NET_IN_GRACE,
+ NFSD_NET_SOMEBODY_RECLAIMED,
+ NFSD_NET_TRACK_RECLAIM_COMPLETES,
+ NFSD_NET_UP,
+ NFSD_NET_LOCKD_UP,
+};
+
enum {
/* cache misses due only to checksum comparison failures */
NFSD_STATS_PAYLOAD_MISSES,
@@ -66,9 +76,9 @@ struct nfsd_net {
struct cache_detail *nametoid_cache;
struct lock_manager nfsd4_manager;
- bool grace_ended;
- bool grace_end_forced;
+ unsigned long flags;
time64_t boot_time;
+ time64_t boot_time_bt; /* same instant in CLOCK_BOOTTIME */
struct dentry *nfsd_client_dir;
@@ -84,6 +94,7 @@ struct nfsd_net {
*/
struct list_head *reclaim_str_hashtbl;
int reclaim_str_hashtbl_size;
+ struct rw_semaphore reclaim_str_hashtbl_lock;
struct list_head *conf_id_hashtbl;
struct rb_root conf_name_tree;
struct list_head *unconf_id_hashtbl;
@@ -96,12 +107,16 @@ struct nfsd_net {
* close_lru holds (open) stateowner queue ordered by nfs4_stateowner.so_time
* for last close replay.
*
- * All of the above fields are protected by the client_mutex.
+ * reclaim_str_hashtbl[], reclaim_str_hashtbl_size are protected by
+ * reclaim_str_hashtbl_lock.
+ *
+ * All of the remaining fields are protected by the client_lock.
*/
struct list_head client_lru;
struct list_head close_lru;
- /* protects del_recall_lru and delegation hash/unhash */
+ /* protects del_recall_lru and delegation hash/unhash;
+ * nests outside client_lock */
spinlock_t deleg_lock ____cacheline_aligned;
struct list_head del_recall_lru;
@@ -110,26 +125,21 @@ struct nfsd_net {
struct delayed_work laundromat_work;
- /* client_lock protects the client lru list and session hash table */
+ /* client_lock protects the client lru list and session hash
+ * table; nests inside deleg_lock */
spinlock_t client_lock;
/* protects blocked_locks_lru */
spinlock_t blocked_locks_lock;
struct file *rec_file;
- bool in_grace;
const struct nfsd4_client_tracking_ops *client_tracking_ops;
time64_t nfsd4_lease;
time64_t nfsd4_grace;
- bool somebody_reclaimed;
- bool track_reclaim_completes;
atomic_t nr_reclaim_complete;
- bool nfsd_net_up;
- bool lockd_up;
-
seqlock_t writeverf_lock;
unsigned char writeverf[8];
@@ -209,7 +219,8 @@ struct nfsd_net {
/* utsname taken from the process that starts the server */
char nfsd_name[UNX_MAXNODENAME+1];
- struct nfsd_fcache_disposal *fcache_disposal;
+ spinlock_t fcache_dispose_lock;
+ struct list_head fcache_dispose_list;
siphash_key_t siphash_key;
diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c
index 76305b86c1a9..2998640f259d 100644
--- a/fs/nfsd/nfs2acl.c
+++ b/fs/nfsd/nfs2acl.c
@@ -115,14 +115,19 @@ static __be32 nfsacld_proc_setacl(struct svc_rqst *rqstp)
inode_lock(inode);
- error = set_posix_acl(&nop_mnt_idmap, fh->fh_dentry, ACL_TYPE_ACCESS,
- argp->acl_access);
- if (error)
- goto out_drop_lock;
- error = set_posix_acl(&nop_mnt_idmap, fh->fh_dentry, ACL_TYPE_DEFAULT,
- argp->acl_default);
- if (error)
- goto out_drop_lock;
+ error = 0;
+ if (argp->mask & NFS_ACL) {
+ error = set_posix_acl(&nop_mnt_idmap, fh->fh_dentry,
+ ACL_TYPE_ACCESS, argp->acl_access);
+ if (error)
+ goto out_drop_lock;
+ }
+ if (argp->mask & NFS_DFACL) {
+ error = set_posix_acl(&nop_mnt_idmap, fh->fh_dentry,
+ ACL_TYPE_DEFAULT, argp->acl_default);
+ if (error)
+ goto out_drop_lock;
+ }
inode_unlock(inode);
@@ -248,22 +253,21 @@ nfsaclsvc_encode_getaclres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
if (!svcxdr_encode_stat(xdr, resp->status))
return false;
-
- if (dentry == NULL || d_really_is_negative(dentry))
- return true;
- inode = d_inode(dentry);
-
- if (!svcxdr_encode_fattr(rqstp, xdr, &resp->fh, &resp->stat))
- return false;
- if (xdr_stream_encode_u32(xdr, resp->mask) < 0)
- return false;
-
- if (!nfs_stream_encode_acl(xdr, inode, resp->acl_access,
- resp->mask & NFS_ACL, 0))
- return false;
- if (!nfs_stream_encode_acl(xdr, inode, resp->acl_default,
- resp->mask & NFS_DFACL, NFS_ACL_DEFAULT))
- return false;
+ switch (resp->status) {
+ case nfs_ok:
+ inode = d_inode(dentry);
+ if (!svcxdr_encode_fattr(rqstp, xdr, &resp->fh, &resp->stat))
+ return false;
+ if (xdr_stream_encode_u32(xdr, resp->mask) < 0)
+ return false;
+ if (!nfs_stream_encode_acl(xdr, inode, resp->acl_access,
+ resp->mask & NFS_ACL, 0))
+ return false;
+ if (!nfs_stream_encode_acl(xdr, inode, resp->acl_default,
+ resp->mask & NFS_DFACL, NFS_ACL_DEFAULT))
+ return false;
+ break;
+ }
return true;
}
diff --git a/fs/nfsd/nfs3acl.c b/fs/nfsd/nfs3acl.c
index e87731380be8..a87f9d7f32be 100644
--- a/fs/nfsd/nfs3acl.c
+++ b/fs/nfsd/nfs3acl.c
@@ -105,12 +105,17 @@ static __be32 nfsd3_proc_setacl(struct svc_rqst *rqstp)
inode_lock(inode);
- error = set_posix_acl(&nop_mnt_idmap, fh->fh_dentry, ACL_TYPE_ACCESS,
- argp->acl_access);
- if (error)
- goto out_drop_lock;
- error = set_posix_acl(&nop_mnt_idmap, fh->fh_dentry, ACL_TYPE_DEFAULT,
- argp->acl_default);
+ error = 0;
+ if (argp->mask & NFS_ACL) {
+ error = set_posix_acl(&nop_mnt_idmap, fh->fh_dentry,
+ ACL_TYPE_ACCESS, argp->acl_access);
+ if (error)
+ goto out_drop_lock;
+ }
+ if (argp->mask & NFS_DFACL) {
+ error = set_posix_acl(&nop_mnt_idmap, fh->fh_dentry,
+ ACL_TYPE_DEFAULT, argp->acl_default);
+ }
out_drop_lock:
inode_unlock(inode);
diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index aeda7a802bdf..617a70d13292 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -29,6 +29,25 @@ static int nfs3_ftypes[] = {
S_IFIFO, /* NF3FIFO */
};
+/*
+ * Reject a client-supplied atime or mtime whose nanoseconds field is out
+ * of range. Such a value is well-formed on the wire but is not a valid
+ * timespec64, and storing it verbatim can corrupt on-disk timestamps.
+ * tv_nsec is a long, so it is cast to unsigned long (the same width) to
+ * catch both an over-large value and one that became negative when an
+ * out-of-range u32 wire nseconds was assigned to a 32-bit long.
+ */
+static bool nfsd3_time_in_range(const struct iattr *iap)
+{
+ if ((iap->ia_valid & ATTR_ATIME_SET) &&
+ (unsigned long)iap->ia_atime.tv_nsec >= NSEC_PER_SEC)
+ return false;
+ if ((iap->ia_valid & ATTR_MTIME_SET) &&
+ (unsigned long)iap->ia_mtime.tv_nsec >= NSEC_PER_SEC)
+ return false;
+ return true;
+}
+
static __be32 nfsd3_map_status(__be32 status)
{
switch (status) {
@@ -101,9 +120,14 @@ nfsd3_proc_setattr(struct svc_rqst *rqstp)
SVCFH_fmt(&argp->fh));
fh_copy(&resp->fh, &argp->fh);
+ if (!nfsd3_time_in_range(&argp->attrs)) {
+ resp->status = nfserr_inval;
+ goto out;
+ }
if (argp->check_guard)
guardtime = &argp->guardtime;
resp->status = nfsd_setattr(rqstp, &resp->fh, &attrs, guardtime);
+out:
resp->status = nfsd3_map_status(resp->status);
return rpc_success;
}
@@ -265,6 +289,8 @@ nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
trace_nfsd_vfs_create(rqstp, fhp, S_IFREG, argp->name, argp->len);
+ if (!nfsd3_time_in_range(iap))
+ return nfserr_inval;
if (isdotent(argp->name, argp->len))
return nfserr_exist;
if (!(iap->ia_valid & ATTR_MODE))
@@ -400,8 +426,13 @@ nfsd3_proc_mkdir(struct svc_rqst *rqstp)
argp->attrs.ia_valid &= ~ATTR_SIZE;
fh_copy(&resp->dirfh, &argp->fh);
fh_init(&resp->fh, NFS3_FHSIZE);
+ if (!nfsd3_time_in_range(&argp->attrs)) {
+ resp->status = nfserr_inval;
+ goto out;
+ }
resp->status = nfsd_create(rqstp, &resp->dirfh, argp->name, argp->len,
&attrs, S_IFDIR, 0, &resp->fh);
+out:
resp->status = nfsd3_map_status(resp->status);
return rpc_success;
}
@@ -415,6 +446,10 @@ nfsd3_proc_symlink(struct svc_rqst *rqstp)
.na_iattr = &argp->attrs,
};
+ if (!nfsd3_time_in_range(&argp->attrs)) {
+ resp->status = nfserr_inval;
+ goto out;
+ }
if (argp->tlen == 0) {
resp->status = nfserr_inval;
goto out;
@@ -471,6 +506,11 @@ nfsd3_proc_mknod(struct svc_rqst *rqstp)
goto out;
}
+ if (!nfsd3_time_in_range(&argp->attrs)) {
+ resp->status = nfserr_inval;
+ goto out;
+ }
+
type = nfs3_ftypes[argp->ftype];
resp->status = nfsd_create(rqstp, &resp->dirfh, argp->name, argp->len,
&attrs, type, rdev, &resp->fh);
diff --git a/fs/nfsd/nfs4callback.c b/fs/nfsd/nfs4callback.c
index 50827405468d..7c868afc329e 100644
--- a/fs/nfsd/nfs4callback.c
+++ b/fs/nfsd/nfs4callback.c
@@ -108,6 +108,8 @@ static int decode_cb_fattr4(struct xdr_stream *xdr, uint32_t *bitmap,
if (!xdrgen_decode_fattr4_time_deleg_access(xdr, &access))
return -EIO;
+ if (access.nseconds >= NSEC_PER_SEC)
+ return -EIO;
fattr->ncf_cb_atime.tv_sec = access.seconds;
fattr->ncf_cb_atime.tv_nsec = access.nseconds;
@@ -117,6 +119,8 @@ static int decode_cb_fattr4(struct xdr_stream *xdr, uint32_t *bitmap,
if (!xdrgen_decode_fattr4_time_deleg_modify(xdr, &modify))
return -EIO;
+ if (modify.nseconds >= NSEC_PER_SEC)
+ return -EIO;
fattr->ncf_cb_mtime.tv_sec = modify.seconds;
fattr->ncf_cb_mtime.tv_nsec = modify.nseconds;
@@ -456,13 +460,20 @@ static void encode_cb_sequence4args(struct xdr_stream *xdr,
const struct nfsd4_callback *cb,
struct nfs4_cb_compound_hdr *hdr)
{
- struct nfsd4_session *session = cb->cb_clp->cl_cb_session;
+ struct nfsd4_session *session;
struct nfsd4_referring_call_list *rcl;
__be32 *p;
if (hdr->minorversion == 0)
return;
+ rcu_read_lock();
+ session = rcu_dereference(cb->cb_clp->cl_cb_session);
+ if (!session) {
+ rcu_read_unlock();
+ return;
+ }
+
encode_nfs_cb_opnum4(xdr, OP_CB_SEQUENCE);
encode_sessionid4(xdr, session);
@@ -478,6 +489,7 @@ static void encode_cb_sequence4args(struct xdr_stream *xdr,
encode_referring_call_list4(xdr, rcl);
hdr->nops++;
+ rcu_read_unlock();
}
static void update_cb_slot_table(struct nfsd4_session *ses, u32 target)
@@ -529,21 +541,32 @@ static void update_cb_slot_table(struct nfsd4_session *ses, u32 target)
static int decode_cb_sequence4resok(struct xdr_stream *xdr,
struct nfsd4_callback *cb)
{
- struct nfsd4_session *session = cb->cb_clp->cl_cb_session;
+ struct nfsd4_session *session;
int status = -ESERVERFAULT;
__be32 *p;
u32 seqid, slotid, target;
+ rcu_read_lock();
+ session = rcu_dereference(cb->cb_clp->cl_cb_session);
+ if (!session) {
+ rcu_read_unlock();
+ cb->cb_seq_status = -NFS4ERR_BADSESSION;
+ return -NFS4ERR_BADSESSION;
+ }
+
/*
* If the server returns different values for sessionID, slotID or
* sequence number, the server is looney tunes.
*/
p = xdr_inline_decode(xdr, NFS4_MAX_SESSIONID_LEN + 4 + 4 + 4 + 4);
- if (unlikely(p == NULL))
+ if (unlikely(p == NULL)) {
+ rcu_read_unlock();
goto out_overflow;
+ }
if (memcmp(p, session->se_sessionid.data, NFS4_MAX_SESSIONID_LEN)) {
dprintk("NFS: %s Invalid session id\n", __func__);
+ rcu_read_unlock();
goto out;
}
p += XDR_QUADLEN(NFS4_MAX_SESSIONID_LEN);
@@ -551,12 +574,14 @@ static int decode_cb_sequence4resok(struct xdr_stream *xdr,
seqid = be32_to_cpup(p++);
if (seqid != session->se_cb_seq_nr[cb->cb_held_slot]) {
dprintk("NFS: %s Invalid sequence number\n", __func__);
+ rcu_read_unlock();
goto out;
}
slotid = be32_to_cpup(p++);
if (slotid != cb->cb_held_slot) {
dprintk("NFS: %s Invalid slotid\n", __func__);
+ rcu_read_unlock();
goto out;
}
@@ -564,6 +589,7 @@ static int decode_cb_sequence4resok(struct xdr_stream *xdr,
target = be32_to_cpup(p++);
update_cb_slot_table(session, target);
+ rcu_read_unlock();
status = 0;
out:
cb->cb_seq_status = status;
@@ -1150,9 +1176,8 @@ static int setup_callback_client(struct nfs4_client *clp, struct nfs4_cb_conn *c
} else {
if (!conn->cb_xprt || !ses)
return -EINVAL;
- clp->cl_cb_session = ses;
args.bc_xprt = conn->cb_xprt;
- args.prognumber = clp->cl_cb_session->se_cb_prog;
+ args.prognumber = ses->se_cb_prog;
args.protocol = conn->cb_xprt->xpt_class->xcl_ident |
XPRT_TRANSPORT_BC;
args.authflavor = ses->se_cb_sec.flavor;
@@ -1170,8 +1195,10 @@ static int setup_callback_client(struct nfs4_client *clp, struct nfs4_cb_conn *c
return -ENOMEM;
}
- if (clp->cl_minorversion != 0)
+ if (clp->cl_minorversion != 0) {
clp->cl_cb_conn.cb_xprt = conn->cb_xprt;
+ rcu_assign_pointer(clp->cl_cb_session, ses);
+ }
clp->cl_cb_client = client;
clp->cl_cb_cred = cred;
rcu_read_lock();
@@ -1278,18 +1305,33 @@ static int grab_slot(struct nfsd4_session *ses)
static bool nfsd41_cb_get_slot(struct nfsd4_callback *cb, struct rpc_task *task)
{
struct nfs4_client *clp = cb->cb_clp;
- struct nfsd4_session *ses = clp->cl_cb_session;
+ struct nfsd4_session *ses;
if (cb->cb_held_slot >= 0)
return true;
+
+ rcu_read_lock();
+ ses = rcu_dereference(clp->cl_cb_session);
+ if (!ses) {
+ rcu_read_unlock();
+ rpc_sleep_on(&clp->cl_cb_waitq, task, NULL);
+ return false;
+ }
cb->cb_held_slot = grab_slot(ses);
if (cb->cb_held_slot < 0) {
+ rcu_read_unlock();
rpc_sleep_on(&clp->cl_cb_waitq, task, NULL);
/* Race breaker */
- cb->cb_held_slot = grab_slot(ses);
+ rcu_read_lock();
+ ses = rcu_dereference(clp->cl_cb_session);
+ if (ses)
+ cb->cb_held_slot = grab_slot(ses);
+ rcu_read_unlock();
if (cb->cb_held_slot < 0)
return false;
rpc_wake_up_queued_task(&clp->cl_cb_waitq, task);
+ } else {
+ rcu_read_unlock();
}
return true;
}
@@ -1297,12 +1339,17 @@ static bool nfsd41_cb_get_slot(struct nfsd4_callback *cb, struct rpc_task *task)
static void nfsd41_cb_release_slot(struct nfsd4_callback *cb)
{
struct nfs4_client *clp = cb->cb_clp;
- struct nfsd4_session *ses = clp->cl_cb_session;
+ struct nfsd4_session *ses;
if (cb->cb_held_slot >= 0) {
- spin_lock(&ses->se_lock);
- ses->se_cb_slot_avail |= BIT(cb->cb_held_slot);
- spin_unlock(&ses->se_lock);
+ rcu_read_lock();
+ ses = rcu_dereference(clp->cl_cb_session);
+ if (ses) {
+ spin_lock(&ses->se_lock);
+ ses->se_cb_slot_avail |= BIT(cb->cb_held_slot);
+ spin_unlock(&ses->se_lock);
+ }
+ rcu_read_unlock();
cb->cb_held_slot = -1;
rpc_wake_up_next(&clp->cl_cb_waitq);
}
@@ -1434,22 +1481,35 @@ static void nfsd4_cb_prepare(struct rpc_task *task, void *calldata)
trace_nfsd_cb_rpc_prepare(clp);
cb->cb_seq_status = 1;
cb->cb_status = 0;
- if (minorversion && !nfsd41_cb_get_slot(cb, task))
- return;
+ if (minorversion) {
+ if (!rcu_access_pointer(clp->cl_cb_session)) {
+ rpc_exit(task, -EIO);
+ return;
+ }
+ if (!nfsd41_cb_get_slot(cb, task))
+ return;
+ }
rpc_call_start(task);
}
/* Returns true if CB_COMPOUND processing should continue */
static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback *cb)
{
- struct nfsd4_session *session = cb->cb_clp->cl_cb_session;
+ struct nfsd4_session *session;
bool ret = false;
if (cb->cb_held_slot < 0)
goto requeue;
+ rcu_read_lock();
+ session = rcu_dereference(cb->cb_clp->cl_cb_session);
+ if (!session) {
+ rcu_read_unlock();
+ goto requeue;
+ }
+
/* This is the operation status code for CB_SEQUENCE */
- trace_nfsd_cb_seq_status(task, cb);
+ trace_nfsd_cb_seq_status(task, cb, session);
switch (cb->cb_seq_status) {
case 0:
/*
@@ -1481,12 +1541,16 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback
fallthrough;
case -NFS4ERR_BADSESSION:
nfsd4_mark_cb_fault(cb->cb_clp);
+ rcu_read_unlock();
goto requeue;
case -NFS4ERR_DELAY:
cb->cb_seq_status = 1;
- if (RPC_SIGNALLED(task) || !rpc_restart_call(task))
+ if (RPC_SIGNALLED(task) || !rpc_restart_call(task)) {
+ rcu_read_unlock();
goto requeue;
+ }
rpc_delay(task, 2 * HZ);
+ rcu_read_unlock();
return false;
case -NFS4ERR_SEQ_MISORDERED:
case -NFS4ERR_BADSLOT:
@@ -1498,11 +1562,13 @@ static bool nfsd4_cb_sequence_done(struct rpc_task *task, struct nfsd4_callback
*/
nfsd4_mark_cb_fault(cb->cb_clp);
cb->cb_held_slot = -1;
+ rcu_read_unlock();
goto retry_nowait;
default:
nfsd4_mark_cb_fault(cb->cb_clp);
}
- trace_nfsd_cb_free_slot(task, cb);
+ trace_nfsd_cb_free_slot(task, cb, session);
+ rcu_read_unlock();
nfsd41_cb_release_slot(cb);
return ret;
retry_nowait:
@@ -1624,7 +1690,15 @@ static struct nfsd4_conn * __nfsd4_find_backchannel(struct nfs4_client *clp)
* Note there isn't a lot of locking in this code; instead we depend on
* the fact that it is run from clp->cl_callback_wq, which won't run two
* work items at once. So, for example, clp->cl_callback_wq handles all
- * access of cl_cb_client and all calls to rpc_create or rpc_shutdown_client.
+ * access of cl_cb_client, and all calls to rpc_create or
+ * rpc_shutdown_client.
+ *
+ * cl_cb_session is written only from cl_callback_wq (via
+ * rcu_assign_pointer) and read from rpciod under rcu_read_lock (via
+ * rcu_dereference) by encode_cb_sequence4args(), decode_cb_sequence4resok(),
+ * nfsd4_cb_sequence_done(), and the cb-slot helpers. Sessions are freed
+ * with kfree_rcu() so that rpciod readers in an RCU read-side critical
+ * section never dereference a freed session.
*/
static void nfsd4_process_cb_update(struct nfsd4_callback *cb)
{
@@ -1676,6 +1750,7 @@ static void nfsd4_process_cb_update(struct nfsd4_callback *cb)
nfsd4_mark_cb_down(clp);
if (c)
svc_xprt_put(c->cn_xprt);
+ rcu_assign_pointer(clp->cl_cb_session, ses);
return;
}
}
diff --git a/fs/nfsd/nfs4layouts.c b/fs/nfsd/nfs4layouts.c
index f34320e4c2f4..279ff1e9dffb 100644
--- a/fs/nfsd/nfs4layouts.c
+++ b/fs/nfsd/nfs4layouts.c
@@ -247,13 +247,20 @@ nfsd4_alloc_layout_stateid(struct nfsd4_compound_state *cstate,
nfsd4_init_cb(&ls->ls_recall, clp, &nfsd4_cb_layout_ops,
NFSPROC4_CLNT_CB_LAYOUT);
- if (parent->sc_type == SC_TYPE_DELEG)
+ if (parent->sc_type == SC_TYPE_DELEG) {
+ spin_lock(&fp->fi_lock);
ls->ls_file = nfsd_file_get(fp->fi_deleg_file);
- else
+ spin_unlock(&fp->fi_lock);
+ } else {
ls->ls_file = find_any_file(fp);
- BUG_ON(!ls->ls_file);
+ }
+ if (!ls->ls_file) {
+ nfs4_put_stid(stp);
+ return NULL;
+ }
ls->ls_fenced = false;
+ ls->ls_fence_inflight = false;
ls->ls_fence_delay = 0;
INIT_DELAYED_WORK(&ls->ls_fence_work, nfsd4_layout_fence_worker);
@@ -791,15 +798,6 @@ nfsd4_layout_fence_worker(struct work_struct *work)
struct nfs4_client *clp;
struct nfsd_net *nn;
- /*
- * The workqueue clears WORK_STRUCT_PENDING before invoking
- * this callback. Re-arm immediately so that
- * delayed_work_pending() returns true while the fence
- * operation is in progress, preventing
- * lm_breaker_timedout() from taking a duplicate reference.
- */
- mod_delayed_work(system_dfl_wq, &ls->ls_fence_work, 0);
-
spin_lock(&ls->ls_lock);
if (list_empty(&ls->ls_layouts)) {
spin_unlock(&ls->ls_lock);
@@ -809,6 +807,9 @@ dispose:
nfsd4_close_layout(ls);
ls->ls_fenced = true;
+ spin_lock(&ls->ls_lock);
+ ls->ls_fence_inflight = false;
+ spin_unlock(&ls->ls_lock);
nfs4_put_stid(&ls->ls_stid);
return;
}
@@ -894,18 +895,26 @@ nfsd4_layout_lm_breaker_timedout(struct file_lease *fl)
if ((!nfsd4_layout_ops[ls->ls_layout_type]->fence_client) ||
ls->ls_fenced)
return true;
- if (delayed_work_pending(&ls->ls_fence_work))
- return false;
/*
* Make sure layout has not been returned yet before
- * taking a reference count on the layout stateid.
+ * taking a reference count on the layout stateid. The
+ * ls_fence_inflight flag is set together with the sc_count
+ * increment under ls_lock so that a fence worker invocation
+ * already in progress (which has cleared WORK_STRUCT_PENDING
+ * but not yet reached dispose:) cannot be coalesced with a
+ * fresh schedule that takes an extra unmatched reference.
*/
spin_lock(&ls->ls_lock);
+ if (ls->ls_fence_inflight) {
+ spin_unlock(&ls->ls_lock);
+ return false;
+ }
if (list_empty(&ls->ls_layouts) ||
!refcount_inc_not_zero(&ls->ls_stid.sc_count)) {
spin_unlock(&ls->ls_lock);
return true;
}
+ ls->ls_fence_inflight = true;
spin_unlock(&ls->ls_lock);
mod_delayed_work(system_dfl_wq, &ls->ls_fence_work, 0);
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 8561540ab2db..e3c3249e63be 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -306,10 +306,6 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
goto out;
}
- status = fh_compose(resfhp, fhp->fh_export, child, fhp);
- if (status != nfs_ok)
- goto out;
-
v_mtime = 0;
v_atime = 0;
if (nfsd4_create_is_exclusive(open->op_createmode)) {
@@ -335,6 +331,10 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
if (status != nfs_ok)
goto out;
+ status = fh_compose(resfhp, fhp->fh_export, child, fhp);
+ if (status != nfs_ok)
+ goto out;
+
switch (open->op_createmode) {
case NFS4_CREATE_UNCHECKED:
if (!d_is_reg(child))
@@ -385,6 +385,10 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
open->op_created = true;
fh_fill_post_attrs(fhp);
+ status = fh_compose(resfhp, fhp->fh_export, child, fhp);
+ if (status != nfs_ok)
+ goto out;
+
/* A newly created file already has a file size of zero. */
if ((iap->ia_valid & ATTR_SIZE) && (iap->ia_size == 0))
iap->ia_valid &= ~ATTR_SIZE;
@@ -663,7 +667,7 @@ nfsd4_open(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
pr_warn("nfsd4_process_open2 failed to open newly-created file: status=%u\n",
be32_to_cpu(status));
if (reclaim && !status)
- nn->somebody_reclaimed = true;
+ set_bit(NFSD_NET_SOMEBODY_RECLAIMED, &nn->flags);
out:
if (open->op_filp) {
fput(open->op_filp);
@@ -677,8 +681,6 @@ out:
nfsd4_cleanup_open_state(cstate, open);
nfsd4_bump_seqid(cstate, status);
out_err:
- posix_acl_release(open->op_dpacl);
- posix_acl_release(open->op_pacl);
return status;
}
@@ -700,6 +702,13 @@ static __be32 nfsd4_open_omfg(struct svc_rqst *rqstp, struct nfsd4_compound_stat
return nfsd4_open(rqstp, cstate, &op->u);
}
+static void
+nfsd4_open_release(union nfsd4_op_u *u)
+{
+ posix_acl_release(u->open.op_dpacl);
+ posix_acl_release(u->open.op_pacl);
+}
+
/*
* filehandle-manipulating ops.
*/
@@ -839,6 +848,20 @@ nfsd4_create(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
if (status)
goto out_aftermask;
+ /* Sanitize cr_type to avoid returning ATTRNOTSUPP. */
+ switch (create->cr_type) {
+ case NF4LNK:
+ case NF4BLK:
+ case NF4CHR:
+ case NF4SOCK:
+ case NF4FIFO:
+ case NF4DIR:
+ break;
+ default:
+ status = nfserr_badtype;
+ goto out_aftermask;
+ }
+
if (create->cr_acl) {
if (attrs.na_dpacl || attrs.na_pacl) {
status = nfserr_inval;
@@ -846,6 +869,8 @@ nfsd4_create(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
}
status = nfsd4_acl_to_attr(create->cr_type, create->cr_acl,
&attrs);
+ if (status != nfs_ok)
+ goto out_aftermask;
}
current->fs->umask = create->cr_umask;
switch (create->cr_type) {
@@ -1253,7 +1278,7 @@ nfsd4_setattr(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
if (deleg_attrs) {
status = nfserr_bad_stateid;
- if (st->sc_type & SC_TYPE_DELEG) {
+ if (st && (st->sc_type & SC_TYPE_DELEG)) {
struct nfs4_delegation *dp = delegstateid(st);
/* Only for *_ATTRS_DELEG flavors */
@@ -1494,6 +1519,9 @@ static void nfs4_put_copy(struct nfsd4_copy *copy)
{
if (!refcount_dec_and_test(&copy->refcount))
return;
+ /* Drop the task_struct pinned in nfsd4_copy(); NULL on sync copies. */
+ if (copy->copy_task)
+ put_task_struct(copy->copy_task);
kfree(copy->cp_src);
kfree(copy);
}
@@ -1503,20 +1531,18 @@ static void release_copy_files(struct nfsd4_copy *copy);
static void nfsd4_stop_copy(struct nfsd4_copy *copy)
{
trace_nfsd_copy_async_cancel(copy);
- if (!test_and_set_bit(NFSD4_COPY_F_STOPPED, &copy->cp_flags)) {
- kthread_stop(copy->copy_task);
- if (!test_bit(NFSD4_COPY_F_CB_ERROR, &copy->cp_flags))
- copy->nfserr = nfs_ok;
- set_bit(NFSD4_COPY_F_COMPLETED, &copy->cp_flags);
- }
-
/*
- * The copy was removed from async_copies before this function
- * was called, so the reaper cannot clean it up. Release files
- * here regardless of who won the STOPPED race. If the thread
- * set STOPPED, it has finished using the files. If STOPPED
- * was set here, kthread_stop() waited for the thread to exit.
+ * Join the kthread before releasing its resources. The task_struct is
+ * pinned in nfsd4_copy(), so kthread_stop() is safe even after the
+ * one-shot kthread has exited. The caller already unlinked the copy,
+ * so this runs once per copy.
*/
+ set_bit(NFSD4_COPY_F_STOPPED, &copy->cp_flags);
+ kthread_stop(copy->copy_task);
+ if (!test_bit(NFSD4_COPY_F_CB_ERROR, &copy->cp_flags))
+ copy->nfserr = nfs_ok;
+ set_bit(NFSD4_COPY_F_COMPLETED, &copy->cp_flags);
+
release_copy_files(copy);
nfs4_put_copy(copy);
}
@@ -1530,7 +1556,13 @@ static struct nfsd4_copy *nfsd4_unhash_copy(struct nfs4_client *clp)
copy = list_first_entry(&clp->async_copies, struct nfsd4_copy,
copies);
refcount_inc(&copy->refcount);
- copy->cp_clp = NULL;
+ /*
+ * Unlinking hides the copy from the reaper, so drop its
+ * s2s_cp_stateids entry here while cp_clp is still valid.
+ */
+ nfs4_free_copy_state(copy);
+ /* Pairs with smp_load_acquire() in nfsd4_send_cb_offload(). */
+ smp_store_release(&copy->cp_clp, NULL);
if (!list_empty(&copy->copies))
list_del_init(&copy->copies);
}
@@ -1542,8 +1574,11 @@ void nfsd4_shutdown_copy(struct nfs4_client *clp)
{
struct nfsd4_copy *copy;
- while ((copy = nfsd4_unhash_copy(clp)) != NULL)
+ while ((copy = nfsd4_unhash_copy(clp)) != NULL) {
nfsd4_stop_copy(copy);
+ /* Reaper can't reach the unhashed copy; drop its membership ref. */
+ nfs4_put_copy(copy);
+ }
}
static bool nfsd4_copy_on_sb(const struct nfsd4_copy *copy,
@@ -1606,7 +1641,11 @@ void nfsd4_cancel_copy_by_sb(struct net *net, struct super_block *sb)
struct nfs4_client *clp = copy->cp_clp;
list_del_init(&copy->copies);
+ /* Reaper can't reach it; drop the s2s entry while cp_clp is valid. */
+ nfs4_free_copy_state(copy);
nfsd4_stop_copy(copy);
+ /* Drop the membership ref the reaper would have dropped. */
+ nfs4_put_copy(copy);
nfsd4_put_client(clp);
}
}
@@ -1901,6 +1940,8 @@ static void nfsd4_cb_offload_release(struct nfsd4_callback *cb)
set_bit(NFSD4_COPY_F_OFFLOAD_DONE, &copy->cp_flags);
nfsd4_put_client(cb->cb_clp);
+ /* Drop the copy reference taken in nfsd4_send_cb_offload(). */
+ nfs4_put_copy(copy);
}
static int nfsd4_cb_offload_done(struct nfsd4_callback *cb,
@@ -1950,6 +1991,7 @@ static ssize_t _nfsd_copy_file_range(struct nfsd4_copy *copy,
/* See RFC 7862 p.67: */
if (bytes_total == 0)
bytes_total = ULLONG_MAX;
+ since = READ_ONCE(dst->f_wb_err);
do {
/* Only async copies can be stopped here */
if (kthread_should_stop())
@@ -1965,13 +2007,14 @@ static ssize_t _nfsd_copy_file_range(struct nfsd4_copy *copy,
} while (bytes_total > 0 && nfsd4_copy_is_async(copy));
/* for a non-zero asynchronous copy do a commit of data */
if (nfsd4_copy_is_async(copy) && copy->cp_res.wr_bytes_written > 0) {
- since = READ_ONCE(dst->f_wb_err);
end = copy->cp_dst_pos + copy->cp_res.wr_bytes_written - 1;
status = vfs_fsync_range(dst, copy->cp_dst_pos, end, 0);
if (!status)
status = filemap_check_wb_err(dst->f_mapping, since);
if (!status)
set_bit(NFSD4_COPY_F_COMMITTED, &copy->cp_flags);
+ else if (status != -EAGAIN && status != -ESTALE)
+ nfsd_reset_write_verifier(copy->cp_nn);
}
return bytes_copied;
}
@@ -2030,28 +2073,29 @@ static void release_copy_files(struct nfsd4_copy *copy)
}
}
+/*
+ * Called from the reaper and from nfsd4_copy()'s error path; in both
+ * cases the copy is already unreachable from clp->async_copies.
+ */
static void cleanup_async_copy(struct nfsd4_copy *copy)
{
nfs4_free_copy_state(copy);
release_copy_files(copy);
- if (copy->cp_clp) {
- spin_lock(&copy->cp_clp->async_lock);
- if (!list_empty(&copy->copies))
- list_del_init(&copy->copies);
- spin_unlock(&copy->cp_clp->async_lock);
- }
nfs4_put_copy(copy);
}
static void nfsd4_send_cb_offload(struct nfsd4_copy *copy)
{
struct nfsd4_cb_offload *cbo = &copy->cp_cb_offload;
- struct nfs4_client *clp = copy->cp_clp;
+ struct nfs4_client *clp;
/*
- * cp_clp is NULL when called via nfsd4_shutdown_copy() during
- * client destruction. Skip the callback; the client is gone.
+ * Pairs with smp_store_release(&cp_clp) in find_async_copy() and
+ * nfsd4_unhash_copy(); the set_bit/clear_bit writers are unordered.
+ * cp_clp is NULL once the copy was canceled; skip the callback, the
+ * canceling path owns the notification.
*/
+ clp = smp_load_acquire(&copy->cp_clp);
if (!clp) {
set_bit(NFSD4_COPY_F_OFFLOAD_DONE, &copy->cp_flags);
return;
@@ -2063,10 +2107,12 @@ static void nfsd4_send_cb_offload(struct nfsd4_copy *copy)
cbo->co_retries = 5;
/*
- * Hold a reference on the client while the callback is in flight.
- * Released in nfsd4_cb_offload_release().
+ * Hold the client and the copy across the in-flight callback; co_cb is
+ * embedded in the copy, so it must outlive the callback. Both are
+ * dropped in nfsd4_cb_offload_release().
*/
kref_get(&clp->cl_nfsdfs.cl_ref);
+ refcount_inc(&copy->refcount);
nfsd4_init_cb(&cbo->co_cb, clp, &nfsd4_cb_offload_ops,
NFSPROC4_CLNT_CB_OFFLOAD);
@@ -2118,16 +2164,20 @@ static int nfsd4_do_async_copy(void *data)
do_callback:
if (!test_bit(NFSD4_COPY_F_CB_ERROR, &copy->cp_flags))
copy->nfserr = nfserr;
- /* The kthread exits forthwith. Ensure that a subsequent
- * OFFLOAD_CANCEL won't try to kill it again. */
- set_bit(NFSD4_COPY_F_STOPPED, &copy->cp_flags);
-
+ /*
+ * Don't set NFSD4_COPY_F_STOPPED here: it tells a teardown caller it
+ * may skip kthread_stop(), which would then release nf_dst and the
+ * client while still in use. Only nfsd4_stop_copy() sets it, after
+ * joining.
+ */
set_bit(NFSD4_COPY_F_COMPLETED, &copy->cp_flags);
trace_nfsd_copy_async_done(copy);
atomic_dec(&copy->cp_nn->pending_async_copies);
if (copy->cp_res.wr_bytes_written > 0 && copy->attr_update)
nfsd_update_cmtime_attr(copy->nf_dst->nf_file, 0);
nfsd4_send_cb_offload(copy);
+ /* Drop the kthread's reference (taken in nfsd4_copy()); copy may be freed after this. */
+ nfs4_put_copy(copy);
return 0;
}
@@ -2153,21 +2203,21 @@ nfsd4_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
}
status = nfsd4_setup_inter_ssc(rqstp, cstate, copy);
if (status) {
- trace_nfsd_copy_done(copy, status);
- return nfserr_offload_denied;
+ status = nfserr_offload_denied;
+ goto out;
}
} else {
trace_nfsd_copy_intra(copy);
status = nfsd4_setup_intra_ssc(rqstp, cstate, copy);
- if (status) {
- trace_nfsd_copy_done(copy, status);
- return status;
- }
+ if (status)
+ goto out;
}
memcpy(&copy->fh, &cstate->current_fh.fh_handle,
sizeof(struct knfsd_fh));
if (nfsd4_copy_is_async(copy)) {
+ struct task_struct *task;
+
async_copy = kzalloc_obj(struct nfsd4_copy);
if (!async_copy)
goto out_err;
@@ -2182,11 +2232,12 @@ nfsd4_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
async_copy->cp_src = kmalloc_obj(*async_copy->cp_src);
if (!async_copy->cp_src)
goto out_dec_async_copy_err;
- if (!nfs4_init_copy_state(nn, copy))
+ dup_copy_fields(copy, async_copy);
+
+ if (!nfs4_init_copy_state(nn, async_copy))
goto out_dec_async_copy_err;
- memcpy(&result->cb_stateid, &copy->cp_stateid.cs_stid,
+ memcpy(&result->cb_stateid, &async_copy->cp_stateid.cs_stid,
sizeof(result->cb_stateid));
- dup_copy_fields(copy, async_copy);
if ((READ_ONCE(copy->nf_dst->nf_file->f_mode) &
FMODE_NOCMTIME) != 0)
async_copy->attr_update = true;
@@ -2195,15 +2246,27 @@ nfsd4_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
NFS4_MAX_SESSIONID_LEN);
async_copy->cp_cb_offload.co_referring_slotid = cstate->slot->sl_index;
async_copy->cp_cb_offload.co_referring_seqno = cstate->slot->sl_seqid;
- async_copy->copy_task = kthread_create(nfsd4_do_async_copy,
- async_copy, "%s", "copy thread");
- if (IS_ERR(async_copy->copy_task))
+ task = kthread_create(nfsd4_do_async_copy, async_copy,
+ "%s", "copy thread");
+ if (IS_ERR(task))
goto out_dec_async_copy_err;
+ /*
+ * Pin the task_struct so kthread_stop() is safe after this
+ * one-shot kthread exits. Released by nfs4_put_copy().
+ */
+ get_task_struct(task);
+ async_copy->copy_task = task;
+ /*
+ * Take the kthread's ref and wake it before publishing, so the
+ * publisher touches async_copy no further and teardown can
+ * drain it.
+ */
+ refcount_inc(&async_copy->refcount);
+ wake_up_process(async_copy->copy_task);
spin_lock(&async_copy->cp_clp->async_lock);
list_add(&async_copy->copies,
&async_copy->cp_clp->async_copies);
spin_unlock(&async_copy->cp_clp->async_lock);
- wake_up_process(async_copy->copy_task);
status = nfs_ok;
} else {
status = nfsd4_do_copy(copy, copy->nf_src->nf_file,
@@ -2257,8 +2320,18 @@ find_async_copy(struct nfs4_client *clp, stateid_t *stateid)
spin_lock(&clp->async_lock);
copy = find_async_copy_locked(clp, stateid);
- if (copy)
+ if (copy) {
refcount_inc(&copy->refcount);
+ nfs4_free_copy_state(copy);
+ /*
+ * Mirror nfsd4_unhash_copy(): unlink and clear cp_clp under
+ * async_lock so the reaper can't reach it. Caller drops the
+ * membership ref after nfsd4_stop_copy().
+ */
+ smp_store_release(&copy->cp_clp, NULL);
+ if (!list_empty(&copy->copies))
+ list_del_init(&copy->copies);
+ }
spin_unlock(&clp->async_lock);
return copy;
}
@@ -2277,8 +2350,11 @@ nfsd4_offload_cancel(struct svc_rqst *rqstp,
struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
return manage_cpntf_state(nn, &os->stateid, clp, NULL);
- } else
+ } else {
nfsd4_stop_copy(copy);
+ /* find_async_copy() unlinked it from the reaper; drop the membership ref. */
+ nfs4_put_copy(copy);
+ }
return nfs_ok;
}
@@ -2292,7 +2368,6 @@ nfsd4_copy_notify(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
struct nfs4_stid *stid = NULL;
struct nfs4_cpntf_state *cps;
- struct nfs4_client *clp = cstate->clp;
status = nfs4_preprocess_stateid_op(rqstp, cstate, &cstate->current_fh,
&cn->cpn_src_stateid, RD_STATE, NULL,
@@ -2306,12 +2381,14 @@ nfsd4_copy_notify(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
cn->cpn_lease_time.tv_nsec = 0;
status = nfserrno(-ENOMEM);
+ /*
+ * The returned cps is published and fully initialized, and carries an
+ * extra reference for us; drop it once we are done with it.
+ */
cps = nfs4_alloc_init_cpntf_state(nn, stid);
if (!cps)
goto out;
memcpy(&cn->cpn_cnr_stateid, &cps->cp_stateid.cs_stid, sizeof(stateid_t));
- memcpy(&cps->cp_p_stateid, &stid->sc_stateid, sizeof(stateid_t));
- memcpy(&cps->cp_p_clid, &clp->cl_clientid, sizeof(clientid_t));
/* For now, only return one server address in cpn_src, the
* address used by the client to connect to this server.
@@ -2320,10 +2397,11 @@ nfsd4_copy_notify(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
status = nfsd4_set_netaddr((struct sockaddr *)&rqstp->rq_daddr,
&cn->cpn_src->u.nl4_addr);
WARN_ON_ONCE(status);
- if (status) {
- nfs4_put_cpntf_state(nn, cps);
- goto out;
- }
+ /*
+ * Drop our extra reference. The membership reference keeps the entry
+ * alive for a later inter-server READ, or until the laundromat reaps it.
+ */
+ nfs4_put_cpntf_state(nn, cps);
out:
nfs4_put_stid(stid);
return status;
@@ -3119,9 +3197,22 @@ nfsd4_proc_compound(struct svc_rqst *rqstp)
op->status = nfsd4_open_omfg(rqstp, cstate, op);
goto encode_op;
}
- if (!current_fh->fh_dentry &&
- !HAS_FH_FLAG(current_fh, NFSD4_FH_FOREIGN)) {
- if (!(op->opdesc->op_flags & ALLOWED_WITHOUT_FH)) {
+ if (!current_fh->fh_dentry) {
+ if (HAS_FH_FLAG(current_fh, NFSD4_FH_FOREIGN)) {
+ /*
+ * FOREIGN fh from inter-SSC PUTFH: only
+ * SAVEFH may proceed with a NULL fh_dentry.
+ * Per RFC 7862 S15.2.3, validation of a
+ * foreign fh is deferred to the operation
+ * that consumes it, and NFS4ERR_STALE is
+ * returned at that point.
+ */
+ if (op->opnum != OP_SAVEFH &&
+ !(op->opdesc->op_flags & ALLOWED_WITHOUT_FH)) {
+ op->status = nfserr_stale;
+ goto encode_op;
+ }
+ } else if (!(op->opdesc->op_flags & ALLOWED_WITHOUT_FH)) {
op->status = nfserr_nofilehandle;
goto encode_op;
}
@@ -3185,6 +3276,9 @@ encode_op:
status = op->status;
}
+ if (op->opdesc && op->opdesc->op_release)
+ op->opdesc->op_release(&op->u);
+
trace_nfsd_compound_status(args->client_opcnt, resp->opcnt,
status, nfsd4_op_name(op->opnum));
@@ -3507,7 +3601,7 @@ static u32 nfsd4_get_dir_delegation_rsize(const struct svc_rqst *rqstp,
op_encode_stateid_maxsz +
2 /* gddr_notification */ +
2 /* gddr_child_attributes */ +
- 2 /* gddr_dir_attributes */);
+ 2 /* gddr_dir_attributes */) * sizeof(__be32);
}
#ifdef CONFIG_NFSD_PNFS
@@ -3684,6 +3778,7 @@ static const struct nfsd4_operation nfsd4_ops[] = {
},
[OP_OPEN] = {
.op_func = nfsd4_open,
+ .op_release = nfsd4_open_release,
.op_flags = OP_HANDLES_WRONGSEC | OP_MODIFIES_SOMETHING,
.op_name = "OP_OPEN",
.op_rsize_bop = nfsd4_open_rsize,
diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c
index 6ea25a52d2f4..d513971fb119 100644
--- a/fs/nfsd/nfs4recover.c
+++ b/fs/nfsd/nfs4recover.c
@@ -167,7 +167,7 @@ out_end:
end_creating(dentry);
out:
if (status == 0) {
- if (nn->in_grace)
+ if (test_bit(NFSD_NET_IN_GRACE, &nn->flags))
__nfsd4_create_reclaim_record_grace(clp, dname, nn);
vfs_fsync(nn->rec_file, 0);
} else {
@@ -285,10 +285,12 @@ __nfsd4_remove_reclaim_record_grace(const char *dname, int len,
return;
}
name.len = len;
+ down_write(&nn->reclaim_str_hashtbl_lock);
crp = nfsd4_find_reclaim_client(name, nn);
- kfree(name.data);
if (crp)
nfs4_remove_reclaim_record(crp, nn);
+ up_write(&nn->reclaim_str_hashtbl_lock);
+ kfree(name.data);
}
static void
@@ -317,7 +319,7 @@ nfsd4_remove_clid_dir(struct nfs4_client *clp)
nfs4_reset_creds(original_cred);
if (status == 0) {
vfs_fsync(nn->rec_file, 0);
- if (nn->in_grace)
+ if (test_bit(NFSD_NET_IN_GRACE, &nn->flags))
__nfsd4_remove_reclaim_record_grace(dname,
HEXDIR_LEN, nn);
}
@@ -373,7 +375,7 @@ nfsd4_recdir_purge_old(struct nfsd_net *nn)
{
int status;
- nn->in_grace = false;
+ clear_bit(NFSD_NET_IN_GRACE, &nn->flags);
if (!nn->rec_file)
return;
status = mnt_want_write_file(nn->rec_file);
@@ -455,7 +457,7 @@ nfsd4_init_recdir(struct net *net)
nfs4_reset_creds(original_cred);
if (!status)
- nn->in_grace = true;
+ set_bit(NFSD_NET_IN_GRACE, &nn->flags);
return status;
}
@@ -484,6 +486,7 @@ nfs4_legacy_state_init(struct net *net)
for (i = 0; i < CLIENT_HASH_SIZE; i++)
INIT_LIST_HEAD(&nn->reclaim_str_hashtbl[i]);
nn->reclaim_str_hashtbl_size = 0;
+ init_rwsem(&nn->reclaim_str_hashtbl_lock);
return 0;
}
@@ -598,13 +601,16 @@ nfsd4_check_legacy_client(struct nfs4_client *clp)
goto out_enoent;
}
name.len = HEXDIR_LEN;
+ down_read(&nn->reclaim_str_hashtbl_lock);
crp = nfsd4_find_reclaim_client(name, nn);
- kfree(name.data);
if (crp) {
set_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags);
crp->cr_clp = clp;
- return 0;
}
+ up_read(&nn->reclaim_str_hashtbl_lock);
+ kfree(name.data);
+ if (crp)
+ return 0;
out_enoent:
return -ENOENT;
@@ -1176,6 +1182,7 @@ nfsd4_cld_check(struct nfs4_client *clp)
return 0;
/* look for it in the reclaim hashtable otherwise */
+ down_read(&nn->reclaim_str_hashtbl_lock);
crp = nfsd4_find_reclaim_client(clp->cl_name, nn);
if (crp)
goto found;
@@ -1191,6 +1198,7 @@ nfsd4_cld_check(struct nfs4_client *clp)
if (!name.data) {
dprintk("%s: failed to allocate memory for name.data!\n",
__func__);
+ up_read(&nn->reclaim_str_hashtbl_lock);
return -ENOENT;
}
name.len = HEXDIR_LEN;
@@ -1201,9 +1209,11 @@ nfsd4_cld_check(struct nfs4_client *clp)
}
#endif
+ up_read(&nn->reclaim_str_hashtbl_lock);
return -ENOENT;
found:
crp->cr_clp = clp;
+ up_read(&nn->reclaim_str_hashtbl_lock);
return 0;
}
@@ -1215,6 +1225,7 @@ nfsd4_cld_check_v2(struct nfs4_client *clp)
struct cld_net *cn = nn->cld_net;
#endif
struct nfs4_client_reclaim *crp;
+ unsigned int princhashlen;
char *principal = NULL;
/* did we already find that this client is stable? */
@@ -1222,6 +1233,7 @@ nfsd4_cld_check_v2(struct nfs4_client *clp)
return 0;
/* look for it in the reclaim hashtable otherwise */
+ down_read(&nn->reclaim_str_hashtbl_lock);
crp = nfsd4_find_reclaim_client(clp->cl_name, nn);
if (crp)
goto found;
@@ -1237,6 +1249,7 @@ nfsd4_cld_check_v2(struct nfs4_client *clp)
if (!name.data) {
dprintk("%s: failed to allocate memory for name.data\n",
__func__);
+ up_read(&nn->reclaim_str_hashtbl_lock);
return -ENOENT;
}
name.len = HEXDIR_LEN;
@@ -1247,23 +1260,31 @@ nfsd4_cld_check_v2(struct nfs4_client *clp)
}
#endif
+ up_read(&nn->reclaim_str_hashtbl_lock);
return -ENOENT;
found:
- if (crp->cr_princhash.len) {
+ princhashlen = crp->cr_princhash.len;
+ if (princhashlen) {
u8 digest[SHA256_DIGEST_SIZE];
+ u8 *pdata;
if (clp->cl_cred.cr_raw_principal)
principal = clp->cl_cred.cr_raw_principal;
else if (clp->cl_cred.cr_principal)
principal = clp->cl_cred.cr_principal;
- if (principal == NULL)
+ if (principal == NULL) {
+ up_read(&nn->reclaim_str_hashtbl_lock);
return -ENOENT;
+ }
sha256(principal, strlen(principal), digest);
- if (memcmp(crp->cr_princhash.data, digest,
- crp->cr_princhash.len))
+ pdata = crp->cr_princhash.data;
+ if (memcmp(pdata, digest, princhashlen)) {
+ up_read(&nn->reclaim_str_hashtbl_lock);
return -ENOENT;
+ }
}
crp->cr_clp = clp;
+ up_read(&nn->reclaim_str_hashtbl_lock);
return 0;
}
@@ -1362,7 +1383,8 @@ nfs4_cld_state_init(struct net *net)
for (i = 0; i < CLIENT_HASH_SIZE; i++)
INIT_LIST_HEAD(&nn->reclaim_str_hashtbl[i]);
nn->reclaim_str_hashtbl_size = 0;
- nn->track_reclaim_completes = true;
+ init_rwsem(&nn->reclaim_str_hashtbl_lock);
+ set_bit(NFSD_NET_TRACK_RECLAIM_COMPLETES, &nn->flags);
atomic_set(&nn->nr_reclaim_complete, 0);
return 0;
@@ -1373,7 +1395,7 @@ nfs4_cld_state_shutdown(struct net *net)
{
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
- nn->track_reclaim_completes = false;
+ clear_bit(NFSD_NET_TRACK_RECLAIM_COMPLETES, &nn->flags);
kfree(nn->reclaim_str_hashtbl);
}
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index a42f34842d77..386ca748522b 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -939,7 +939,7 @@ out_free:
* Create a unique stateid_t to represent each COPY.
*/
static int nfs4_init_cp_state(struct nfsd_net *nn, copy_stateid_t *stid,
- unsigned char cs_type)
+ unsigned char cs_type, struct nfs4_stid *p_stid)
{
int new_id;
@@ -949,19 +949,34 @@ static int nfs4_init_cp_state(struct nfsd_net *nn, copy_stateid_t *stid,
idr_preload(GFP_KERNEL);
spin_lock(&nn->s2s_cp_lock);
new_id = idr_alloc_cyclic(&nn->s2s_cp_stateids, stid, 0, 0, GFP_NOWAIT);
- stid->cs_stid.si_opaque.so_id = new_id;
- stid->cs_stid.si_generation = 1;
+ if (new_id >= 0) {
+ stid->cs_stid.si_opaque.so_id = new_id;
+ stid->cs_stid.si_generation = 1;
+ /*
+ * Set cs_type and link onto sc_cp_list under the same lock
+ * that installed the IDR entry, so a concurrent
+ * manage_cpntf_state() sees either no entry or a fully
+ * linked cp_list.
+ */
+ stid->cs_type = cs_type;
+ if (p_stid) {
+ struct nfs4_cpntf_state *cps =
+ container_of(stid, struct nfs4_cpntf_state,
+ cp_stateid);
+
+ list_add(&cps->cp_list, &p_stid->sc_cp_list);
+ }
+ }
spin_unlock(&nn->s2s_cp_lock);
idr_preload_end();
if (new_id < 0)
return 0;
- stid->cs_type = cs_type;
return 1;
}
int nfs4_init_copy_state(struct nfsd_net *nn, struct nfsd4_copy *copy)
{
- return nfs4_init_cp_state(nn, &copy->cp_stateid, NFS4_COPY_STID);
+ return nfs4_init_cp_state(nn, &copy->cp_stateid, NFS4_COPY_STID, NULL);
}
struct nfs4_cpntf_state *nfs4_alloc_init_cpntf_state(struct nfsd_net *nn,
@@ -972,13 +987,21 @@ struct nfs4_cpntf_state *nfs4_alloc_init_cpntf_state(struct nfsd_net *nn,
cps = kzalloc_obj(struct nfs4_cpntf_state);
if (!cps)
return NULL;
+ /* So a stale list_del_init() before linking is a no-op. */
+ INIT_LIST_HEAD(&cps->cp_list);
cps->cpntf_time = ktime_get_boottime_seconds();
- refcount_set(&cps->cp_stateid.cs_count, 1);
- if (!nfs4_init_cp_state(nn, &cps->cp_stateid, NFS4_COPYNOTIFY_STID))
+ /*
+ * Fully initialize the entry before nfs4_init_cp_state() publishes it,
+ * since a concurrent OFFLOAD_CANCEL could then free it. Take an extra
+ * reference for the caller (dropped with nfs4_put_cpntf_state()).
+ */
+ memcpy(&cps->cp_p_stateid, &p_stid->sc_stateid, sizeof(stateid_t));
+ memcpy(&cps->cp_p_clid, &p_stid->sc_client->cl_clientid,
+ sizeof(clientid_t));
+ refcount_set(&cps->cp_stateid.cs_count, 2);
+ if (!nfs4_init_cp_state(nn, &cps->cp_stateid, NFS4_COPYNOTIFY_STID,
+ p_stid))
goto out_free;
- spin_lock(&nn->s2s_cp_lock);
- list_add(&cps->cp_list, &p_stid->sc_cp_list);
- spin_unlock(&nn->s2s_cp_lock);
return cps;
out_free:
kfree(cps);
@@ -998,18 +1021,66 @@ void nfs4_free_copy_state(struct nfsd4_copy *copy)
spin_unlock(&nn->s2s_cp_lock);
}
+/*
+ * Drop the parent's reference on an already-unlinked cpntf entry. If a
+ * concurrent holder still owns a reference, its nfs4_put_cpntf_state() does
+ * the final free.
+ *
+ * nn->s2s_cp_lock must be held.
+ */
+static void put_cpntf_state_unlinked_locked(struct nfs4_cpntf_state *cps)
+{
+ WARN_ON_ONCE(cps->cp_stateid.cs_type != NFS4_COPYNOTIFY_STID);
+ WARN_ON_ONCE(!list_empty(&cps->cp_list));
+
+ if (refcount_dec_and_test(&cps->cp_stateid.cs_count))
+ kfree(cps);
+}
+
+/*
+ * Unhash from the IDR and sc_cp_list. Gated on list_empty() to avoid
+ * evicting a recycled so_id.
+ */
+static void nfsd4_unhash_cpntf_state(struct nfsd_net *nn, struct nfs4_cpntf_state *cps)
+{
+ lockdep_assert_held(&nn->s2s_cp_lock);
+
+ if (!list_empty(&cps->cp_list)) {
+ list_del_init(&cps->cp_list);
+ idr_remove(&nn->s2s_cp_stateids, cps->cp_stateid.cs_stid.si_opaque.so_id);
+ }
+}
+
+/*
+ * Revoke a copy-notify stateid: unlink it from the IDR and sc_cp_list first
+ * so no new finder can discover it, then drop the membership reference. Every
+ * revoke path (cancel, laundromat, drain) must use this rather than
+ * _free_cpntf_state_locked(), which unlinks only at refcount zero and so could
+ * let a second revoke free the entry under a concurrent reader.
+ *
+ * nn->s2s_cp_lock must be held.
+ */
+static void revoke_cpntf_state_locked(struct nfsd_net *nn,
+ struct nfs4_cpntf_state *cps)
+{
+ nfsd4_unhash_cpntf_state(nn, cps);
+ put_cpntf_state_unlinked_locked(cps);
+}
+
static void nfs4_free_cpntf_statelist(struct net *net, struct nfs4_stid *stid)
{
- struct nfs4_cpntf_state *cps;
+ struct nfs4_cpntf_state *cps, *tmp;
struct nfsd_net *nn;
nn = net_generic(net, nfsd_net_id);
spin_lock(&nn->s2s_cp_lock);
- while (!list_empty(&stid->sc_cp_list)) {
- cps = list_first_entry(&stid->sc_cp_list,
- struct nfs4_cpntf_state, cp_list);
- _free_cpntf_state_locked(nn, cps);
- }
+ /*
+ * Revoke unlinks each entry before dropping the parent's reference, so
+ * the drain terminates in one pass per entry regardless of cs_count; a
+ * concurrent holder does the final kfree via nfs4_put_cpntf_state().
+ */
+ list_for_each_entry_safe(cps, tmp, &stid->sc_cp_list, cp_list)
+ revoke_cpntf_state_locked(nn, cps);
spin_unlock(&nn->s2s_cp_lock);
}
@@ -1889,9 +1960,19 @@ void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb)
struct nfs4_client *clp;
retry:
list_for_each_entry(clp, head, cl_idhash) {
- struct nfs4_stid *stid = find_one_sb_stid(clp, sb,
- sc_types);
+ struct nfs4_stid *stid;
+
+ /*
+ * force_expire_client() ignores cl_rpc_users once
+ * its wait_event() has passed, so pinning cannot
+ * keep an already-expiring client alive; the
+ * expiry path revokes its states instead.
+ */
+ if (is_client_expired(clp))
+ continue;
+ stid = find_one_sb_stid(clp, sb, sc_types);
if (stid) {
+ atomic_inc(&clp->cl_rpc_users);
spin_unlock(&nn->client_lock);
revoke_one_stid(nn, clp, stid);
nfs4_put_stid(stid);
@@ -1904,6 +1985,9 @@ void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb)
*/
nn->nfs40_last_revoke =
ktime_get_boottime_seconds();
+ if (atomic_dec_and_test(&clp->cl_rpc_users) &&
+ is_client_expired(clp))
+ wake_up_all(&expiry_wq);
goto retry;
}
}
@@ -2295,7 +2379,7 @@ static void __free_session(struct nfsd4_session *ses)
{
free_session_slots(ses, 0);
xa_destroy(&ses->se_slots);
- kfree(ses);
+ kfree_rcu(ses, rcu_head);
}
static void free_session(struct nfsd4_session *ses)
@@ -2641,14 +2725,24 @@ __destroy_client(struct nfs4_client *clp)
release_openowner(oo);
}
for (i = 0; i < OWNER_HASH_SIZE; i++) {
- struct nfs4_stateowner *so, *tmp;
+ struct nfs4_stateowner *so;
- list_for_each_entry_safe(so, tmp, &clp->cl_ownerstr_hashtbl[i],
- so_strhash) {
+ spin_lock(&clp->cl_lock);
+ while (!list_empty(&clp->cl_ownerstr_hashtbl[i])) {
+ so = list_first_entry(&clp->cl_ownerstr_hashtbl[i],
+ struct nfs4_stateowner, so_strhash);
/* Should be no openowners at this point */
WARN_ON_ONCE(so->so_is_open_owner);
+ nfs4_get_stateowner(so);
+ unhash_lockowner_locked(lockowner(so));
+ spin_unlock(&clp->cl_lock);
+
remove_blocked_locks(lockowner(so));
+ nfs4_put_stateowner(so);
+
+ spin_lock(&clp->cl_lock);
}
+ spin_unlock(&clp->cl_lock);
}
nfsd4_return_all_client_layouts(clp);
nfsd4_shutdown_copy(clp);
@@ -2675,16 +2769,23 @@ static void inc_reclaim_complete(struct nfs4_client *clp)
{
struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
- if (!nn->track_reclaim_completes)
+ if (!test_bit(NFSD_NET_TRACK_RECLAIM_COMPLETES, &nn->flags))
return;
- if (!nfsd4_find_reclaim_client(clp->cl_name, nn))
+
+ down_read(&nn->reclaim_str_hashtbl_lock);
+ if (!nfsd4_find_reclaim_client(clp->cl_name, nn)) {
+ up_read(&nn->reclaim_str_hashtbl_lock);
return;
+ }
if (atomic_inc_return(&nn->nr_reclaim_complete) ==
nn->reclaim_str_hashtbl_size) {
+ up_read(&nn->reclaim_str_hashtbl_lock);
printk(KERN_INFO "NFSD: all clients done reclaiming, ending NFSv4 grace period (net %x)\n",
clp->net->ns.inum);
nfsd4_end_grace(nn);
+ return;
}
+ up_read(&nn->reclaim_str_hashtbl_lock);
}
static void expire_client(struct nfs4_client *clp)
@@ -3259,6 +3360,11 @@ static void force_expire_client(struct nfs4_client *clp)
trace_nfsd_clid_admin_expired(&clp->cl_clientid);
+ /*
+ * cl_time is cleared under client_lock before the wait so a
+ * revocation walk pinning cl_rpc_users under it either skips
+ * this client or is seen by this wait_event().
+ */
spin_lock(&nn->client_lock);
clp->cl_time = 0;
spin_unlock(&nn->client_lock);
@@ -3414,7 +3520,7 @@ static struct nfs4_client *create_client(struct xdr_netobj name,
clp->cl_time = ktime_get_boottime_seconds();
copy_verf(clp, verf);
memcpy(&clp->cl_addr, sa, sizeof(struct sockaddr_storage));
- clp->cl_cb_session = NULL;
+ RCU_INIT_POINTER(clp->cl_cb_session, NULL);
clp->net = net;
clp->cl_nfsd_dentry = nfsd_client_mkdir(
nn, &clp->cl_nfsdfs,
@@ -4496,6 +4602,19 @@ static void nfsd4_construct_sequence_response(struct nfsd4_session *session,
seq->status_flags |= SEQ4_STATUS_ADMIN_STATE_REVOKED;
}
+static bool nfsd4_slots_inuse(struct nfsd4_session *ses, int from)
+{
+ int i;
+
+ for (i = from; i < ses->se_fchannel.maxreqs; i++) {
+ struct nfsd4_slot *slot = xa_load(&ses->se_slots, i);
+
+ if (slot->sl_flags & NFSD4_SLOT_INUSE)
+ return true;
+ }
+ return false;
+}
+
__be32
nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
union nfsd4_op_u *u)
@@ -4575,7 +4694,9 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
if (session->se_target_maxslots < session->se_fchannel.maxreqs &&
slot->sl_generation == session->se_slot_gen &&
- seq->maxslots <= session->se_target_maxslots)
+ seq->maxslots <= session->se_target_maxslots &&
+ seq->slotid < session->se_target_maxslots &&
+ !nfsd4_slots_inuse(session, session->se_target_maxslots))
/* Client acknowledged our reduce maxreqs */
free_session_slots(session, session->se_target_maxslots);
@@ -5018,8 +5139,6 @@ nfsd4_init_leases_net(struct nfsd_net *nn)
nn->nfsd4_lease = 90; /* default lease time */
nn->nfsd4_grace = 90;
- nn->somebody_reclaimed = false;
- nn->track_reclaim_completes = false;
nn->clverifier_counter = get_random_u32();
nn->clientid_base = get_random_u32();
nn->clientid_counter = nn->clientid_base + 1;
@@ -5172,6 +5291,7 @@ static void nfsd4_drop_revoked_stid(struct nfs4_stid *s)
case SC_TYPE_DELEG:
dp = delegstateid(s);
list_del_init(&dp->dl_recall_lru);
+ s->sc_status |= SC_STATUS_FREED;
spin_unlock(&cl->cl_lock);
nfs4_put_stid(s);
break;
@@ -5615,8 +5735,10 @@ static void nfsd_break_one_deleg(struct nfs4_delegation *dp)
refcount_inc(&dp->dl_stid.sc_count);
queued = nfsd4_run_cb(&dp->dl_recall);
WARN_ON_ONCE(!queued);
- if (!queued)
+ if (!queued) {
refcount_dec(&dp->dl_stid.sc_count);
+ clear_bit(NFSD4_CALLBACK_RUNNING, &dp->dl_recall.cb_flags);
+ }
}
/* Called from break_lease() with flc_lock held. */
@@ -6728,12 +6850,21 @@ nfsd4_renew(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
static void
nfsd4_end_grace(struct nfsd_net *nn)
{
- /* do nothing if grace period already ended */
- if (nn->grace_ended)
+ /*
+ * nfsd4_end_grace() can be entered concurrently from the
+ * laundromat workqueue and from an nfsd compound thread
+ * handling RECLAIM_COMPLETE. Without serialization, both
+ * callers can observe NFSD_NET_GRACE_ENDED clear and proceed
+ * into nfsd4_record_grace_done(). For tracking ops whose
+ * grace_done drains reclaim_str_hashtbl, that results in
+ * list corruption and a double free of every
+ * nfs4_client_reclaim entry. Use an atomic test-and-set so
+ * exactly one caller proceeds.
+ */
+ if (test_and_set_bit(NFSD_NET_GRACE_ENDED, &nn->flags))
return;
trace_nfsd_grace_complete(nn);
- nn->grace_ended = true;
/*
* If the server goes down again right now, an NFSv4
* client will still be allowed to reclaim after it comes back up,
@@ -6774,10 +6905,10 @@ bool nfsd4_force_end_grace(struct nfsd_net *nn)
{
if (!nn->client_tracking_ops)
return false;
- if (READ_ONCE(nn->grace_ended))
+ if (test_bit(NFSD_NET_GRACE_ENDED, &nn->flags))
return false;
/* laundromat_work must be initialised now, though it might be disabled */
- WRITE_ONCE(nn->grace_end_forced, true);
+ set_bit(NFSD_NET_GRACE_END_FORCED, &nn->flags);
/* mod_delayed_work() doesn't queue work after
* nfs4_state_shutdown_net() has called disable_delayed_work_sync()
*/
@@ -6791,18 +6922,22 @@ bool nfsd4_force_end_grace(struct nfsd_net *nn)
*/
static bool clients_still_reclaiming(struct nfsd_net *nn)
{
- time64_t double_grace_period_end = nn->boot_time +
+ time64_t double_grace_period_end = nn->boot_time_bt +
2 * nn->nfsd4_lease;
- if (READ_ONCE(nn->grace_end_forced))
+ if (test_bit(NFSD_NET_GRACE_END_FORCED, &nn->flags))
return false;
- if (nn->track_reclaim_completes &&
- atomic_read(&nn->nr_reclaim_complete) ==
- nn->reclaim_str_hashtbl_size)
- return false;
- if (!nn->somebody_reclaimed)
+ if (test_bit(NFSD_NET_TRACK_RECLAIM_COMPLETES, &nn->flags)) {
+ int size;
+
+ down_read(&nn->reclaim_str_hashtbl_lock);
+ size = nn->reclaim_str_hashtbl_size;
+ up_read(&nn->reclaim_str_hashtbl_lock);
+ if (atomic_read(&nn->nr_reclaim_complete) == size)
+ return false;
+ }
+ if (!test_and_clear_bit(NFSD_NET_SOMEBODY_RECLAIMED, &nn->flags))
return false;
- nn->somebody_reclaimed = false;
/*
* If we've given them *two* lease times to reclaim, and they're
* still not done, give up:
@@ -6859,30 +6994,36 @@ static void nfsd4_ssc_shutdown_umount(struct nfsd_net *nn)
static void nfsd4_ssc_expire_umount(struct nfsd_net *nn)
{
bool do_wakeup = false;
- struct nfsd4_ssc_umount_item *ni = NULL;
- struct nfsd4_ssc_umount_item *tmp;
+ struct nfsd4_ssc_umount_item *ni;
+restart:
spin_lock(&nn->nfsd_ssc_lock);
- list_for_each_entry_safe(ni, tmp, &nn->nfsd_ssc_mount_list, nsui_list) {
- if (time_after(jiffies, ni->nsui_expire)) {
- if (refcount_read(&ni->nsui_refcnt) > 1)
- continue;
+ list_for_each_entry(ni, &nn->nfsd_ssc_mount_list, nsui_list) {
+ if (!time_after(jiffies, ni->nsui_expire))
+ break;
+ if (refcount_read(&ni->nsui_refcnt) > 1)
+ continue;
- /* mark being unmount */
- ni->nsui_busy = true;
- spin_unlock(&nn->nfsd_ssc_lock);
- mntput(ni->nsui_vfsmount);
- spin_lock(&nn->nfsd_ssc_lock);
+ /* Prevent concurrent setup during unmount */
+ ni->nsui_busy = true;
+ spin_unlock(&nn->nfsd_ssc_lock);
+ mntput(ni->nsui_vfsmount);
+ spin_lock(&nn->nfsd_ssc_lock);
- /* waiters need to start from begin of list */
- list_del(&ni->nsui_list);
- kfree(ni);
+ /* Force concurrent scanners to restart */
+ list_del(&ni->nsui_list);
+ kfree(ni);
- /* wakeup ssc_connect waiters */
- do_wakeup = true;
- continue;
- }
- break;
+ /* wakeup ssc_connect waiters */
+ do_wakeup = true;
+ /*
+ * Concurrent nfsd4_ssc_cancel_dul() can free any item
+ * on the list under nfsd_ssc_lock while mntput() runs
+ * above. Restart from the head; the list is short and
+ * the expire worker is periodic, so this is cheap.
+ */
+ spin_unlock(&nn->nfsd_ssc_lock);
+ goto restart;
}
if (do_wakeup)
wake_up_all(&nn->nfsd_ssc_waitq);
@@ -7032,16 +7173,22 @@ retry:
if (atomic_read(&clp->cl_admin_revoked) == 0)
continue;
+ if (is_client_expired(clp))
+ continue;
spin_lock(&clp->cl_lock);
idr_for_each_entry_ul(&clp->cl_stateids, stid, tmp, id)
if (stid->sc_status & SC_STATUS_ADMIN_REVOKED) {
refcount_inc(&stid->sc_count);
+ atomic_inc(&clp->cl_rpc_users);
spin_unlock(&nn->client_lock);
/* this function drops ->cl_lock */
nfsd4_drop_revoked_stid(stid);
nfs4_put_stid(stid);
spin_lock(&nn->client_lock);
+ if (atomic_dec_and_test(&clp->cl_rpc_users) &&
+ is_client_expired(clp))
+ wake_up_all(&expiry_wq);
goto retry;
}
spin_unlock(&clp->cl_lock);
@@ -7062,6 +7209,7 @@ nfs4_laundromat(struct nfsd_net *nn)
.new_timeo = nn->nfsd4_lease
};
struct nfs4_cpntf_state *cps;
+ struct nfs4_client *clp;
copy_stateid_t *cps_t;
int i;
@@ -7076,7 +7224,7 @@ nfs4_laundromat(struct nfsd_net *nn)
cps = container_of(cps_t, struct nfs4_cpntf_state, cp_stateid);
if (cps->cp_stateid.cs_type == NFS4_COPYNOTIFY_STID &&
state_expired(&lt, cps->cpntf_time))
- _free_cpntf_state_locked(nn, cps);
+ revoke_cpntf_state_locked(nn, cps);
}
spin_unlock(&nn->s2s_cp_lock);
nfsd4_async_copy_reaper(nn);
@@ -7090,6 +7238,18 @@ nfs4_laundromat(struct nfsd_net *nn)
dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru);
if (!state_expired(&lt, dp->dl_time))
break;
+ clp = dp->dl_stid.sc_client;
+ spin_lock(&nn->client_lock);
+ if (is_client_expired(clp)) {
+ spin_unlock(&nn->client_lock);
+ continue;
+ }
+ /*
+ * Pin without reviving: get_client_locked() would
+ * flip a courtesy client back to NFSD4_ACTIVE.
+ */
+ atomic_inc(&clp->cl_rpc_users);
+ spin_unlock(&nn->client_lock);
refcount_inc(&dp->dl_stid.sc_count);
unhash_delegation_locked(dp, SC_STATUS_REVOKED);
list_add(&dp->dl_recall_lru, &reaplist);
@@ -7098,8 +7258,18 @@ nfs4_laundromat(struct nfsd_net *nn)
while (!list_empty(&reaplist)) {
dp = list_first_entry(&reaplist, struct nfs4_delegation,
dl_recall_lru);
+ clp = dp->dl_stid.sc_client;
list_del_init(&dp->dl_recall_lru);
revoke_delegation(dp);
+ /*
+ * Unpin without renewing: put_client_renew() would
+ * renew the reaped client's lease.
+ */
+ if (atomic_dec_and_lock(&clp->cl_rpc_users, &nn->client_lock)) {
+ if (is_client_expired(clp))
+ wake_up_all(&expiry_wq);
+ spin_unlock(&nn->client_lock);
+ }
}
spin_lock(&nn->client_lock);
@@ -7195,12 +7365,12 @@ deleg_reaper(struct nfsd_net *nn)
continue;
if (atomic_read(&clp->cl_delegs_in_recall))
continue;
- if (test_and_set_bit(NFSD4_CALLBACK_RUNNING, &clp->cl_ra->ra_cb.cb_flags))
- continue;
if (ktime_get_boottime_seconds() - clp->cl_ra_time < 5)
continue;
if (clp->cl_cb_state != NFSD4_CB_UP)
continue;
+ if (test_and_set_bit(NFSD4_CALLBACK_RUNNING, &clp->cl_ra->ra_cb.cb_flags))
+ continue;
/* release in nfsd4_cb_recall_any_release */
kref_get(&clp->cl_nfsdfs.cl_ref);
@@ -7463,16 +7633,14 @@ nfs4_check_file(struct svc_rqst *rqstp, struct svc_fh *fhp, struct nfs4_stid *s,
out:
return status;
}
-static void
-_free_cpntf_state_locked(struct nfsd_net *nn, struct nfs4_cpntf_state *cps)
+
+static void _free_cpntf_state_locked(struct nfsd_net *nn, struct nfs4_cpntf_state *cps)
{
WARN_ON_ONCE(cps->cp_stateid.cs_type != NFS4_COPYNOTIFY_STID);
- if (!refcount_dec_and_test(&cps->cp_stateid.cs_count))
- return;
- list_del(&cps->cp_list);
- idr_remove(&nn->s2s_cp_stateids,
- cps->cp_stateid.cs_stid.si_opaque.so_id);
- kfree(cps);
+ if (refcount_dec_and_test(&cps->cp_stateid.cs_count)) {
+ nfsd4_unhash_cpntf_state(nn, cps);
+ kfree(cps);
+ }
}
/*
* A READ from an inter server to server COPY will have a
@@ -7497,10 +7665,20 @@ __be32 manage_cpntf_state(struct nfsd_net *nn, stateid_t *st,
state = NULL;
goto unlock;
}
- if (!clp)
+ if (!clp) {
refcount_inc(&state->cp_stateid.cs_count);
- else
- _free_cpntf_state_locked(nn, state);
+ } else if (memcmp(&clp->cl_clientid, &state->cp_p_clid,
+ sizeof(clientid_t))) {
+ /*
+ * OFFLOAD_CANCEL: only the creating client may cancel.
+ * so_id is guessable, so without this check any client
+ * could free another's cpntf state.
+ */
+ state = NULL;
+ goto unlock;
+ } else {
+ revoke_cpntf_state_locked(nn, state);
+ }
}
unlock:
spin_unlock(&nn->s2s_cp_lock);
@@ -7787,7 +7965,7 @@ retry:
return status;
stp = openlockstateid(s);
if (nfsd4_cstate_assign_replay(cstate, stp->st_stateowner) == -EAGAIN) {
- nfs4_put_stateowner(stp->st_stateowner);
+ nfs4_put_stid(&stp->st_stid);
goto retry;
}
@@ -8036,6 +8214,10 @@ nfsd4_delegreturn(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
if (status)
goto put_stateid;
+ status = nfs4_check_fh(&cstate->current_fh, &dp->dl_stid);
+ if (status)
+ goto put_stateid;
+
trace_nfsd_deleg_return(stateid);
destroy_delegation(dp);
smp_mb__after_atomic();
@@ -8506,6 +8688,9 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
status = nfserr_no_grace;
if (!locks_in_grace(net) && lock->lk_reclaim)
goto out;
+ if (lock->lk_reclaim &&
+ test_bit(NFSD4_CLIENT_RECLAIM_COMPLETE, &cstate->clp->cl_flags))
+ goto out;
if (lock->lk_reclaim)
flags |= FL_RECLAIM;
@@ -8542,10 +8727,11 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
goto out;
}
- if (lock->lk_type & (NFS4_READW_LT | NFS4_WRITEW_LT) &&
- nfsd4_has_session(cstate) &&
- locks_can_async_lock(nf->nf_file->f_op))
- flags |= FL_SLEEP;
+ if ((lock->lk_type == NFS4_READW_LT ||
+ lock->lk_type == NFS4_WRITEW_LT) &&
+ nfsd4_has_session(cstate) &&
+ locks_can_async_lock(nf->nf_file->f_op))
+ flags |= FL_SLEEP;
nbl = find_or_allocate_block(lock_sop, &fp->fi_fhandle, nn);
if (!nbl) {
@@ -8587,7 +8773,7 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
nfs4_inc_and_copy_stateid(&lock->lk_resp_stateid, &lock_stp->st_stid);
status = 0;
if (lock->lk_reclaim)
- nn->somebody_reclaimed = true;
+ set_bit(NFSD_NET_SOMEBODY_RECLAIMED, &nn->flags);
break;
case FILE_LOCK_DEFERRED:
kref_put(&nbl->nbl_kref, free_nbl);
@@ -8963,9 +9149,13 @@ bool
nfs4_has_reclaimed_state(struct xdr_netobj name, struct nfsd_net *nn)
{
struct nfs4_client_reclaim *crp;
+ bool found;
+ down_read(&nn->reclaim_str_hashtbl_lock);
crp = nfsd4_find_reclaim_client(name, nn);
- return (crp && crp->cr_clp);
+ found = (crp && crp->cr_clp);
+ up_read(&nn->reclaim_str_hashtbl_lock);
+ return found;
}
/*
@@ -8978,10 +9168,39 @@ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash,
unsigned int strhashval;
struct nfs4_client_reclaim *crp;
+ down_write(&nn->reclaim_str_hashtbl_lock);
+
+ /*
+ * A reclaim record for this client name may already exist (for
+ * example, populated at boot from the recovery directory before
+ * an in-grace RECLAIM_COMPLETE or an nfsdcld downcall delivers
+ * the same name). Dedup here so reclaim_str_hashtbl_size stays
+ * equal to the number of distinct client names; inc_reclaim_complete
+ * relies on that equality to end the grace period via the fast path.
+ */
+ crp = nfsd4_find_reclaim_client(name, nn);
+ if (crp) {
+ if (princhash.len && crp->cr_princhash.len == 0) {
+ void *pdata = kmemdup(princhash.data, princhash.len,
+ GFP_KERNEL);
+ if (pdata) {
+ crp->cr_princhash.data = pdata;
+ crp->cr_princhash.len = princhash.len;
+ } else {
+ dprintk("%s: failed to allocate memory for princhash.data!\n",
+ __func__);
+ crp = NULL;
+ }
+ }
+ up_write(&nn->reclaim_str_hashtbl_lock);
+ return crp;
+ }
+
name.data = kmemdup(name.data, name.len, GFP_KERNEL);
if (!name.data) {
dprintk("%s: failed to allocate memory for name.data!\n",
__func__);
+ up_write(&nn->reclaim_str_hashtbl_lock);
return NULL;
}
if (princhash.len) {
@@ -8990,6 +9209,7 @@ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash,
dprintk("%s: failed to allocate memory for princhash.data!\n",
__func__);
kfree(name.data);
+ up_write(&nn->reclaim_str_hashtbl_lock);
return NULL;
}
} else
@@ -9009,6 +9229,7 @@ nfs4_client_to_reclaim(struct xdr_netobj name, struct xdr_netobj princhash,
kfree(name.data);
kfree(princhash.data);
}
+ up_write(&nn->reclaim_str_hashtbl_lock);
return crp;
}
@@ -9028,6 +9249,7 @@ nfs4_release_reclaim(struct nfsd_net *nn)
struct nfs4_client_reclaim *crp = NULL;
int i;
+ down_write(&nn->reclaim_str_hashtbl_lock);
for (i = 0; i < CLIENT_HASH_SIZE; i++) {
while (!list_empty(&nn->reclaim_str_hashtbl[i])) {
crp = list_entry(nn->reclaim_str_hashtbl[i].next,
@@ -9036,6 +9258,7 @@ nfs4_release_reclaim(struct nfsd_net *nn)
}
}
WARN_ON_ONCE(nn->reclaim_str_hashtbl_size);
+ up_write(&nn->reclaim_str_hashtbl_lock);
}
/*
@@ -9113,8 +9336,9 @@ static int nfs4_state_create_net(struct net *net)
nn->conf_name_tree = RB_ROOT;
nn->unconf_name_tree = RB_ROOT;
nn->boot_time = ktime_get_real_seconds();
- nn->grace_ended = false;
- nn->grace_end_forced = false;
+ nn->boot_time_bt = ktime_get_boottime_seconds();
+ clear_bit(NFSD_NET_GRACE_ENDED, &nn->flags);
+ clear_bit(NFSD_NET_GRACE_END_FORCED, &nn->flags);
nn->nfsd4_manager.block_opens = true;
INIT_LIST_HEAD(&nn->nfsd4_manager.list);
INIT_LIST_HEAD(&nn->client_lru);
@@ -9200,7 +9424,8 @@ nfs4_state_start_net(struct net *net)
nfsd4_client_tracking_init(net);
/* safe for laundromat to run now */
enable_delayed_work(&nn->laundromat_work);
- if (nn->track_reclaim_completes && nn->reclaim_str_hashtbl_size == 0)
+ if (test_bit(NFSD_NET_TRACK_RECLAIM_COMPLETES, &nn->flags) &&
+ nn->reclaim_str_hashtbl_size == 0)
goto skip_grace;
printk(KERN_INFO "NFSD: starting %lld-second grace period (net %x)\n",
nn->nfsd4_grace, net->ns.inum);
diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
index e17488a911f7..6a0a97d39bf6 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -449,9 +449,18 @@ nfsd4_decode_posixacl(struct nfsd4_compoundargs *argp, struct posix_acl **acl)
if (xdr_stream_decode_u32(argp->xdr, &count) < 0)
return nfserr_bad_xdr;
+ /*
+ * The NFSv4 POSIX ACL draft doesn't define a max number of ACE's, but
+ * the NFSACL spec does. For NFSv4, cap the number of entries to the v3
+ * limit, as we want to ensure that ACLs set via NFSv4 POSIX ACL
+ * extensions are retrievable via NFSACL.
+ */
+ if (count > NFS_ACL_MAX_ENTRIES)
+ return nfserr_inval;
+
*acl = posix_acl_alloc(count, GFP_KERNEL);
if (*acl == NULL)
- return nfserr_resource;
+ return nfserr_jukebox;
(*acl)->a_count = count;
for (ace = (*acl)->a_entries; ace < (*acl)->a_entries + count; ace++) {
@@ -628,6 +637,8 @@ nfsd4_decode_fattr4(struct nfsd4_compoundargs *argp, u32 *bmval, u32 bmlen,
if (!xdrgen_decode_fattr4_time_deleg_access(argp->xdr, &access))
return nfserr_bad_xdr;
+ if (access.nseconds >= NSEC_PER_SEC)
+ return nfserr_inval;
iattr->ia_atime.tv_sec = access.seconds;
iattr->ia_atime.tv_nsec = access.nseconds;
iattr->ia_valid |= ATTR_ATIME | ATTR_ATIME_SET | ATTR_DELEG;
@@ -637,6 +648,8 @@ nfsd4_decode_fattr4(struct nfsd4_compoundargs *argp, u32 *bmval, u32 bmlen,
if (!xdrgen_decode_fattr4_time_deleg_modify(argp->xdr, &modify))
return nfserr_bad_xdr;
+ if (modify.nseconds >= NSEC_PER_SEC)
+ return nfserr_inval;
iattr->ia_mtime.tv_sec = modify.seconds;
iattr->ia_mtime.tv_nsec = modify.nseconds;
iattr->ia_ctime.tv_sec = modify.seconds;
@@ -955,6 +968,10 @@ nfsd4_decode_create(struct nfsd4_compoundargs *argp, union nfsd4_op_u *u)
case NF4LNK:
if (xdr_stream_decode_u32(argp->xdr, &create->cr_datalen) < 0)
return nfserr_bad_xdr;
+ if (create->cr_datalen == 0)
+ return nfserr_inval;
+ if (create->cr_datalen > NFS4_MAXPATHLEN)
+ return nfserr_nametoolong;
p = xdr_inline_decode(argp->xdr, create->cr_datalen);
if (!p)
return nfserr_bad_xdr;
@@ -2106,6 +2123,7 @@ static __be32 nfsd4_decode_nl4_server(struct nfsd4_compoundargs *argp,
{
struct nfs42_netaddr *naddr;
__be32 *p;
+ u32 str_len;
if (xdr_stream_decode_u32(argp->xdr, &ns->nl4_type) < 0)
return nfserr_bad_xdr;
@@ -2135,6 +2153,18 @@ static __be32 nfsd4_decode_nl4_server(struct nfsd4_compoundargs *argp,
return nfserr_bad_xdr;
memcpy(naddr->addr, p, naddr->addr_len);
break;
+ case NL4_NAME:
+ case NL4_URL:
+ /*
+ * Well-formed XDR, but only NL4_NETADDR is supported. Consume
+ * the utf8str_cis to keep the stream aligned, then return
+ * NFS4ERR_NOTSUPP rather than the misleading NFS4ERR_BADXDR.
+ */
+ if (xdr_stream_decode_u32(argp->xdr, &str_len) < 0)
+ return nfserr_bad_xdr;
+ if (!xdr_inline_decode(argp->xdr, str_len))
+ return nfserr_bad_xdr;
+ return nfserr_notsupp;
default:
return nfserr_bad_xdr;
}
@@ -6390,9 +6420,6 @@ status:
write_bytes_to_xdr_buf(xdr->buf, op_status_offset,
&op->status, XDR_UNIT);
release:
- if (opdesc && opdesc->op_release)
- opdesc->op_release(&op->u);
-
/*
* Account for pages consumed while encoding this operation.
* The xdr_stream primitives don't manage rq_next_page.
@@ -6424,9 +6451,12 @@ void nfsd4_release_compoundargs(struct svc_rqst *rqstp)
{
struct nfsd4_compoundargs *args = rqstp->rq_argp;
+ args->opcnt = 0;
if (args->ops != args->iops) {
- vfree(args->ops);
+ void *old_ops = args->ops;
+
args->ops = args->iops;
+ kvfree_rcu_mightsleep(old_ops);
}
while (args->to_free) {
struct svcxdr_tmpbuf *tb = args->to_free;
diff --git a/fs/nfsd/nfscache.c b/fs/nfsd/nfscache.c
index 154468ceccdc..a9fa9dd410b2 100644
--- a/fs/nfsd/nfscache.c
+++ b/fs/nfsd/nfscache.c
@@ -200,14 +200,14 @@ int nfsd_reply_cache_init(struct nfsd_net *nn)
nn->nfsd_reply_cache_shrinker->seeks = 1;
nn->nfsd_reply_cache_shrinker->private_data = nn;
- shrinker_register(nn->nfsd_reply_cache_shrinker);
-
for (i = 0; i < hashsize; i++) {
INIT_LIST_HEAD(&nn->drc_hashtbl[i].lru_head);
spin_lock_init(&nn->drc_hashtbl[i].cache_lock);
}
nn->drc_hashsize = hashsize;
+ shrinker_register(nn->nfsd_reply_cache_shrinker);
+
return 0;
out_shrinker:
kvfree(nn->drc_hashtbl);
@@ -275,7 +275,7 @@ nfsd_prune_bucket_locked(struct nfsd_net *nn, struct nfsd_drc_bucket *b,
nfsd_cacherep_unlink_locked(nn, b, rp);
list_add(&rp->c_lru, dispose);
- if (max && ++freed > max)
+ if (max && ++freed >= max)
break;
}
}
diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index fa92e31d19d6..caf59421f8f4 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -296,14 +296,15 @@ static ssize_t write_unlock_fs(struct file *file, char *buf, size_t size)
* 2. Is that directory a mount point, or
* 3. Is that directory the root of an exported file system?
*/
- nfsd4_cancel_copy_by_sb(netns(file), path.dentry->d_sb);
error = nlmsvc_unlock_all_by_sb(path.dentry->d_sb);
mutex_lock(&nfsd_mutex);
nn = net_generic(netns(file), nfsd_net_id);
- if (nn->nfsd_serv)
+ if (nn->nfsd_serv) {
+ nfsd4_cancel_copy_by_sb(netns(file), path.dentry->d_sb);
nfsd4_revoke_states(nn, path.dentry->d_sb);
- else
+ } else {
error = -EINVAL;
+ }
mutex_unlock(&nfsd_mutex);
path_put(&path);
@@ -420,6 +421,7 @@ static ssize_t write_threads(struct file *file, char *buf, size_t size)
char *mesg = buf;
int rv;
struct net *net = netns(file);
+ struct nfsd_net *nn = net_generic(net, nfsd_net_id);
if (size > 0) {
int newthreads;
@@ -430,7 +432,10 @@ static ssize_t write_threads(struct file *file, char *buf, size_t size)
return -EINVAL;
trace_nfsd_ctl_threads(net, newthreads);
mutex_lock(&nfsd_mutex);
- rv = nfsd_svc(1, &newthreads, net, file->f_cred, NULL);
+ if (newthreads > 0 || nn->nfsd_serv != NULL)
+ rv = nfsd_svc(1, &newthreads, net, file->f_cred, NULL);
+ else
+ rv = 0;
mutex_unlock(&nfsd_mutex);
if (rv < 0)
return rv;
@@ -1111,7 +1116,7 @@ static ssize_t write_v4_end_grace(struct file *file, char *buf, size_t size)
}
return scnprintf(buf, SIMPLE_TRANSACTION_LIMIT, "%c\n",
- nn->grace_ended ? 'Y' : 'N');
+ test_bit(NFSD_NET_GRACE_ENDED, &nn->flags) ? 'Y' : 'N');
}
#endif
@@ -1414,8 +1419,8 @@ static int create_proc_exports_entry(void)
unsigned int nfsd_net_id;
struct nfsd_genl_rqstp {
- struct sockaddr rq_daddr;
- struct sockaddr rq_saddr;
+ struct sockaddr_storage rq_daddr;
+ struct sockaddr_storage rq_saddr;
unsigned long rq_flags;
ktime_t rq_stime;
__be32 rq_xid;
@@ -1448,9 +1453,9 @@ static int nfsd_genl_rpc_status_compose_msg(struct sk_buff *skb,
nla_put_s64(skb, NFSD_A_RPC_STATUS_SERVICE_TIME,
ktime_to_us(genl_rqstp->rq_stime),
NFSD_A_RPC_STATUS_PAD))
- return -ENOBUFS;
+ goto out_cancel;
- switch (genl_rqstp->rq_saddr.sa_family) {
+ switch (genl_rqstp->rq_saddr.ss_family) {
case AF_INET: {
const struct sockaddr_in *s_in, *d_in;
@@ -1464,7 +1469,7 @@ static int nfsd_genl_rpc_status_compose_msg(struct sk_buff *skb,
s_in->sin_port) ||
nla_put_be16(skb, NFSD_A_RPC_STATUS_DPORT,
d_in->sin_port))
- return -ENOBUFS;
+ goto out_cancel;
break;
}
case AF_INET6: {
@@ -1480,7 +1485,7 @@ static int nfsd_genl_rpc_status_compose_msg(struct sk_buff *skb,
s_in->sin6_port) ||
nla_put_be16(skb, NFSD_A_RPC_STATUS_DPORT,
d_in->sin6_port))
- return -ENOBUFS;
+ goto out_cancel;
break;
}
}
@@ -1488,10 +1493,14 @@ static int nfsd_genl_rpc_status_compose_msg(struct sk_buff *skb,
for (i = 0; i < genl_rqstp->rq_opcnt; i++)
if (nla_put_u32(skb, NFSD_A_RPC_STATUS_COMPOUND_OPS,
genl_rqstp->rq_opnum[i]))
- return -ENOBUFS;
+ goto out_cancel;
genlmsg_end(skb, hdr);
return 0;
+
+out_cancel:
+ genlmsg_cancel(skb, hdr);
+ return -ENOBUFS;
}
/**
@@ -1519,18 +1528,28 @@ int nfsd_nl_rpc_status_get_dumpit(struct sk_buff *skb,
for (i = 0; i < nn->nfsd_serv->sv_nrpools; i++) {
struct svc_rqst *rqstp;
+ long thread_skip = 0;
if (i < cb->args[0]) /* already consumed */
continue;
+ /*
+ * The saved thread index only applies to the pool the dump
+ * was resumed in. Subsequent pools must start from thread 0,
+ * otherwise their first cb->args[1] threads are silently
+ * skipped.
+ */
+ if (i == cb->args[0])
+ thread_skip = cb->args[1];
+
rqstp_index = 0;
list_for_each_entry_rcu(rqstp,
&nn->nfsd_serv->sv_pools[i].sp_all_threads,
rq_all) {
- struct nfsd_genl_rqstp genl_rqstp;
+ struct nfsd_genl_rqstp genl_rqstp = {};
unsigned int status_counter;
- if (rqstp_index++ < cb->args[1]) /* already consumed */
+ if (rqstp_index++ < thread_skip) /* already consumed */
continue;
/*
* Acquire rq_status_counter before parsing the rqst
@@ -1551,9 +1570,9 @@ int nfsd_nl_rpc_status_get_dumpit(struct sk_buff *skb,
genl_rqstp.rq_stime = rqstp->rq_stime;
genl_rqstp.rq_opcnt = 0;
memcpy(&genl_rqstp.rq_daddr, svc_daddr(rqstp),
- sizeof(struct sockaddr));
+ sizeof(struct sockaddr_storage));
memcpy(&genl_rqstp.rq_saddr, svc_addr(rqstp),
- sizeof(struct sockaddr));
+ sizeof(struct sockaddr_storage));
#ifdef CONFIG_NFSD_V4
if (rqstp->rq_vers == NFS4_VERSION &&
@@ -1572,17 +1591,26 @@ int nfsd_nl_rpc_status_get_dumpit(struct sk_buff *skb,
#endif /* CONFIG_NFSD_V4 */
/*
- * Acquire rq_status_counter before reporting the rqst
- * fields to the user.
+ * Read-side load-load fence: order the field reads
+ * above before the counter re-read below, mirroring
+ * the smp_rmb() in the standard seqcount retry. The
+ * begin-side smp_load_acquire() above pairs with the
+ * smp_store_release() in nfsd_dispatch().
*/
- if (smp_load_acquire(&rqstp->rq_status_counter) !=
- status_counter)
+ smp_rmb();
+ if (READ_ONCE(rqstp->rq_status_counter) != status_counter)
continue;
ret = nfsd_genl_rpc_status_compose_msg(skb, cb,
&genl_rqstp);
- if (ret)
+ if (ret) {
+ if (skb->len) {
+ cb->args[0] = i;
+ cb->args[1] = rqstp_index - 1;
+ ret = skb->len;
+ }
goto out;
+ }
}
}
@@ -1944,6 +1972,60 @@ err_free_msg:
}
/**
+ * nfsd_nl_validate_listeners - sanity-check the listener list from userland
+ * @info: netlink metadata and command arguments
+ *
+ * Walk every NFSD_A_SERVER_SOCK_ADDR attribute and confirm that each entry
+ * is well-formed: it parses against the policy, carries both an address and
+ * a transport name, and the address is long enough for its family. Doing
+ * this up front lets the callers below assume every entry is valid and
+ * guarantees we make no changes when the request is malformed.
+ *
+ * Return: 0 if every entry is valid, or a negative errno otherwise.
+ */
+static int nfsd_nl_validate_listeners(struct genl_info *info)
+{
+ const struct nlattr *attr;
+ int rem;
+
+ nlmsg_for_each_attr_type(attr, NFSD_A_SERVER_SOCK_ADDR, info->nlhdr,
+ GENL_HDRLEN, rem) {
+ struct nlattr *tb[NFSD_A_SOCK_MAX + 1];
+ struct sockaddr *sa;
+ int err;
+
+ err = nla_parse_nested(tb, NFSD_A_SOCK_MAX, attr,
+ nfsd_sock_nl_policy, info->extack);
+ if (err < 0)
+ return err;
+
+ if (!tb[NFSD_A_SOCK_ADDR] || !tb[NFSD_A_SOCK_TRANSPORT_NAME])
+ return -EINVAL;
+
+ sa = nla_data(tb[NFSD_A_SOCK_ADDR]);
+ if (nla_len(tb[NFSD_A_SOCK_ADDR]) < sizeof(sa->sa_family))
+ return -EINVAL;
+
+ switch (sa->sa_family) {
+ case AF_INET:
+ if (nla_len(tb[NFSD_A_SOCK_ADDR]) <
+ sizeof(struct sockaddr_in))
+ return -EINVAL;
+ break;
+ case AF_INET6:
+ if (nla_len(tb[NFSD_A_SOCK_ADDR]) <
+ sizeof(struct sockaddr_in6))
+ return -EINVAL;
+ break;
+ default:
+ return -EAFNOSUPPORT;
+ }
+ }
+
+ return 0;
+}
+
+/**
* nfsd_nl_listener_set_doit - set the nfs running sockets
* @skb: reply buffer
* @info: netlink metadata and command arguments
@@ -1961,6 +2043,15 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info)
bool delete = false;
int err, rem;
+ /*
+ * Validate the entire listener list before making any changes, so a
+ * malformed request fails cleanly without creating a serv or touching
+ * the existing listeners.
+ */
+ err = nfsd_nl_validate_listeners(info);
+ if (err)
+ return err;
+
mutex_lock(&nfsd_mutex);
err = nfsd_create_serv(net);
@@ -1987,16 +2078,11 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info)
const char *xcl_name;
struct sockaddr *sa;
+ /* validated up front in nfsd_nl_validate_listeners() */
if (nla_parse_nested(tb, NFSD_A_SOCK_MAX, attr,
nfsd_sock_nl_policy, info->extack) < 0)
continue;
- if (!tb[NFSD_A_SOCK_ADDR] || !tb[NFSD_A_SOCK_TRANSPORT_NAME])
- continue;
-
- if (nla_len(tb[NFSD_A_SOCK_ADDR]) < sizeof(*sa))
- continue;
-
xcl_name = nla_data(tb[NFSD_A_SOCK_TRANSPORT_NAME]);
sa = nla_data(tb[NFSD_A_SOCK_ADDR]);
@@ -2048,16 +2134,11 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info)
struct sockaddr *sa;
int ret;
+ /* validated up front in nfsd_nl_validate_listeners() */
if (nla_parse_nested(tb, NFSD_A_SOCK_MAX, attr,
nfsd_sock_nl_policy, info->extack) < 0)
continue;
- if (!tb[NFSD_A_SOCK_ADDR] || !tb[NFSD_A_SOCK_TRANSPORT_NAME])
- continue;
-
- if (nla_len(tb[NFSD_A_SOCK_ADDR]) < sizeof(*sa))
- continue;
-
xcl_name = nla_data(tb[NFSD_A_SOCK_TRANSPORT_NAME]);
sa = nla_data(tb[NFSD_A_SOCK_ADDR]);
@@ -2512,11 +2593,12 @@ static int __init init_nfsd(void)
{
int retval;
- nfsd_debugfs_init();
-
retval = nfsd4_init_slabs();
if (retval)
return retval;
+
+ nfsd_debugfs_init();
+
retval = nfsd4_init_pnfs();
if (retval)
goto out_free_slabs;
@@ -2561,8 +2643,8 @@ out_free_lockd:
out_free_pnfs:
nfsd4_exit_pnfs();
out_free_slabs:
- nfsd4_free_slabs();
nfsd_debugfs_exit();
+ nfsd4_free_slabs();
return retval;
}
@@ -2577,9 +2659,9 @@ static void __exit exit_nfsd(void)
unregister_pernet_subsys(&nfsd_net_ops);
nfsd_drc_slab_free();
nfsd_lockd_shutdown();
- nfsd4_free_slabs();
nfsd4_exit_pnfs();
nfsd_debugfs_exit();
+ nfsd4_free_slabs();
}
MODULE_AUTHOR("Olaf Kirch <okir@monad.swb.de>");
diff --git a/fs/nfsd/nfsfh.c b/fs/nfsd/nfsfh.c
index 429ca5c6ec08..ab53de1c280d 100644
--- a/fs/nfsd/nfsfh.c
+++ b/fs/nfsd/nfsfh.c
@@ -70,10 +70,8 @@ nfsd_mode_check(struct dentry *dentry, umode_t requested)
if (requested == 0) /* the caller doesn't care */
return nfs_ok;
if (mode == requested) {
- if (mode == S_IFDIR && !d_can_lookup(dentry)) {
- WARN_ON_ONCE(1);
+ if (mode == S_IFDIR && !d_can_lookup(dentry))
return nfserr_notdir;
- }
return nfs_ok;
}
if (mode == S_IFLNK) {
@@ -344,15 +342,19 @@ static __be32 nfsd_set_fh_dentry(struct svc_rqst *rqstp, struct net *net,
if (dentry->d_sb->s_export_op->flags & EXPORT_OP_NOWCC)
fhp->fh_no_wcc = true;
fhp->fh_64bit_cookies = true;
- if (exp->ex_flags & NFSEXP_V4ROOT)
+ if (exp->ex_flags & NFSEXP_V4ROOT) {
+ dput(dentry);
goto out;
+ }
break;
case NFS_FHSIZE:
fhp->fh_no_wcc = true;
if (EX_WGATHER(exp))
fhp->fh_use_wgather = true;
- if (exp->ex_flags & NFSEXP_V4ROOT)
+ if (exp->ex_flags & NFSEXP_V4ROOT) {
+ dput(dentry);
goto out;
+ }
}
fhp->fh_dentry = dentry;
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index 8873033d1e82..a73d5c259cd9 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -82,6 +82,7 @@ nfsd_proc_setattr(struct svc_rqst *rqstp)
.na_iattr = iap,
};
struct svc_fh *fhp;
+ int hosterr;
dprintk("nfsd: SETATTR %s, valid=%x, size=%ld\n",
SVCFH_fmt(&argp->fh),
@@ -117,6 +118,12 @@ nfsd_proc_setattr(struct svc_rqst *rqstp)
if (resp->status != nfs_ok)
goto out;
+ hosterr = fh_want_write(fhp);
+ if (hosterr) {
+ resp->status = nfserrno(hosterr);
+ goto out;
+ }
+
if (delta < 0)
delta = -delta;
if (delta < MAX_TOUCH_TIME_ERROR &&
diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c
index 4f1ab3222a4d..5e0a0c1cbb91 100644
--- a/fs/nfsd/nfssvc.c
+++ b/fs/nfsd/nfssvc.c
@@ -351,7 +351,7 @@ static int nfsd_startup_net(struct net *net, const struct cred *cred)
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
int ret;
- if (nn->nfsd_net_up)
+ if (test_bit(NFSD_NET_UP, &nn->flags))
return 0;
ret = nfsd_startup_generic();
@@ -364,11 +364,11 @@ static int nfsd_startup_net(struct net *net, const struct cred *cred)
goto out_socks;
}
- if (nfsd_needs_lockd(nn) && !nn->lockd_up) {
+ if (nfsd_needs_lockd(nn) && !test_bit(NFSD_NET_LOCKD_UP, &nn->flags)) {
ret = lockd_up(net, cred);
if (ret)
goto out_socks;
- nn->lockd_up = true;
+ set_bit(NFSD_NET_LOCKD_UP, &nn->flags);
}
ret = nfsd_file_cache_start_net(net);
@@ -386,7 +386,7 @@ static int nfsd_startup_net(struct net *net, const struct cred *cred)
if (ret)
goto out_reply_cache;
- nn->nfsd_net_up = true;
+ set_bit(NFSD_NET_UP, &nn->flags);
return 0;
out_reply_cache:
@@ -394,9 +394,9 @@ out_reply_cache:
out_filecache:
nfsd_file_cache_shutdown_net(net);
out_lockd:
- if (nn->lockd_up) {
+ if (test_bit(NFSD_NET_LOCKD_UP, &nn->flags)) {
lockd_down(net);
- nn->lockd_up = false;
+ clear_bit(NFSD_NET_LOCKD_UP, &nn->flags);
}
out_socks:
nfsd_shutdown_generic();
@@ -407,7 +407,7 @@ static void nfsd_shutdown_net(struct net *net)
{
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
- if (nn->nfsd_net_up) {
+ if (test_bit(NFSD_NET_UP, &nn->flags)) {
percpu_ref_kill_and_confirm(&nn->nfsd_net_ref, nfsd_net_done);
wait_for_completion(&nn->nfsd_net_confirm_done);
@@ -415,18 +415,18 @@ static void nfsd_shutdown_net(struct net *net)
nfs4_state_shutdown_net(net);
nfsd_reply_cache_shutdown(nn);
nfsd_file_cache_shutdown_net(net);
- if (nn->lockd_up) {
+ if (test_bit(NFSD_NET_LOCKD_UP, &nn->flags)) {
lockd_down(net);
- nn->lockd_up = false;
+ clear_bit(NFSD_NET_LOCKD_UP, &nn->flags);
}
wait_for_completion(&nn->nfsd_net_free_done);
}
percpu_ref_exit(&nn->nfsd_net_ref);
- if (nn->nfsd_net_up)
+ if (test_bit(NFSD_NET_UP, &nn->flags))
nfsd_shutdown_generic();
- nn->nfsd_net_up = false;
+ clear_bit(NFSD_NET_UP, &nn->flags);
}
static DEFINE_SPINLOCK(nfsd_notifier_lock);
@@ -815,7 +815,7 @@ nfsd_acl_init_request(struct svc_rqst *rqstp,
ret->mismatch.lovers = NFSD_ACL_NRVERS;
for (i = NFSD_ACL_MINVERS; i < NFSD_ACL_NRVERS; i++) {
- if (nfsd_support_acl_version(rqstp->rq_vers) &&
+ if (nfsd_support_acl_version(i) &&
nfsd_vers(nn, i, NFSD_TEST)) {
ret->mismatch.lovers = i;
break;
@@ -825,7 +825,7 @@ nfsd_acl_init_request(struct svc_rqst *rqstp,
return rpc_prog_unavail;
ret->mismatch.hivers = NFSD_ACL_MINVERS;
for (i = NFSD_ACL_NRVERS - 1; i >= NFSD_ACL_MINVERS; i--) {
- if (nfsd_support_acl_version(rqstp->rq_vers) &&
+ if (nfsd_support_acl_version(i) &&
nfsd_vers(nn, i, NFSD_TEST)) {
ret->mismatch.hivers = i;
break;
@@ -960,6 +960,20 @@ nfsd(void *vrqstp)
return 0;
}
+/*
+ * Set rq_status_counter back to an even value, indicating that the rqstp
+ * fields are no longer meaningful to a lockless reader. This pairs with the
+ * odd-valued store made once the request has been decoded, and must run on
+ * every return path that follows it so that the seq-lock like protocol used
+ * by nfsd_nl_rpc_status_get_dumpit() is not left permanently odd. The store
+ * also advances the counter so a concurrent reader detects the transition.
+ */
+static void nfsd_status_counter_set_idle(struct svc_rqst *rqstp)
+{
+ smp_store_release(&rqstp->rq_status_counter,
+ (rqstp->rq_status_counter | 1) + 1);
+}
+
/**
* nfsd_dispatch - Process an NFS or NFSACL or LOCALIO Request
* @rqstp: incoming request
@@ -1022,14 +1036,9 @@ int nfsd_dispatch(struct svc_rqst *rqstp)
if (!proc->pc_encode(rqstp, &rqstp->rq_res_stream))
goto out_encode_err;
- /*
- * Release rq_status_counter setting it to an even value after the rpc
- * request has been properly processed.
- */
- smp_store_release(&rqstp->rq_status_counter, rqstp->rq_status_counter + 1);
-
nfsd_cache_update(rqstp, rp, ntli->ntli_cachetype, nfs_reply);
out_cached_reply:
+ nfsd_status_counter_set_idle(rqstp);
return 1;
out_decode_err:
@@ -1040,12 +1049,14 @@ out_decode_err:
out_update_drop:
nfsd_cache_update(rqstp, rp, RC_NOCACHE, NULL);
out_dropit:
+ nfsd_status_counter_set_idle(rqstp);
return 0;
out_encode_err:
trace_nfsd_cant_encode_err(rqstp);
nfsd_cache_update(rqstp, rp, RC_NOCACHE, NULL);
*statp = rpc_system_err;
+ nfsd_status_counter_set_idle(rqstp);
return 1;
}
diff --git a/fs/nfsd/nfsxdr.c b/fs/nfsd/nfsxdr.c
index ae71e0621317..86c9dd4b334b 100644
--- a/fs/nfsd/nfsxdr.c
+++ b/fs/nfsd/nfsxdr.c
@@ -10,6 +10,16 @@
#include "auth.h"
/*
+ * Sun convention: a sattr time-useconds field of one full second (an
+ * otherwise out-of-range value) means "set this time to the current
+ * server time." It's needed to make permissions checks for the "touch"
+ * program across NFSv2 mounts work correctly. See description of
+ * sattr in section 6.1 of "NFS Illustrated" by Brent Callaghan,
+ * Addison-Wesley, ISBN 0-201-32750-5
+ */
+#define NFS2_SATTR_SET_TO_SERVER_TIME (1000000)
+
+/*
* Mapping of S_IF* types to NFS file types
*/
static const u32 nfs_ftypes[] = {
@@ -172,27 +182,29 @@ svcxdr_decode_sattr(struct svc_rqst *rqstp, struct xdr_stream *xdr,
tmp1 = be32_to_cpup(p++);
tmp2 = be32_to_cpup(p++);
if (tmp1 != (u32)-1 && tmp2 != (u32)-1) {
+ /*
+ * Range test here to prevent the multiplication from
+ * wrapping to a valid (but incorrect) value on 32-bit
+ * platforms.
+ */
+ if (tmp2 > NFS2_SATTR_SET_TO_SERVER_TIME)
+ return false;
iap->ia_valid |= ATTR_ATIME | ATTR_ATIME_SET;
iap->ia_atime.tv_sec = tmp1;
iap->ia_atime.tv_nsec = tmp2 * NSEC_PER_USEC;
+ if (tmp2 == NFS2_SATTR_SET_TO_SERVER_TIME)
+ iap->ia_valid &= ~ATTR_ATIME_SET;
}
tmp1 = be32_to_cpup(p++);
tmp2 = be32_to_cpup(p++);
if (tmp1 != (u32)-1 && tmp2 != (u32)-1) {
+ if (tmp2 > NFS2_SATTR_SET_TO_SERVER_TIME)
+ return false;
iap->ia_valid |= ATTR_MTIME | ATTR_MTIME_SET;
iap->ia_mtime.tv_sec = tmp1;
iap->ia_mtime.tv_nsec = tmp2 * NSEC_PER_USEC;
- /*
- * Passing the invalid value useconds=1000000 for mtime
- * is a Sun convention for "set both mtime and atime to
- * current server time". It's needed to make permissions
- * checks for the "touch" program across v2 mounts to
- * Solaris and Irix boxes work correctly. See description of
- * sattr in section 6.1 of "NFS Illustrated" by
- * Brent Callaghan, Addison-Wesley, ISBN 0-201-32750-5
- */
- if (tmp2 == 1000000)
+ if (tmp2 == NFS2_SATTR_SET_TO_SERVER_TIME)
iap->ia_valid &= ~(ATTR_ATIME_SET|ATTR_MTIME_SET);
}
diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h
index dec83e92650d..f44ea672670f 100644
--- a/fs/nfsd/state.h
+++ b/fs/nfsd/state.h
@@ -384,6 +384,7 @@ struct nfsd4_session {
u16 se_slot_gen;
bool se_dead;
u32 se_target_maxslots;
+ struct rcu_head rcu_head;
};
/* formatted contents of nfs4_sessionid */
@@ -496,7 +497,7 @@ struct nfs4_client {
#define NFSD4_CB_FAULT 3
int cl_cb_state;
struct nfsd4_callback cl_cb_null;
- struct nfsd4_session *cl_cb_session;
+ struct nfsd4_session __rcu *cl_cb_session;
/* for all client information that callback code might need: */
spinlock_t cl_lock;
@@ -754,6 +755,7 @@ struct nfs4_layout_stateid {
struct delayed_work ls_fence_work;
unsigned int ls_fence_delay;
bool ls_fenced;
+ bool ls_fence_inflight;
};
static inline struct nfs4_layout_stateid *layoutstateid(struct nfs4_stid *s)
diff --git a/fs/nfsd/trace.h b/fs/nfsd/trace.h
index 1c5a1e50f946..33953d38314e 100644
--- a/fs/nfsd/trace.h
+++ b/fs/nfsd/trace.h
@@ -271,7 +271,7 @@ TRACE_EVENT_CONDITION(nfsd_fh_verify,
TP_CONDITION(rqstp != NULL),
TP_STRUCT__entry(
__field(unsigned int, netns_ino)
- __sockaddr(server, rqstp->rq_xprt->xpt_remotelen)
+ __sockaddr(server, rqstp->rq_xprt->xpt_locallen)
__sockaddr(client, rqstp->rq_xprt->xpt_remotelen)
__field(u32, xid)
__field(u32, fh_hash)
@@ -310,7 +310,7 @@ TRACE_EVENT_CONDITION(nfsd_fh_verify_err,
TP_CONDITION(rqstp != NULL && error),
TP_STRUCT__entry(
__field(unsigned int, netns_ino)
- __sockaddr(server, rqstp->rq_xprt->xpt_remotelen)
+ __sockaddr(server, rqstp->rq_xprt->xpt_locallen)
__sockaddr(client, rqstp->rq_xprt->xpt_remotelen)
__field(u32, xid)
__field(u32, fh_hash)
@@ -1727,9 +1727,10 @@ DEFINE_NFSD_CB_LIFETIME_EVENT(bc_shutdown);
TRACE_EVENT(nfsd_cb_seq_status,
TP_PROTO(
const struct rpc_task *task,
- const struct nfsd4_callback *cb
+ const struct nfsd4_callback *cb,
+ const struct nfsd4_session *session
),
- TP_ARGS(task, cb),
+ TP_ARGS(task, cb, session),
TP_STRUCT__entry(
__field(unsigned int, task_id)
__field(unsigned int, client_id)
@@ -1741,8 +1742,6 @@ TRACE_EVENT(nfsd_cb_seq_status,
__field(int, seq_status)
),
TP_fast_assign(
- const struct nfs4_client *clp = cb->cb_clp;
- const struct nfsd4_session *session = clp->cl_cb_session;
const struct nfsd4_sessionid *sid =
(struct nfsd4_sessionid *)&session->se_sessionid;
@@ -1768,9 +1767,10 @@ TRACE_EVENT(nfsd_cb_seq_status,
TRACE_EVENT(nfsd_cb_free_slot,
TP_PROTO(
const struct rpc_task *task,
- const struct nfsd4_callback *cb
+ const struct nfsd4_callback *cb,
+ const struct nfsd4_session *session
),
- TP_ARGS(task, cb),
+ TP_ARGS(task, cb, session),
TP_STRUCT__entry(
__field(unsigned int, task_id)
__field(unsigned int, client_id)
@@ -1781,8 +1781,6 @@ TRACE_EVENT(nfsd_cb_free_slot,
__field(u32, slot_seqno)
),
TP_fast_assign(
- const struct nfs4_client *clp = cb->cb_clp;
- const struct nfsd4_session *session = clp->cl_cb_session;
const struct nfsd4_sessionid *sid =
(struct nfsd4_sessionid *)&session->se_sessionid;
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index 1e89c7ff9493..f73012bc742f 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -137,8 +137,10 @@ nfsd_cross_mnt(struct svc_rqst *rqstp, struct dentry **dpp,
follow_flags = LOOKUP_AUTOMOUNT;
err = follow_down(&path, follow_flags);
- if (err < 0)
+ if (err < 0) {
+ path_put(&path);
goto out;
+ }
if (path.mnt == exp->ex_path.mnt && path.dentry == dentry &&
nfsd_mountpoint(dentry, exp) == 2) {
/* This is only a mountpoint in some other namespace */
@@ -419,21 +421,22 @@ nfsd_sanitize_attrs(struct inode *inode, struct iattr *iap)
}
static __be32
-nfsd_get_write_access(struct svc_rqst *rqstp, struct svc_fh *fhp,
- struct iattr *iap)
+nfsd_may_truncate(struct svc_rqst *rqstp, struct svc_fh *fhp,
+ struct iattr *iap)
{
struct inode *inode = d_inode(fhp->fh_dentry);
- if (iap->ia_size < inode->i_size) {
- __be32 err;
+ if (iap->ia_size >= i_size_read(inode))
+ return nfs_ok;
- err = nfsd_permission(&rqstp->rq_cred,
- fhp->fh_export, fhp->fh_dentry,
- NFSD_MAY_TRUNC | NFSD_MAY_OWNER_OVERRIDE);
- if (err)
- return err;
- }
- return nfserrno(get_write_access(inode));
+ return nfsd_permission(&rqstp->rq_cred, fhp->fh_export, fhp->fh_dentry,
+ NFSD_MAY_TRUNC | NFSD_MAY_OWNER_OVERRIDE);
+}
+
+static __be32
+nfsd_get_write_access(struct svc_fh *fhp)
+{
+ return nfserrno(get_write_access(d_inode(fhp->fh_dentry)));
}
static int __nfsd_setattr(struct dentry *dentry, struct iattr *iap)
@@ -560,12 +563,17 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp,
* setattr call.
*/
if (size_change) {
- err = nfsd_get_write_access(rqstp, fhp, iap);
+ err = nfsd_get_write_access(fhp);
if (err)
return err;
}
inode_lock(inode);
+ if (size_change) {
+ err = nfsd_may_truncate(rqstp, fhp, iap);
+ if (err)
+ goto out_unlock;
+ }
err = fh_fill_pre_attrs(fhp);
if (err)
goto out_unlock;
@@ -1374,6 +1382,7 @@ nfsd_direct_write(struct svc_rqst *rqstp, struct svc_fh *fhp,
struct file *file = nf->nf_file;
unsigned int nsegs, i;
ssize_t host_err;
+ size_t expected;
nsegs = nfsd_write_dio_iters_init(nf, rqstp->rq_bvec, nvecs,
kiocb, *cnt, segments);
@@ -1395,11 +1404,13 @@ nfsd_direct_write(struct svc_rqst *rqstp, struct svc_fh *fhp,
kiocb->ki_flags |= IOCB_DONTCACHE;
}
+ expected = iov_iter_count(&segments[i].iter);
+
host_err = vfs_iocb_iter_write(file, kiocb, &segments[i].iter);
if (host_err < 0)
return host_err;
*cnt += host_err;
- if (host_err < segments[i].iter.count)
+ if (host_err < (ssize_t)expected)
break; /* partial write */
}
diff --git a/fs/nilfs2/bmap.c b/fs/nilfs2/bmap.c
index 5f0f1f283af0..83f6ea30cc8b 100644
--- a/fs/nilfs2/bmap.c
+++ b/fs/nilfs2/bmap.c
@@ -175,7 +175,7 @@ static int nilfs_bmap_do_delete(struct nilfs_bmap *bmap, __u64 key)
return ret;
}
- return bmap->b_ops->bop_delete(bmap, key);
+ return bmap->b_ops->bop_delete(bmap, key, false);
}
/**
diff --git a/fs/nilfs2/bmap.h b/fs/nilfs2/bmap.h
index 4656df392722..a72f3c308a5d 100644
--- a/fs/nilfs2/bmap.h
+++ b/fs/nilfs2/bmap.h
@@ -63,7 +63,7 @@ struct nilfs_bmap_operations {
int (*bop_lookup_contig)(const struct nilfs_bmap *, __u64, __u64 *,
unsigned int);
int (*bop_insert)(struct nilfs_bmap *, __u64, __u64);
- int (*bop_delete)(struct nilfs_bmap *, __u64);
+ int (*bop_delete)(struct nilfs_bmap *bmap, __u64 key, bool deform);
void (*bop_clear)(struct nilfs_bmap *);
int (*bop_propagate)(struct nilfs_bmap *, struct buffer_head *);
diff --git a/fs/nilfs2/btree.c b/fs/nilfs2/btree.c
index 64d5f7c5ab44..64bac66af25b 100644
--- a/fs/nilfs2/btree.c
+++ b/fs/nilfs2/btree.c
@@ -1425,6 +1425,28 @@ static void nilfs_btree_shrink(struct nilfs_bmap *btree,
path[level].bp_bh = NULL;
}
+/**
+ * nilfs_btree_discard - discard the last node for the mapping transformation
+ * @btree: bmap struct of btree
+ * @path: array of nilfs_btree_path struct
+ * @level: level of the B-tree node being operated on
+ * @keyp: argument for passing a key (unused)
+ * @ptrp: argument for passing a pointer (unused)
+ */
+static void nilfs_btree_discard(struct nilfs_bmap *btree,
+ struct nilfs_btree_path *path, int level,
+ __u64 *keyp, __u64 *ptrp)
+{
+ struct nilfs_btree_node *root = nilfs_btree_get_root(btree);
+
+ nilfs_btree_node_delete(root, 0, NULL, NULL,
+ NILFS_BTREE_ROOT_NCHILDREN_MAX);
+ nilfs_btree_node_set_level(root, level);
+
+ nilfs_btnode_delete(path[level].bp_bh);
+ path[level].bp_bh = NULL;
+}
+
static void nilfs_btree_nop(struct nilfs_bmap *btree,
struct nilfs_btree_path *path,
int level, __u64 *keyp, __u64 *ptrp)
@@ -1435,7 +1457,7 @@ static int nilfs_btree_prepare_delete(struct nilfs_bmap *btree,
struct nilfs_btree_path *path,
int *levelp,
struct nilfs_bmap_stats *stats,
- struct inode *dat)
+ struct inode *dat, bool deform)
{
struct buffer_head *bh;
struct nilfs_btree_node *node, *parent, *sib;
@@ -1522,15 +1544,17 @@ static int nilfs_btree_prepare_delete(struct nilfs_bmap *btree,
if (nilfs_btree_node_get_nchildren(node) - 1 <=
NILFS_BTREE_ROOT_NCHILDREN_MAX) {
path[level].bp_op = nilfs_btree_shrink;
- stats->bs_nblocks += 2;
- level++;
- path[level].bp_op = nilfs_btree_nop;
- goto shrink_root_child;
+ } else if (deform) {
+ path[level].bp_op = nilfs_btree_discard;
} else {
path[level].bp_op = nilfs_btree_do_delete;
stats->bs_nblocks++;
goto out;
}
+ stats->bs_nblocks += 2;
+ level++;
+ path[level].bp_op = nilfs_btree_nop;
+ goto shrink_root_child;
}
}
@@ -1581,7 +1605,7 @@ static void nilfs_btree_commit_delete(struct nilfs_bmap *btree,
nilfs_bmap_set_dirty(btree);
}
-static int nilfs_btree_delete(struct nilfs_bmap *btree, __u64 key)
+static int nilfs_btree_delete(struct nilfs_bmap *btree, __u64 key, bool deform)
{
struct nilfs_btree_path *path;
@@ -1601,7 +1625,8 @@ static int nilfs_btree_delete(struct nilfs_bmap *btree, __u64 key)
dat = NILFS_BMAP_USE_VBN(btree) ? nilfs_bmap_get_dat(btree) : NULL;
- ret = nilfs_btree_prepare_delete(btree, path, &level, &stats, dat);
+ ret = nilfs_btree_prepare_delete(btree, path, &level, &stats, dat,
+ deform);
if (ret < 0)
goto out;
nilfs_btree_commit_delete(btree, path, level, dat);
diff --git a/fs/nilfs2/direct.c b/fs/nilfs2/direct.c
index 8bd0b1374e25..b8643d3aa2f8 100644
--- a/fs/nilfs2/direct.c
+++ b/fs/nilfs2/direct.c
@@ -144,7 +144,7 @@ static int nilfs_direct_insert(struct nilfs_bmap *bmap, __u64 key, __u64 ptr)
return ret;
}
-static int nilfs_direct_delete(struct nilfs_bmap *bmap, __u64 key)
+static int nilfs_direct_delete(struct nilfs_bmap *bmap, __u64 key, bool deform)
{
union nilfs_bmap_ptr_req req;
struct inode *dat;
@@ -234,7 +234,7 @@ int nilfs_direct_delete_and_convert(struct nilfs_bmap *bmap,
/* no need to allocate any resource for conversion */
/* delete */
- ret = bmap->b_ops->bop_delete(bmap, key);
+ ret = bmap->b_ops->bop_delete(bmap, key, true);
if (ret < 0)
return ret;
diff --git a/fs/notify/fanotify/fanotify.c b/fs/notify/fanotify/fanotify.c
index a3555bebad63..b05b6d3abb87 100644
--- a/fs/notify/fanotify/fanotify.c
+++ b/fs/notify/fanotify/fanotify.c
@@ -600,8 +600,7 @@ static struct fanotify_event *fanotify_alloc_perm_event(const void *data,
pevent->hdr.len = 0;
pevent->state = FAN_EVENT_INIT;
pevent->path = *path;
- /* NULL ppos means no range info */
- pevent->ppos = range ? &range->pos : NULL;
+ pevent->pos = range ? range->pos : FANOTIFY_NO_RANGE;
pevent->count = range ? range->count : 0;
path_get(path);
diff --git a/fs/notify/fanotify/fanotify.h b/fs/notify/fanotify/fanotify.h
index a0619e7694d5..3710543dbf82 100644
--- a/fs/notify/fanotify/fanotify.h
+++ b/fs/notify/fanotify/fanotify.h
@@ -428,6 +428,8 @@ FANOTIFY_ME(struct fanotify_event *event)
return container_of(event, struct fanotify_mnt_event, fae);
}
+#define FANOTIFY_NO_RANGE ((loff_t)-1)
+
/*
* Structure for permission fanotify events. It gets allocated and freed in
* fanotify_handle_event() since we wait there for user response. When the
@@ -438,7 +440,7 @@ FANOTIFY_ME(struct fanotify_event *event)
struct fanotify_perm_event {
struct fanotify_event fae;
struct path path;
- const loff_t *ppos; /* optional file range info */
+ loff_t pos; /* FANOTIFY_NO_RANGE if unavailable */
size_t count;
u32 response; /* userspace answer to the event */
unsigned short state; /* state of the event */
@@ -468,7 +470,7 @@ static inline bool fanotify_event_has_access_range(struct fanotify_event *event)
if (!(event->mask & FANOTIFY_PRE_CONTENT_EVENTS))
return false;
- return FANOTIFY_PERM(event)->ppos;
+ return FANOTIFY_PERM(event)->pos != FANOTIFY_NO_RANGE;
}
static inline struct fanotify_event *FANOTIFY_E(struct fsnotify_event *fse)
diff --git a/fs/notify/fanotify/fanotify_user.c b/fs/notify/fanotify/fanotify_user.c
index b604e3da58ad..7278286f5a6d 100644
--- a/fs/notify/fanotify/fanotify_user.c
+++ b/fs/notify/fanotify/fanotify_user.c
@@ -675,12 +675,9 @@ static size_t copy_range_info_to_user(struct fanotify_event *event,
if (WARN_ON_ONCE(info_len > count))
return -EFAULT;
- if (WARN_ON_ONCE(!pevent->ppos))
- return -EINVAL;
-
info.hdr.info_type = FAN_EVENT_INFO_TYPE_RANGE;
info.hdr.len = info_len;
- info.offset = *(pevent->ppos);
+ info.offset = pevent->pos;
info.count = pevent->count;
if (copy_to_user(buf, &info, info_len))
@@ -1316,16 +1313,18 @@ static bool fanotify_mark_update_flags(struct fsnotify_mark *fsn_mark,
static bool fanotify_mark_add_to_mask(struct fsnotify_mark *fsn_mark,
__u32 mask, unsigned int fan_flags)
{
+ __u32 old_mask;
bool recalc;
spin_lock(&fsn_mark->lock);
- if (!(fan_flags & FANOTIFY_MARK_IGNORE_BITS))
+ if (!(fan_flags & FANOTIFY_MARK_IGNORE_BITS)) {
+ old_mask = fsn_mark->mask;
fsn_mark->mask |= mask;
- else
+ recalc = old_mask != fsn_mark->mask;
+ } else {
fsn_mark->ignore_mask |= mask;
-
- recalc = fsnotify_calc_mask(fsn_mark) &
- ~fsnotify_conn_mask(fsn_mark->connector);
+ recalc = true;
+ }
recalc |= fanotify_mark_update_flags(fsn_mark, fan_flags);
spin_unlock(&fsn_mark->lock);
diff --git a/fs/notify/inotify/inotify_user.c b/fs/notify/inotify/inotify_user.c
index ed37491c1618..5f19c24ec187 100644
--- a/fs/notify/inotify/inotify_user.c
+++ b/fs/notify/inotify/inotify_user.c
@@ -539,7 +539,6 @@ static int inotify_update_existing_watch(struct fsnotify_group *group,
{
struct fsnotify_mark *fsn_mark;
struct inotify_inode_mark *i_mark;
- __u32 old_mask, new_mask;
int replace = !(arg & IN_MASK_ADD);
int create = (arg & IN_MASK_CREATE);
int ret;
@@ -555,27 +554,15 @@ static int inotify_update_existing_watch(struct fsnotify_group *group,
i_mark = container_of(fsn_mark, struct inotify_inode_mark, fsn_mark);
spin_lock(&fsn_mark->lock);
- old_mask = fsn_mark->mask;
if (replace) {
fsn_mark->mask = 0;
fsn_mark->flags &= ~INOTIFY_MARK_FLAGS;
}
fsn_mark->mask |= inotify_arg_to_mask(inode, arg);
fsn_mark->flags |= inotify_arg_to_flags(arg);
- new_mask = fsn_mark->mask;
spin_unlock(&fsn_mark->lock);
- if (old_mask != new_mask) {
- /* more bits in old than in new? */
- int dropped = (old_mask & ~new_mask);
- /* more bits in this fsn_mark than the inode's mask? */
- int do_inode = (new_mask & ~READ_ONCE(inode->i_fsnotify_mask));
-
- /* update the inode with this new fsn_mark */
- if (dropped || do_inode)
- fsnotify_recalc_mask(fsn_mark->connector);
-
- }
+ fsnotify_recalc_mask(fsn_mark->connector);
/* return the wd */
ret = i_mark->wd;
diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c
index 58f32aac5f61..0c147b99d4ee 100644
--- a/fs/ntfs/attrib.c
+++ b/fs/ntfs/attrib.c
@@ -693,6 +693,8 @@ static bool ntfs_non_resident_attr_value_is_valid(const struct attr_record *a)
u32 attr_len;
u32 min_len;
u16 mp_offset;
+ u16 name_offset;
+ u32 name_end;
attr_len = le32_to_cpu(a->length);
min_len = offsetof(struct attr_record, data.non_resident.initialized_size) +
@@ -701,7 +703,27 @@ static bool ntfs_non_resident_attr_value_is_valid(const struct attr_record *a)
return false;
mp_offset = le16_to_cpu(a->data.non_resident.mapping_pairs_offset);
- return mp_offset >= min_len && mp_offset <= attr_len;
+ if (mp_offset < min_len || mp_offset > attr_len)
+ return false;
+
+ if (a->name_length) {
+ name_offset = le16_to_cpu(a->name_offset);
+
+ if (name_offset < min_len || name_offset >= attr_len)
+ return false;
+
+ name_end = name_offset + a->name_length * sizeof(__le16);
+ if (name_end > attr_len || name_end > mp_offset)
+ return false;
+ }
+
+ /* Ensure there's room for the compressed_size field if needed. */
+ if (!(a->flags & (ATTR_IS_SPARSE | ATTR_COMPRESSION_MASK)) &&
+ attr_len - mp_offset <
+ sizeof(a->data.non_resident.compressed_size))
+ return false;
+
+ return true;
}
static bool ntfs_attr_value_is_valid(struct ntfs_volume *vol,
diff --git a/fs/ntfs/lcnalloc.c b/fs/ntfs/lcnalloc.c
index 835a041023a2..aa2e017a4384 100644
--- a/fs/ntfs/lcnalloc.c
+++ b/fs/ntfs/lcnalloc.c
@@ -298,7 +298,12 @@ struct runlist_element *ntfs_cluster_alloc(struct ntfs_volume *vol, const s64 st
clusters = count;
rlpos = rlsize = 0;
mapping = lcnbmp_vi->i_mapping;
- i_size = i_size_read(lcnbmp_vi);
+ /*
+ * lcn_empty_bits_per_page is sized from nr_clusters, but $Bitmap can
+ * cover more clusters than that; bound the scan by the array.
+ */
+ i_size = min_t(s64, i_size_read(lcnbmp_vi),
+ ((s64)vol->nr_clusters + 7) >> 3);
while (1) {
ntfs_debug("Start of outer while loop: done_zones 0x%x, search_zone %i, pass %i, zone_start 0x%llx, zone_end 0x%llx, bmp_initial_pos 0x%llx, bmp_pos 0x%llx, rlpos %i, rlsize %i.",
done_zones, search_zone, pass,
diff --git a/fs/ntfs/runlist.c b/fs/ntfs/runlist.c
index 8e0fd400e7f7..00373e450ea7 100644
--- a/fs/ntfs/runlist.c
+++ b/fs/ntfs/runlist.c
@@ -772,6 +772,25 @@ struct runlist_element *ntfs_mapping_pairs_decompress(const struct ntfs_volume *
return ERR_PTR(-EIO);
}
+ /*
+ * An empty mapping-pairs array is valid only for a zero-length
+ * attribute.
+ */
+ if (!*buf &&
+ (vcn ||
+ le64_to_cpu(attr->data.non_resident.highest_vcn) !=
+ (u64)(vcn - 1) ||
+ le64_to_cpu(attr->data.non_resident.allocated_size) ||
+ le64_to_cpu(attr->data.non_resident.data_size) ||
+ le64_to_cpu(attr->data.non_resident.initialized_size))) {
+ ntfs_error(vol->sb, "Invalid empty mapping pairs array.");
+ return ERR_PTR(-EIO);
+ }
+ if (!vcn && !*buf && old_runlist && old_runlist->rl) {
+ *new_rl_count = old_runlist->count;
+ return old_runlist->rl;
+ }
+
/* Current position in runlist array. */
rlpos = 0;
/* Allocate first page and set current runlist size to one page. */
@@ -884,12 +903,41 @@ struct runlist_element *ntfs_mapping_pairs_decompress(const struct ntfs_volume *
ntfs_error(vol->sb, "lcn == -1");
}
#endif
+ /* Check lcn is within the volume. */
+ if (unlikely(lcn >= (s64)vol->nr_clusters)) {
+ ntfs_error(vol->sb,
+ "LCN >= nr_clusters in mapping pairs array.");
+ goto err_out;
+ }
+
/* Check lcn is not below -1. */
if (unlikely(lcn < -1)) {
ntfs_error(vol->sb, "Invalid s64 < -1 in mapping pairs array.");
goto err_out;
}
+ if (lcn >= 0) {
+ s64 run_end;
+
+ /*
+ * Ensure that the run stays within the volume.
+ * A valid starting LCN is not sufficient because
+ * the run length comes from disk.
+ */
+ if (unlikely(check_add_overflow(lcn,
+ rl[rlpos].length,
+ &run_end))) {
+ ntfs_error(vol->sb,
+ "Run length overflow in mapping pairs array.");
+ goto err_out;
+ }
+ if (unlikely(run_end > (s64)vol->nr_clusters)) {
+ ntfs_error(vol->sb,
+ "Run extends beyond volume boundary.");
+ goto err_out;
+ }
+ }
+
/* chkdsk accepts zero-sized runs only for holes */
if ((lcn != -1) && !rl[rlpos].length) {
ntfs_error(vol->sb,
diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c
index 8abe7bee4c0d..cd8fa2c13370 100644
--- a/fs/ntfs/super.c
+++ b/fs/ntfs/super.c
@@ -645,7 +645,7 @@ static bool parse_ntfs_boot_sector(struct ntfs_volume *vol,
{
unsigned int sectors_per_cluster, sectors_per_cluster_bits, nr_hidden_sects;
int clusters_per_mft_record, clusters_per_index_record;
- s64 ll;
+ u64 ll;
vol->sector_size = le16_to_cpu(b->bpb.bytes_per_sector);
vol->sector_size_bits = ffs(vol->sector_size) - 1;
@@ -755,23 +755,23 @@ static bool parse_ntfs_boot_sector(struct ntfs_volume *vol,
* the same as it is much faster on 32-bit CPUs.
*/
ll = le64_to_cpu(b->number_of_sectors) >> sectors_per_cluster_bits;
- if ((u64)ll >= 1ULL << 32) {
+ if (ll >= 1ULL << 32) {
ntfs_error(vol->sb, "Cannot handle 64-bit clusters.");
return false;
}
vol->nr_clusters = ll;
ntfs_debug("vol->nr_clusters = 0x%llx", vol->nr_clusters);
ll = le64_to_cpu(b->mft_lcn);
- if (ll >= vol->nr_clusters) {
- ntfs_error(vol->sb, "MFT LCN (%lli, 0x%llx) is beyond end of volume. Weird.",
+ if (ll >= (u64)vol->nr_clusters) {
+ ntfs_error(vol->sb, "MFT LCN (%llu, 0x%llx) is beyond end of volume. Weird.",
ll, ll);
return false;
}
vol->mft_lcn = ll;
ntfs_debug("vol->mft_lcn = 0x%llx", vol->mft_lcn);
ll = le64_to_cpu(b->mftmirr_lcn);
- if (ll >= vol->nr_clusters) {
- ntfs_error(vol->sb, "MFTMirr LCN (%lli, 0x%llx) is beyond end of volume. Weird.",
+ if (ll >= (u64)vol->nr_clusters) {
+ ntfs_error(vol->sb, "MFTMirr LCN (%llu, 0x%llx) is beyond end of volume. Weird.",
ll, ll);
return false;
}
diff --git a/fs/ntfs3/frecord.c b/fs/ntfs3/frecord.c
index 2b49bc077558..a751661cdca9 100644
--- a/fs/ntfs3/frecord.c
+++ b/fs/ntfs3/frecord.c
@@ -2443,6 +2443,15 @@ int ni_read_frame(struct ntfs_inode *ni, u64 frame_vbo, struct page **pages,
err = unc_size;
else if (!unc_size || unc_size > frame_size)
err = -EINVAL;
+ else if (unc_size < frame_size) {
+ /*
+ * Partial decompress: zero the [unc_size, frame_size)
+ * tail. decompress_lznt() leaves it untouched, so
+ * without this the freshly vmapped pages would expose
+ * uninitialized kernel memory to userspace.
+ */
+ memset(frame_mem + unc_size, 0, frame_size - unc_size);
+ }
}
if (!err && valid_size < frame_vbo + frame_size) {
size_t ok = valid_size - frame_vbo;
diff --git a/fs/ntfs3/fslog.c b/fs/ntfs3/fslog.c
index f038c799e7ac..c759841b7430 100644
--- a/fs/ntfs3/fslog.c
+++ b/fs/ntfs3/fslog.c
@@ -648,6 +648,14 @@ static inline void *enum_rstbl(struct RESTART_TABLE *t, void *c)
}
/*
+ * dp_range_ok - true if [j, j + count) fits in a page_lcns[cap] array.
+ */
+static inline bool dp_range_ok(size_t j, u32 count, u32 cap)
+{
+ return j < cap && count <= cap - j;
+}
+
+/*
* find_dp - Search for a @vcn in Dirty Page Table.
*/
static inline struct DIR_PAGE_ENTRY *find_dp(struct RESTART_TABLE *dptbl,
@@ -789,6 +797,20 @@ static bool check_rstbl(const struct RESTART_TABLE *rt, size_t bytes)
return true;
}
+static bool check_dp_table(const struct RESTART_TABLE *dptbl)
+{
+ u32 rsize = le16_to_cpu(dptbl->size);
+ struct DIR_PAGE_ENTRY *dp = NULL;
+
+ while ((dp = enum_rstbl((struct RESTART_TABLE *)dptbl, dp))) {
+ if (struct_size(dp, page_lcns, le32_to_cpu(dp->lcns_follow)) >
+ rsize)
+ return false;
+ }
+
+ return true;
+}
+
/*
* free_rsttbl_idx - Free a previously allocated index a Restart Table.
*/
@@ -4295,6 +4317,11 @@ check_dirty_page_table:
goto out;
}
+ if (!check_dp_table(rt)) {
+ err = -EINVAL;
+ goto out;
+ }
+
dptbl = kmemdup(rt, t32, GFP_NOFS);
if (!dptbl) {
err = -ENOMEM;
@@ -5087,6 +5114,13 @@ find_dirty_page:
/* Shorten length by any Lcns which were deleted. */
saved_len = dlen;
+ if (!dp_range_ok(le64_to_cpu(lrh->target_vcn) - le64_to_cpu(dp->vcn),
+ le16_to_cpu(lrh->lcns_follow),
+ le32_to_cpu(dp->lcns_follow))) {
+ err = -EINVAL;
+ goto out;
+ }
+
for (i = le16_to_cpu(lrh->lcns_follow); i; i--) {
size_t j;
u32 alen, voff;
diff --git a/fs/ocfs2/cluster/heartbeat.c b/fs/ocfs2/cluster/heartbeat.c
index d12784aaaa4b..ea68dc0887f6 100644
--- a/fs/ocfs2/cluster/heartbeat.c
+++ b/fs/ocfs2/cluster/heartbeat.c
@@ -43,6 +43,14 @@ static DECLARE_RWSEM(o2hb_callback_sem);
* whenever any of the threads sees activity from the node in its region.
*/
static DEFINE_SPINLOCK(o2hb_live_lock);
+/*
+ * Serializes region pin/unpin dependency management (o2hb_dependent_users
+ * and the o2nm_depend_item()/o2nm_undepend_item() calls). o2hb_region_pin()
+ * has to drop o2hb_live_lock across the sleeping o2nm_depend_item(), so the
+ * spinlock alone can no longer keep pin and unpin mutually exclusive; this
+ * mutex, taken outside o2hb_live_lock, does.
+ */
+static DEFINE_MUTEX(o2hb_dependency_mutex);
static struct list_head o2hb_live_slots[O2NM_MAX_NODES];
static unsigned long o2hb_live_node_bitmap[BITS_TO_LONGS(O2NM_MAX_NODES)];
static LIST_HEAD(o2hb_node_events);
@@ -138,7 +146,7 @@ static unsigned int o2hb_dependent_users;
* In global heartbeat mode, we pin/unpin all o2hb regions. This solution
* works for both file system and userdlm domains.
*/
-static int o2hb_region_pin(const char *region_uuid);
+static int o2hb_region_pin(const char *region_uuid, bool from_callback);
static void o2hb_region_unpin(const char *region_uuid);
/* Only sets a new threshold if there are no active regions.
@@ -2142,6 +2150,7 @@ static void o2hb_heartbeat_group_drop_item(struct config_group *group,
* If global heartbeat active and there are dependent users,
* pin all regions if quorum region count <= CUT_OFF
*/
+ mutex_lock(&o2hb_dependency_mutex);
spin_lock(&o2hb_live_lock);
if (!o2hb_dependent_users)
@@ -2149,10 +2158,11 @@ static void o2hb_heartbeat_group_drop_item(struct config_group *group,
if (bitmap_weight(o2hb_quorum_region_bitmap,
O2NM_MAX_REGIONS) <= O2HB_PIN_CUT_OFF)
- o2hb_region_pin(NULL);
+ o2hb_region_pin(NULL, true);
unlock:
spin_unlock(&o2hb_live_lock);
+ mutex_unlock(&o2hb_dependency_mutex);
}
static ssize_t o2hb_heartbeat_group_dead_threshold_show(struct config_item *item,
@@ -2290,48 +2300,113 @@ EXPORT_SYMBOL_GPL(o2hb_setup_callback);
* In local, we only pin the matching region. In global we pin all the active
* regions.
*/
-static int o2hb_region_pin(const char *region_uuid)
+static int o2hb_region_pin(const char *region_uuid, bool from_callback)
{
- int ret = 0, found = 0;
- struct o2hb_region *reg;
+ int ret = 0, found;
+ struct o2hb_region *reg, *pinned;
char *uuid;
assert_spin_locked(&o2hb_live_lock);
- list_for_each_entry(reg, &o2hb_all_regions, hr_all_item) {
- if (reg->hr_item_dropped)
- continue;
+ do {
+ found = 0;
+ pinned = NULL;
- uuid = config_item_name(&reg->hr_item);
+ list_for_each_entry(reg, &o2hb_all_regions, hr_all_item) {
+ if (reg->hr_item_dropped)
+ continue;
- /* local heartbeat */
- if (region_uuid) {
- if (strcmp(region_uuid, uuid))
+ uuid = config_item_name(&reg->hr_item);
+
+ /* local heartbeat */
+ if (region_uuid) {
+ if (strcmp(region_uuid, uuid))
+ continue;
+ found = 1;
+ }
+
+ if (reg->hr_item_pinned || reg->hr_item_dropped) {
+ if (found)
+ break;
continue;
- found = 1;
+ }
+
+ /*
+ * Found a region that needs pinning. Take a reference
+ * so it stays alive while we drop the lock below.
+ */
+ pinned = reg;
+ config_item_get(&reg->hr_item);
+ break;
}
- if (reg->hr_item_pinned || reg->hr_item_dropped)
- goto skip_pin;
+ if (!pinned)
+ break;
+
+ uuid = config_item_name(&pinned->hr_item);
+
+ /*
+ * o2nm_depend_item() -> configfs_depend_item() can sleep (it
+ * takes the configfs root inode rwsem), so it must not run
+ * under o2hb_live_lock. Drop the lock across it; @pinned is
+ * kept alive by the reference taken above. The region list may
+ * change while unlocked, so we rescan from the top afterwards.
+ */
+ spin_unlock(&o2hb_live_lock);
/* Ignore ENOENT only for local hb (userdlm domain) */
- ret = o2nm_depend_item(&reg->hr_item);
+ if (from_callback)
+ ret = o2nm_depend_item_unlocked(&pinned->hr_item);
+ else
+ ret = o2nm_depend_item(&pinned->hr_item);
+
+ spin_lock(&o2hb_live_lock);
if (!ret) {
- mlog(ML_CLUSTER, "Pin region %s\n", uuid);
- reg->hr_item_pinned = 1;
- } else {
- if (ret == -ENOENT && found)
- ret = 0;
- else {
- mlog(ML_ERROR, "Pin region %s fails with %d\n",
- uuid, ret);
+ /*
+ * o2hb_live_lock was dropped across o2nm_depend_item().
+ * o2hb_set_quorum_device() runs in the heartbeat thread
+ * without o2hb_dependency_mutex, so for global heartbeat
+ * it may have crossed O2HB_PIN_CUT_OFF and unpinned the
+ * regions while we slept. If that happened this pin is
+ * no longer wanted; undo it and stop rather than
+ * resurrecting it on the rescan below.
+ */
+ if (!region_uuid &&
+ bitmap_weight(o2hb_quorum_region_bitmap,
+ O2NM_MAX_REGIONS) > O2HB_PIN_CUT_OFF) {
+ o2nm_undepend_item(&pinned->hr_item);
+ spin_unlock(&o2hb_live_lock);
+ config_item_put(&pinned->hr_item);
+ spin_lock(&o2hb_live_lock);
break;
}
+ mlog(ML_CLUSTER, "Pin region %s\n", uuid);
+ pinned->hr_item_pinned = 1;
+ } else if (ret == -ENOENT && (found || !region_uuid)) {
+ /*
+ * For local hb (found): ignore ENOENT from userdlm
+ * domains as before. For global hb (!region_uuid):
+ * the region may have been detached from configfs
+ * while the lock was dropped — skip it and continue
+ * pinning the remaining regions.
+ */
+ ret = 0;
+ } else {
+ mlog(ML_ERROR, "Pin region %s fails with %d\n",
+ uuid, ret);
}
-skip_pin:
- if (found)
- break;
- }
+
+ /*
+ * config_item_put() may drop the last reference and run
+ * o2hb_region_release(), which also grabs o2hb_live_lock and
+ * can sleep, so it must happen with the lock released.
+ */
+ spin_unlock(&o2hb_live_lock);
+ config_item_put(&pinned->hr_item);
+ spin_lock(&o2hb_live_lock);
+
+ /* local hb pins a single matching region */
+ } while (!ret && !region_uuid);
return ret;
}
@@ -2376,12 +2451,13 @@ static int o2hb_region_inc_user(const char *region_uuid)
{
int ret = 0;
+ mutex_lock(&o2hb_dependency_mutex);
spin_lock(&o2hb_live_lock);
/* local heartbeat */
if (!o2hb_global_heartbeat_active()) {
- ret = o2hb_region_pin(region_uuid);
- goto unlock;
+ ret = o2hb_region_pin(region_uuid, false);
+ goto unlock;
}
/*
@@ -2393,16 +2469,23 @@ static int o2hb_region_inc_user(const char *region_uuid)
goto unlock;
if (bitmap_weight(o2hb_quorum_region_bitmap,
- O2NM_MAX_REGIONS) <= O2HB_PIN_CUT_OFF)
- ret = o2hb_region_pin(NULL);
+ O2NM_MAX_REGIONS) <= O2HB_PIN_CUT_OFF) {
+ ret = o2hb_region_pin(NULL, false);
+ if (ret) {
+ o2hb_region_unpin(NULL);
+ o2hb_dependent_users--;
+ }
+ }
unlock:
spin_unlock(&o2hb_live_lock);
+ mutex_unlock(&o2hb_dependency_mutex);
return ret;
}
static void o2hb_region_dec_user(const char *region_uuid)
{
+ mutex_lock(&o2hb_dependency_mutex);
spin_lock(&o2hb_live_lock);
/* local heartbeat */
@@ -2421,6 +2504,7 @@ static void o2hb_region_dec_user(const char *region_uuid)
unlock:
spin_unlock(&o2hb_live_lock);
+ mutex_unlock(&o2hb_dependency_mutex);
}
int o2hb_register_callback(const char *region_uuid,
diff --git a/fs/ocfs2/cluster/nodemanager.c b/fs/ocfs2/cluster/nodemanager.c
index 402563154550..21154e4baddf 100644
--- a/fs/ocfs2/cluster/nodemanager.c
+++ b/fs/ocfs2/cluster/nodemanager.c
@@ -777,6 +777,12 @@ int o2nm_depend_item(struct config_item *item)
return configfs_depend_item(&o2nm_cluster_group.cs_subsys, item);
}
+int o2nm_depend_item_unlocked(struct config_item *item)
+{
+ return configfs_depend_item_unlocked(&o2nm_cluster_group.cs_subsys,
+ item);
+}
+
void o2nm_undepend_item(struct config_item *item)
{
configfs_undepend_item(item);
diff --git a/fs/ocfs2/cluster/nodemanager.h b/fs/ocfs2/cluster/nodemanager.h
index 3490e77a952d..2f72f56996bd 100644
--- a/fs/ocfs2/cluster/nodemanager.h
+++ b/fs/ocfs2/cluster/nodemanager.h
@@ -64,6 +64,7 @@ void o2nm_node_get(struct o2nm_node *node);
void o2nm_node_put(struct o2nm_node *node);
int o2nm_depend_item(struct config_item *item);
+int o2nm_depend_item_unlocked(struct config_item *item);
void o2nm_undepend_item(struct config_item *item);
int o2nm_depend_this_node(void);
void o2nm_undepend_this_node(void);
diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c
index d7fc3cccf2f4..5ee894966b12 100644
--- a/fs/ocfs2/dir.c
+++ b/fs/ocfs2/dir.c
@@ -625,6 +625,28 @@ static int ocfs2_validate_dx_root(struct super_block *sb,
le16_to_cpu(el->l_count));
goto bail;
}
+ } else {
+ struct ocfs2_dx_entry_list *dl_list = &dx_root->dr_entries;
+
+ if (le16_to_cpu(dl_list->de_count) !=
+ ocfs2_dx_entries_per_root(sb)) {
+ ret = ocfs2_error(sb,
+ "Dir Index Root # %llu has invalid de_count %u (expected %u)\n",
+ (unsigned long long)le64_to_cpu(dx_root->dr_blkno),
+ le16_to_cpu(dl_list->de_count),
+ ocfs2_dx_entries_per_root(sb));
+ goto bail;
+ }
+
+ if (le16_to_cpu(dl_list->de_num_used) >
+ le16_to_cpu(dl_list->de_count)) {
+ ret = ocfs2_error(sb,
+ "Dir Index Root # %llu has invalid de_num_used %u (de_count %u)\n",
+ (unsigned long long)le64_to_cpu(dx_root->dr_blkno),
+ le16_to_cpu(dl_list->de_num_used),
+ le16_to_cpu(dl_list->de_count));
+ goto bail;
+ }
}
bail:
@@ -664,10 +686,25 @@ static int ocfs2_validate_dx_leaf(struct super_block *sb,
return ret;
}
- if (!OCFS2_IS_VALID_DX_LEAF(dx_leaf)) {
- ret = ocfs2_error(sb, "Dir Index Leaf has bad signature %.*s\n",
- 7, dx_leaf->dl_signature);
- }
+ if (!OCFS2_IS_VALID_DX_LEAF(dx_leaf))
+ return ocfs2_error(sb, "Dir Index Leaf has bad signature %.*s\n",
+ 7, dx_leaf->dl_signature);
+
+ if (le16_to_cpu(dx_leaf->dl_list.de_count) !=
+ ocfs2_dx_entries_per_leaf(sb))
+ return ocfs2_error(sb,
+ "Dir Index Leaf # %llu has invalid de_count %u (expected %u)\n",
+ (unsigned long long)le64_to_cpu(dx_leaf->dl_blkno),
+ le16_to_cpu(dx_leaf->dl_list.de_count),
+ ocfs2_dx_entries_per_leaf(sb));
+
+ if (le16_to_cpu(dx_leaf->dl_list.de_num_used) >
+ le16_to_cpu(dx_leaf->dl_list.de_count))
+ return ocfs2_error(sb,
+ "Dir Index Leaf # %llu has invalid de_num_used %u (de_count %u)\n",
+ (unsigned long long)le64_to_cpu(dx_leaf->dl_blkno),
+ le16_to_cpu(dx_leaf->dl_list.de_num_used),
+ le16_to_cpu(dx_leaf->dl_list.de_count));
return ret;
}
@@ -1917,7 +1954,7 @@ static int ocfs2_dir_foreach_blk_el(struct inode *inode,
i += le16_to_cpu(de->rec_len);
}
offset = i;
- ctx->pos = (ctx->pos & ~(sb->s_blocksize - 1))
+ ctx->pos = (ctx->pos & ~((loff_t)sb->s_blocksize - 1))
| offset;
*f_version = inode_query_iversion(inode);
}
diff --git a/fs/ocfs2/dlm/dlmmaster.c b/fs/ocfs2/dlm/dlmmaster.c
index aee3b4c56dcc..612969867ff9 100644
--- a/fs/ocfs2/dlm/dlmmaster.c
+++ b/fs/ocfs2/dlm/dlmmaster.c
@@ -3099,6 +3099,12 @@ int dlm_migrate_request_handler(struct o2net_msg *msg, u32 len, void *data,
name = migrate->name;
namelen = migrate->namelen;
+ if (namelen > DLM_LOCKID_NAME_MAX) {
+ mlog(ML_ERROR, "%s: invalid name length %u in migrate request\n",
+ dlm->name, namelen);
+ ret = -EINVAL;
+ goto leave;
+ }
hash = dlm_lockid_hash(name, namelen);
/* preallocate.. if this fails, abort */
diff --git a/fs/ocfs2/dlm/dlmrecovery.c b/fs/ocfs2/dlm/dlmrecovery.c
index 9b97bf73df22..9d4a2695b959 100644
--- a/fs/ocfs2/dlm/dlmrecovery.c
+++ b/fs/ocfs2/dlm/dlmrecovery.c
@@ -1357,6 +1357,15 @@ int dlm_mig_lockres_handler(struct o2net_msg *msg, u32 len, void *data,
if (!dlm_grab(dlm))
return -EINVAL;
+ if (mres->lockname_len > DLM_LOCKID_NAME_MAX ||
+ mres->num_locks > DLM_MAX_MIGRATABLE_LOCKS ||
+ be16_to_cpu(msg->data_len) < struct_size(mres, ml, mres->num_locks)) {
+ mlog(ML_ERROR, "%s: invalid lockres migration message from %u\n",
+ dlm->name, mres->master);
+ dlm_put(dlm);
+ return -EINVAL;
+ }
+
if (!dlm_joined(dlm)) {
mlog(ML_ERROR, "Domain %s not joined! "
"lockres %.*s, master %u\n",
diff --git a/fs/ocfs2/refcounttree.c b/fs/ocfs2/refcounttree.c
index 7323bde70caa..d9f22b4a2654 100644
--- a/fs/ocfs2/refcounttree.c
+++ b/fs/ocfs2/refcounttree.c
@@ -116,6 +116,33 @@ static int ocfs2_validate_refcount_block(struct super_block *sb,
le32_to_cpu(rb->rf_fs_generation));
goto out;
}
+
+ /*
+ * rf_records (rl_count/rl_used/rl_recs[]) is only meaningful when
+ * this block is not an interior tree block (OCFS2_REFCOUNT_TREE_FL);
+ * in that case the same union bytes hold an extent list (rf_list)
+ * instead, which is validated by ocfs2_validate_extent_block().
+ */
+ if (!(le32_to_cpu(rb->rf_flags) & OCFS2_REFCOUNT_TREE_FL)) {
+ if (le16_to_cpu(rb->rf_records.rl_count) !=
+ ocfs2_refcount_recs_per_rb(sb)) {
+ rc = ocfs2_error(sb,
+ "Refcount block #%llu has an invalid rl_count of %u\n",
+ (unsigned long long)bh->b_blocknr,
+ le16_to_cpu(rb->rf_records.rl_count));
+ goto out;
+ }
+
+ if (le16_to_cpu(rb->rf_records.rl_used) >
+ le16_to_cpu(rb->rf_records.rl_count)) {
+ rc = ocfs2_error(sb,
+ "Refcount block #%llu has an invalid rl_used of %u (rl_count %u)\n",
+ (unsigned long long)bh->b_blocknr,
+ le16_to_cpu(rb->rf_records.rl_used),
+ le16_to_cpu(rb->rf_records.rl_count));
+ goto out;
+ }
+ }
out:
return rc;
}
@@ -3360,10 +3387,9 @@ static int ocfs2_replace_cow(struct ocfs2_cow_context *context)
cow_start += num_clusters;
}
- if (ocfs2_dealloc_has_cluster(&context->dealloc)) {
+ if (ocfs2_dealloc_has_cluster(&context->dealloc))
ocfs2_schedule_truncate_log_flush(osb, 1);
- ocfs2_run_deallocs(osb, &context->dealloc);
- }
+ ocfs2_run_deallocs(osb, &context->dealloc);
return ret;
}
@@ -3846,10 +3872,9 @@ unlock:
ocfs2_unlock_refcount_tree(osb, ref_tree, 1);
brelse(ref_root_bh);
- if (!ret && ocfs2_dealloc_has_cluster(&dealloc)) {
+ if (!ret && ocfs2_dealloc_has_cluster(&dealloc))
ocfs2_schedule_truncate_log_flush(osb, 1);
- ocfs2_run_deallocs(osb, &dealloc);
- }
+ ocfs2_run_deallocs(osb, &dealloc);
out:
/*
* Empty the extent map so that we may get the right extent
@@ -4135,10 +4160,9 @@ out_unlock_refcount:
ocfs2_unlock_refcount_tree(osb, ref_tree, 1);
brelse(ref_root_bh);
out:
- if (ocfs2_dealloc_has_cluster(&dealloc)) {
+ if (ocfs2_dealloc_has_cluster(&dealloc))
ocfs2_schedule_truncate_log_flush(osb, 1);
- ocfs2_run_deallocs(osb, &dealloc);
- }
+ ocfs2_run_deallocs(osb, &dealloc);
return ret;
}
@@ -4691,10 +4715,9 @@ loff_t ocfs2_reflink_remap_blocks(struct inode *s_inode,
}
out:
- if (ocfs2_dealloc_has_cluster(&dealloc)) {
+ if (ocfs2_dealloc_has_cluster(&dealloc))
ocfs2_schedule_truncate_log_flush(osb, 1);
- ocfs2_run_deallocs(osb, &dealloc);
- }
+ ocfs2_run_deallocs(osb, &dealloc);
return ret;
}
diff --git a/fs/ocfs2/suballoc.c b/fs/ocfs2/suballoc.c
index a4a2b87a45fe..20c3aec6b987 100644
--- a/fs/ocfs2/suballoc.c
+++ b/fs/ocfs2/suballoc.c
@@ -2759,7 +2759,7 @@ static int _ocfs2_reclaim_suballoc_to_main(handle_t *handle,
fe->i_clusters = cpu_to_le32(tmp_used - le16_to_cpu(cl->cl_cpg));
spin_lock(&OCFS2_I(alloc_inode)->ip_lock);
- OCFS2_I(alloc_inode)->ip_clusters -= le32_to_cpu(fe->i_clusters);
+ OCFS2_I(alloc_inode)->ip_clusters = le32_to_cpu(fe->i_clusters);
fe->i_size = cpu_to_le64(ocfs2_clusters_to_bytes(alloc_inode->i_sb,
le32_to_cpu(fe->i_clusters)));
spin_unlock(&OCFS2_I(alloc_inode)->ip_lock);
diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
index 5989351aff93..f1b3aaab6a71 100644
--- a/fs/ocfs2/xattr.c
+++ b/fs/ocfs2/xattr.c
@@ -7241,10 +7241,9 @@ out_unlock:
ref_tree, 1);
brelse(ref_root_bh);
- if (ocfs2_dealloc_has_cluster(&dealloc)) {
+ if (ocfs2_dealloc_has_cluster(&dealloc))
ocfs2_schedule_truncate_log_flush(OCFS2_SB(old_inode->i_sb), 1);
- ocfs2_run_deallocs(OCFS2_SB(old_inode->i_sb), &dealloc);
- }
+ ocfs2_run_deallocs(OCFS2_SB(old_inode->i_sb), &dealloc);
out:
return ret;
diff --git a/fs/orangefs/devorangefs-req.c b/fs/orangefs/devorangefs-req.c
index 33ee8cb32f83..e5c60da7e677 100644
--- a/fs/orangefs/devorangefs-req.c
+++ b/fs/orangefs/devorangefs-req.c
@@ -474,6 +474,7 @@ static ssize_t orangefs_devreq_write_iter(struct kiocb *iocb,
op->downcall.trailer_size, iter)) {
gossip_err("%s: failed to copy trailer.\n", __func__);
vfree(op->downcall.trailer_buf);
+ op->downcall.trailer_buf = NULL;
goto Efault;
}
diff --git a/fs/orangefs/orangefs-debugfs.c b/fs/orangefs/orangefs-debugfs.c
index 69bd73a2b556..9f94919a6bc6 100644
--- a/fs/orangefs/orangefs-debugfs.c
+++ b/fs/orangefs/orangefs-debugfs.c
@@ -569,6 +569,7 @@ static int orangefs_prepare_cdm_array(char *debug_array_string)
cds_delimiter = strchr(cds_head, '\n');
*cds_delimiter = '\0';
+ cds_head = skip_spaces(cds_head);
keyword_len = strcspn(cds_head, " ");
cdm_array[i].keyword = kzalloc(keyword_len + 1, GFP_KERNEL);
diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
index a033743dbf51..a8d47b1e644b 100644
--- a/fs/overlayfs/dir.c
+++ b/fs/overlayfs/dir.c
@@ -188,7 +188,6 @@ struct dentry *ovl_create_real(struct ovl_fs *ofs, struct dentry *parent,
if (!err && ofs->casefold != ovl_dentry_casefolded(newdentry)) {
pr_warn_ratelimited("wrong inherited casefold (%pd2)\n",
newdentry);
- end_creating(newdentry);
err = -EINVAL;
}
break;
diff --git a/fs/pidfs.c b/fs/pidfs.c
index b57ecc96e967..a6a643f15d08 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -532,6 +532,7 @@ static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
struct task_struct *task __free(put_task) = NULL;
struct nsproxy *nsp __free(put_nsproxy) = NULL;
struct ns_common *ns_common = NULL;
+ int error;
if (!pidfs_ioctl_valid(cmd))
return -ENOIOCTLCMD;
@@ -555,20 +556,33 @@ static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
if (arg)
return -EINVAL;
+ /*
+ * We're trying to open a file descriptor to the namespace so perform a
+ * filesystem cred ptrace check. Hold @task's exec_update_lock for the
+ * duration of the ptrace check and the namespace lookup so that the
+ * credentials used for the access decision match those of @task at the
+ * time its namespace is read, preventing a concurrent execve() from
+ * swapping the task's credentials in between the check and the use. We
+ * mirror nsfs behavior.
+ */
+ error = down_read_killable(&task->signal->exec_update_lock);
+ if (error)
+ return error;
+
+ if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) {
+ error = -EACCES;
+ goto out_unlock;
+ }
+
scoped_guard(task_lock, task) {
nsp = task->nsproxy;
if (nsp)
get_nsproxy(nsp);
}
- if (!nsp)
- return -ESRCH; /* just pretend it didn't exist */
-
- /*
- * We're trying to open a file descriptor to the namespace so perform a
- * filesystem cred ptrace check. Also, we mirror nsfs behavior.
- */
- if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS))
- return -EACCES;
+ if (!nsp) {
+ error = -ESRCH; /* just pretend it didn't exist */
+ goto out_unlock;
+ }
switch (cmd) {
/* Namespaces that hang of nsproxy. */
@@ -650,11 +664,16 @@ static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
#endif
break;
default:
- return -ENOIOCTLCMD;
+ error = -ENOIOCTLCMD;
}
- if (!ns_common)
- return -EOPNOTSUPP;
+ if (!error && !ns_common)
+ error = -EOPNOTSUPP;
+
+out_unlock:
+ up_read(&task->signal->exec_update_lock);
+ if (error)
+ return error;
/* open_namespace() unconditionally consumes the reference */
return open_namespace(ns_common);
diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c
index a1dacc7d8f74..cb3a397b109c 100644
--- a/fs/smb/client/cifsfs.c
+++ b/fs/smb/client/cifsfs.c
@@ -1413,8 +1413,19 @@ static loff_t cifs_remap_file_range(struct file *src_file, loff_t off,
*/
lock_two_nondirectories(target_inode, src_inode);
- if (len == 0)
- len = src_inode->i_size - off;
+ if (len == 0) {
+ loff_t src_size = i_size_read(src_inode);
+
+ if (off > src_size) {
+ rc = -EINVAL;
+ goto unlock;
+ }
+ len = src_size - off;
+ if (!len) {
+ rc = 0;
+ goto unlock;
+ }
+ }
cifs_dbg(FYI, "clone range\n");
diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h
index 854e672a4e37..651670c19c2b 100644
--- a/fs/smb/client/cifsfs.h
+++ b/fs/smb/client/cifsfs.h
@@ -147,6 +147,7 @@ ssize_t cifs_file_copychunk_range(unsigned int xid, struct file *src_file,
long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg);
void cifs_setsize(struct inode *inode, loff_t offset);
+void cifs_resize_file_locked(struct inode *inode, loff_t offset);
struct fs_context;
struct smb3_fs_context;
diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c
index 1f77512252e7..f5aad5f61dce 100644
--- a/fs/smb/client/cifssmb.c
+++ b/fs/smb/client/cifssmb.c
@@ -615,6 +615,11 @@ CIFSTCon(const unsigned int xid, struct cifs_ses *ses,
tcon->tid = smb_buffer_response->Tid;
bcc_ptr = pByteArea(smb_buffer_response);
bytes_left = get_bcc(smb_buffer_response);
+ if (bytes_left < 2) {
+ rc = smb_EIO2(smb_eio_trace_tcon_bcc_too_small,
+ bytes_left, 2);
+ goto out;
+ }
length = strnlen(bcc_ptr, bytes_left - 2);
if (smb_buffer->Flags2 & SMBFLG2_UNICODE)
is_unicode = true;
@@ -670,6 +675,7 @@ CIFSTCon(const unsigned int xid, struct cifs_ses *ses,
reset_cifs_unix_caps(xid, tcon, NULL, NULL);
}
}
+out:
cifs_buf_release(smb_buffer);
return rc;
}
diff --git a/fs/smb/client/dfs_cache.c b/fs/smb/client/dfs_cache.c
index 8cd93cd2f00f..86dba25b7a5a 100644
--- a/fs/smb/client/dfs_cache.c
+++ b/fs/smb/client/dfs_cache.c
@@ -122,6 +122,8 @@ static inline void free_tgts(struct cache_entry *ce)
kfree(t->name);
kfree(t);
}
+
+ WRITE_ONCE(ce->tgthint, NULL);
}
static inline void flush_cache_ent(struct cache_entry *ce)
@@ -869,13 +871,22 @@ int dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, const struct nl
goto out_free_path;
}
- if (ref)
- rc = setup_referral(path, ce, ref, get_tgt_name(ce));
- else
+ if (ref) {
+ char *target = get_tgt_name(ce);
+
+ if (IS_ERR(target)) {
+ rc = PTR_ERR(target);
+ goto out_unlock;
+ }
+ rc = setup_referral(path, ce, ref, target);
+ } else {
rc = 0;
+ }
+
if (!rc && tgt_list)
rc = get_targets(ce, tgt_list);
+out_unlock:
up_read(&htable_rw_lock);
out_free_path:
@@ -915,10 +926,17 @@ int dfs_cache_noreq_find(const char *path, struct dfs_info3_param *ref,
goto out_unlock;
}
- if (ref)
- rc = setup_referral(path, ce, ref, get_tgt_name(ce));
- else
+ if (ref) {
+ char *target = get_tgt_name(ce);
+
+ if (IS_ERR(target)) {
+ rc = PTR_ERR(target);
+ goto out_unlock;
+ }
+ rc = setup_referral(path, ce, ref, target);
+ } else {
rc = 0;
+ }
if (!rc && tgt_list)
rc = get_targets(ce, tgt_list);
@@ -959,7 +977,8 @@ void dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt
t = READ_ONCE(ce->tgthint);
- if (unlikely(!strcasecmp(it->it_name, t->name)))
+ /* Check 't' in case ce->tgthint was cleared by free_tgts() */
+ if (t && unlikely(!strcasecmp(it->it_name, t->name)))
goto out_unlock;
list_for_each_entry(t, &ce->tlist, list) {
diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c
index ac89c1ba56b1..389083f9ce00 100644
--- a/fs/smb/client/file.c
+++ b/fs/smb/client/file.c
@@ -1016,6 +1016,7 @@ static int cifs_do_truncate(const unsigned int xid, struct dentry *dentry)
if (!rc) {
netfs_resize_file(&cinode->netfs, 0, true);
cifs_setsize(inode, 0);
+ cifs_invalidate_cache(inode, 0);
}
}
if (cfile)
diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c
index 0afff761aab9..aec8ed8881a6 100644
--- a/fs/smb/client/inode.c
+++ b/fs/smb/client/inode.c
@@ -3057,9 +3057,27 @@ void cifs_setsize(struct inode *inode, loff_t offset)
inode->i_blocks = blocks;
spin_unlock(&inode->i_lock);
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
+ if (offset > old_size)
+ pagecache_isize_extended(inode, old_size, offset);
truncate_pagecache(inode, offset);
netfs_wait_for_outstanding_io(inode);
- fscache_resize_cookie(cifs_inode_cookie(inode), offset);
+}
+
+void cifs_resize_file_locked(struct inode *inode, loff_t offset)
+{
+ struct fscache_cookie *cookie = cifs_inode_cookie(inode);
+
+ lockdep_assert_held_write(&inode->i_rwsem);
+
+ netfs_resize_file(netfs_inode(inode), offset, true);
+ cifs_setsize(inode, offset);
+
+ if (!cookie)
+ return;
+
+ fscache_use_cookie(cookie, true);
+ fscache_resize_cookie(cookie, offset);
+ cifs_fscache_unuse_inode_cookie(inode, true);
}
int cifs_file_set_size(const unsigned int xid, struct dentry *dentry,
@@ -3100,6 +3118,7 @@ int cifs_file_set_size(const unsigned int xid, struct dentry *dentry,
size, false);
cifs_dbg(FYI, "%s: set_file_size: rc = %d\n", __func__, rc);
cifsFileInfo_put(open_file);
+ tcon = NULL;
}
}
@@ -3125,10 +3144,8 @@ int cifs_file_set_size(const unsigned int xid, struct dentry *dentry,
cifs_put_tlink(tlink);
set_size_out:
- if (rc == 0) {
- netfs_resize_file(&cifsInode->netfs, size, true);
- cifs_setsize(inode, size);
- }
+ if (rc == 0)
+ cifs_resize_file_locked(inode, size);
return rc;
}
@@ -3214,9 +3231,13 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs)
attrs->ia_valid &= ~(ATTR_CTIME | ATTR_MTIME);
}
- /* skip mode change if it's just for clearing setuid/setgid */
- if (attrs->ia_valid & (ATTR_KILL_SUID|ATTR_KILL_SGID))
- attrs->ia_valid &= ~ATTR_MODE;
+ /*
+ * This function is only called when Unix extensions are in effect,
+ * so the mode is always sent to and stored on the server. Do not
+ * skip the mode change when clearing setuid/setgid bits: dropping
+ * ATTR_MODE here would leave those bits set on the server after a
+ * write, which is a security issue.
+ */
args = kmalloc_obj(*args);
if (args == NULL) {
@@ -3425,8 +3446,23 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs)
attrs->ia_valid &= ~(ATTR_UID | ATTR_GID);
}
- /* skip mode change if it's just for clearing setuid/setgid */
- if (attrs->ia_valid & (ATTR_KILL_SUID|ATTR_KILL_SGID))
+ /*
+ * Skip the mode change if it is only being done to clear the
+ * setuid/setgid bits *and* the mode is emulated via the DOS
+ * read-only attribute (the default, non-ACL case), which cannot
+ * represent the setuid/setgid bits anyway.
+ *
+ * When the mode is instead stored on the server - i.e. with the
+ * cifsacl or modefromsid mount options (via an ACL) or with the
+ * SMB3.1.1 POSIX extensions - the cleared mode must be pushed to
+ * the server. Dropping ATTR_MODE here would leave the setuid/
+ * setgid bit set on the server after a write, which is a security
+ * issue (the bits are not stripped as they are on local
+ * filesystems).
+ */
+ if ((attrs->ia_valid & (ATTR_KILL_SUID|ATTR_KILL_SGID)) &&
+ !((sbflags & (CIFS_MOUNT_CIFS_ACL | CIFS_MOUNT_MODE_FROM_SID)) ||
+ cifs_sb_master_tcon(cifs_sb)->posix_extensions))
attrs->ia_valid &= ~ATTR_MODE;
if (attrs->ia_valid & ATTR_MODE) {
diff --git a/fs/smb/client/smb1misc.c b/fs/smb/client/smb1misc.c
index ba56023010d8..cdfbbff24b72 100644
--- a/fs/smb/client/smb1misc.c
+++ b/fs/smb/client/smb1misc.c
@@ -80,7 +80,8 @@ is_valid_oplock_break(char *buffer, struct TCP_Server_Info *srv)
(struct smb_com_transaction_change_notify_rsp *)buf;
struct file_notify_information *pnotify;
__u32 data_offset = 0;
- size_t len = srv->total_read - srv->pdu_size;
+ /* total_read excludes the RFC1002 preamble */
+ size_t len = srv->total_read;
if (get_bcc(buf) > sizeof(struct file_notify_information)) {
data_offset = le32_to_cpu(pSMBr->DataOffset);
diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c
index dc5a8c1da623..954c1a8ef724 100644
--- a/fs/smb/client/smb1ops.c
+++ b/fs/smb/client/smb1ops.c
@@ -717,7 +717,7 @@ static int cifs_query_path_info(const unsigned int xid,
ea->ea_value_length = cpu_to_le16(SMB2_WSL_XATTR_DEV_SIZE);
memcpy(&ea->ea_data[0], SMB2_WSL_XATTR_DEV, SMB2_WSL_XATTR_NAME_LEN + 1);
data->wsl.eas_len += ALIGN(sizeof(*ea) + SMB2_WSL_XATTR_NAME_LEN + 1 +
- SMB2_WSL_XATTR_MODE_SIZE, 4);
+ SMB2_WSL_XATTR_DEV_SIZE, 4);
rc = 0;
} else if (rc >= 0) {
/* It is an error if EA $LXDEV has wrong size. */
diff --git a/fs/smb/client/smb1transport.c b/fs/smb/client/smb1transport.c
index 966f2cf83a51..42e95cc1bd89 100644
--- a/fs/smb/client/smb1transport.c
+++ b/fs/smb/client/smb1transport.c
@@ -375,12 +375,31 @@ coalesce_t2(char *second_buf, struct smb_hdr *target_hdr, unsigned int *pdu_len)
data_area_of_tgt = (char *)&pSMBt->hdr.Protocol +
get_unaligned_le16(&pSMBt->t2_rsp.DataOffset);
- /* validate target area */
data_area_of_src = (char *)&pSMBs->hdr.Protocol +
get_unaligned_le16(&pSMBs->t2_rsp.DataOffset);
data_area_of_tgt += total_in_tgt;
+ /*
+ * DataOffset fields are server-supplied and not validated against
+ * buffer bounds; check both data pointers before mutating the
+ * target header.
+ */
+ if (data_area_of_tgt < (char *)target_hdr +
+ sizeof(struct smb_t2_rsp) + sizeof(__le16) ||
+ data_area_of_tgt + total_in_src >
+ (char *)target_hdr + CIFSMaxBufSize + MAX_CIFS_HDR_SIZE) {
+ cifs_dbg(VFS, "%s: target data area out of bounds\n", __func__);
+ return -EPROTO;
+ }
+ if (data_area_of_src < second_buf +
+ sizeof(struct smb_t2_rsp) + sizeof(__le16) ||
+ data_area_of_src + total_in_src >
+ second_buf + smbCalcSize((struct smb_hdr *)second_buf)) {
+ cifs_dbg(VFS, "%s: secondary data area out of bounds\n", __func__);
+ return -EPROTO;
+ }
+
total_in_tgt += total_in_src;
/* is the result too big for the field? */
if (total_in_tgt > USHRT_MAX) {
@@ -430,10 +449,18 @@ bool
cifs_check_trans2(struct mid_q_entry *mid, struct TCP_Server_Info *server,
char *buf, int malformed)
{
- if (malformed)
- return false;
- if (check2ndT2(buf) <= 0)
+ if (malformed || check2ndT2(buf) <= 0) {
+ /* mid->multiRsp blocks the server buf detach in handle_mid();
+ * returning false here would leak resp_buf and leave a dangling
+ * server->smallbuf/bigbuf after the user thread frees resp_buf.
+ */
+ if (mid->multiRsp) {
+ mid->multiEnd = true;
+ dequeue_mid(server, mid, true);
+ return true;
+ }
return false;
+ }
mid->multiRsp = true;
if (mid->resp_buf) {
/* merge response - fix up 1st*/
diff --git a/fs/smb/client/smb2file.c b/fs/smb/client/smb2file.c
index f35b6488d810..fb2fccbe8667 100644
--- a/fs/smb/client/smb2file.c
+++ b/fs/smb/client/smb2file.c
@@ -61,7 +61,10 @@ static struct smb2_symlink_err_rsp *symlink_data(const struct kvec *iov)
cifs_dbg(FYI, "%s: skipping unhandled error context: 0x%x\n",
__func__, le32_to_cpu(p->ErrorId));
- len = ALIGN(le32_to_cpu(p->ErrorDataLength), 8);
+ len = le32_to_cpu(p->ErrorDataLength);
+ if (len > end - ((u8 *)p + sizeof(*p)))
+ return ERR_PTR(-EINVAL);
+ len = ALIGN(len, 8);
if (len > end - ((u8 *)p + sizeof(*p)))
return ERR_PTR(-EINVAL);
diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c
index 213bc298cdf2..2946391bb992 100644
--- a/fs/smb/client/smb2inode.c
+++ b/fs/smb/client/smb2inode.c
@@ -40,9 +40,11 @@ static struct reparse_data_buffer *reparse_buf_ptr(struct kvec *iov)
buf = (struct reparse_data_buffer *)((u8 *)io + off);
len = sizeof(*buf);
- rdlen = le16_to_cpu(buf->ReparseDataLength);
+ if (count < len)
+ return ERR_PTR(smb_EIO2(smb_eio_trace_reparse_rdlen, count, 0));
- if (count < len || count < rdlen + len)
+ rdlen = le16_to_cpu(buf->ReparseDataLength);
+ if (count < rdlen + len)
return ERR_PTR(smb_EIO2(smb_eio_trace_reparse_rdlen, count, rdlen));
return buf;
}
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index 192649fec25d..0e872d58fae7 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -2222,8 +2222,7 @@ smb2_duplicate_extents(const unsigned int xid,
rc = smb2_set_file_size(xid, tcon, trgtfile, dest_off + len, false);
if (rc)
goto duplicate_extents_out;
- netfs_resize_file(netfs_inode(inode), dest_off + len, true);
- cifs_setsize(inode, dest_off + len);
+ cifs_resize_file_locked(inode, dest_off + len);
}
rc = SMB2_ioctl(xid, tcon, trgtfile->fid.persistent_fid,
trgtfile->fid.volatile_fid,
@@ -3776,8 +3775,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
}
new_eof = off + len;
- netfs_resize_file(&cifsi->netfs, new_eof, true);
- cifs_setsize(inode, new_eof);
+ cifs_resize_file_locked(inode, new_eof);
qrc = SMB2_query_info(xid, tcon,
cfile->fid.persistent_fid,
@@ -3825,8 +3823,7 @@ static long smb3_simple_falloc(struct file *file, struct cifs_tcon *tcon,
if (rc)
goto out;
- netfs_resize_file(&cifsi->netfs, new_eof, true);
- cifs_setsize(inode, new_eof);
+ cifs_resize_file_locked(inode, new_eof);
qrc = SMB2_query_info(xid, tcon,
cfile->fid.persistent_fid,
diff --git a/fs/smb/client/trace.h b/fs/smb/client/trace.h
index 5b21ad3c15fb..12241abb8e2e 100644
--- a/fs/smb/client/trace.h
+++ b/fs/smb/client/trace.h
@@ -133,6 +133,7 @@
EM(smb_eio_trace_sym_slash, "sym_slash") \
EM(smb_eio_trace_sym_target_len, "sym_target_len") \
EM(smb_eio_trace_symlink_file_size, "symlink_file_size") \
+ EM(smb_eio_trace_tcon_bcc_too_small, "tcon_bcc_too_small") \
EM(smb_eio_trace_tdis_in_reconnect, "tdis_in_reconnect") \
EM(smb_eio_trace_tx_chained_async, "tx_chained_async") \
EM(smb_eio_trace_tx_compress_failed, "tx_compress_failed") \
@@ -213,7 +214,7 @@
#define EM(a, b) a,
#define E_(a, b) a
-enum smb_eio_trace { smb_eio_traces } __mode(byte);
+enum smb_eio_trace { smb_eio_traces };
enum smb3_rw_credits_trace { smb3_rw_credits_traces } __mode(byte);
enum smb3_tcon_ref_trace { smb3_tcon_ref_traces } __mode(byte);
diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index a141025581af..9bd8dce660af 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -1722,7 +1722,7 @@ int ksmbd_reopen_durable_fd(struct ksmbd_work *work, struct ksmbd_file *fp)
down_write(&ci->m_lock);
list_for_each_entry_rcu(op, &ci->m_op_list, op_entry,
lockdep_is_held(&ci->m_lock)) {
- if (op->conn)
+ if (op->conn || op->o_fp != fp)
continue;
op->conn = ksmbd_conn_get(fp->conn);
op->sess = work->sess;
diff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c
index a52458435327..9ce620a31016 100644
--- a/fs/tracefs/event_inode.c
+++ b/fs/tracefs/event_inode.c
@@ -438,6 +438,8 @@ static inline struct eventfs_inode *init_ei(struct eventfs_inode *ei, const char
if (!ei->name)
return NULL;
kref_init(&ei->kref);
+ INIT_LIST_HEAD(&ei->children);
+ INIT_LIST_HEAD(&ei->list);
return ei;
}
@@ -725,8 +727,6 @@ struct eventfs_inode *eventfs_create_dir(const char *name, struct eventfs_inode
ei->entries = entries;
ei->nr_entries = size;
ei->data = data;
- INIT_LIST_HEAD(&ei->children);
- INIT_LIST_HEAD(&ei->list);
scoped_guard(mutex, &eventfs_mutex) {
if (!parent->is_freed)
@@ -798,9 +798,6 @@ struct eventfs_inode *eventfs_create_events_dir(const char *name, struct dentry
ei->attr.uid = uid;
ei->attr.gid = gid;
- INIT_LIST_HEAD(&ei->children);
- INIT_LIST_HEAD(&ei->list);
-
ti = get_tracefs(inode);
ti->flags |= TRACEFS_EVENT_INODE;
ti->private = ei;
diff --git a/fs/ubifs/auth.c b/fs/ubifs/auth.c
index a4a0158f712d..1f770795ee70 100644
--- a/fs/ubifs/auth.c
+++ b/fs/ubifs/auth.c
@@ -217,7 +217,7 @@ int ubifs_sb_verify_signature(struct ubifs_info *c,
signode = snod->node;
- if (le32_to_cpu(signode->len) > snod->len + sizeof(struct ubifs_sig_node)) {
+ if (le32_to_cpu(signode->len) > snod->len - sizeof(struct ubifs_sig_node)) {
ubifs_err(c, "invalid signature len %d", le32_to_cpu(signode->len));
err = -EINVAL;
goto out_destroy;
diff --git a/fs/udf/inode.c b/fs/udf/inode.c
index 67bcf83758c8..c519644acfd7 100644
--- a/fs/udf/inode.c
+++ b/fs/udf/inode.c
@@ -531,7 +531,7 @@ static int udf_do_extend_file(struct inode *inode,
sb->s_blocksize - 1) & ~(sb->s_blocksize - 1));
iinfo->i_lenExtents =
(iinfo->i_lenExtents + sb->s_blocksize - 1) &
- ~(sb->s_blocksize - 1);
+ ~((u64)sb->s_blocksize - 1);
}
add = 0;
diff --git a/fs/udf/partition.c b/fs/udf/partition.c
index 2b85c9501bed..ad8dcedca263 100644
--- a/fs/udf/partition.c
+++ b/fs/udf/partition.c
@@ -55,7 +55,7 @@ uint32_t udf_get_pblock_virt15(struct super_block *sb, uint32_t block,
map = &sbi->s_partmaps[partition];
vdata = &map->s_type_specific.s_virtual;
- if (block > vdata->s_num_entries) {
+ if (block >= vdata->s_num_entries) {
udf_debug("Trying to access block beyond end of VAT (%u max %u)\n",
block, vdata->s_num_entries);
return 0xFFFFFFFF;