While randstruct was satisfied with using an open-coded "void *" offset
cast for the netfs_i_context <-> inode casting, __builtin_object_size() as
used by FORTIFY_SOURCE was not as easily fooled.  This was causing the
following complaint[1] from gcc v12:
  In file included from include/linux/string.h:253,
                   from include/linux/ceph/ceph_debug.h:7,
                   from fs/ceph/inode.c:2:
  In function 'fortify_memset_chk',
      inlined from 'netfs_i_context_init' at include/linux/netfs.h:326:2,
      inlined from 'ceph_alloc_inode' at fs/ceph/inode.c:463:2:
  include/linux/fortify-string.h:242:25: warning: call to '__write_overflow_field' declared with attribute warning: detected write beyond size of field (1st parameter); maybe use struct_group()? [-Wattribute-warning]
    242 |                         __write_overflow_field(p_size_field, size);
        |                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Fix this by embedding a struct inode into struct netfs_i_context (which
should perhaps be renamed to struct netfs_inode).  The struct inode
vfs_inode fields are then removed from the 9p, afs, ceph and cifs inode
structs and vfs_inode is then simply changed to "netfs.inode" in those
filesystems.
Further, rename netfs_i_context to netfs_inode, get rid of the
netfs_inode() function that converted a netfs_i_context pointer to an
inode pointer (that can now be done with &ctx->inode) and rename the
netfs_i_context() function to netfs_inode() (which is now a wrapper
around container_of()).
Most of the changes were done with:
  perl -p -i -e 's/vfs_inode/netfs.inode/'g \
        `git grep -l 'vfs_inode' -- fs/{9p,afs,ceph,cifs}/*.[ch]`
Kees suggested doing it with a pair structure[2] and a special
declarator to insert that into the network filesystem's inode
wrapper[3], but I think it's cleaner to embed it - and then it doesn't
matter if struct randomisation reorders things.
Dave Chinner suggested using a filesystem-specific VFS_I() function in
each filesystem to convert that filesystem's own inode wrapper struct
into the VFS inode struct[4].
Version #2:
 - Fix a couple of missed name changes due to a disabled cifs option.
 - Rename nfs_i_context to nfs_inode
 - Use "netfs" instead of "nic" as the member name in per-fs inode wrapper
   structs.
[ This also undoes commit 507160f46c ("netfs: gcc-12: temporarily
  disable '-Wattribute-warning' for now") that is no longer needed ]
Fixes: bc899ee1c8 ("netfs: Add a netfs inode context")
Reported-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: David Howells <dhowells@redhat.com>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Reviewed-by: Kees Cook <keescook@chromium.org>
Reviewed-by: Xiubo Li <xiubli@redhat.com>
cc: Jonathan Corbet <corbet@lwn.net>
cc: Eric Van Hensbergen <ericvh@gmail.com>
cc: Latchesar Ionkov <lucho@ionkov.net>
cc: Dominique Martinet <asmadeus@codewreck.org>
cc: Christian Schoenebeck <linux_oss@crudebyte.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: Ilya Dryomov <idryomov@gmail.com>
cc: Steve French <smfrench@gmail.com>
cc: William Kucharski <william.kucharski@oracle.com>
cc: "Matthew Wilcox (Oracle)" <willy@infradead.org>
cc: Dave Chinner <david@fromorbit.com>
cc: linux-doc@vger.kernel.org
cc: v9fs-developer@lists.sourceforge.net
cc: linux-afs@lists.infradead.org
cc: ceph-devel@vger.kernel.org
cc: linux-cifs@vger.kernel.org
cc: samba-technical@lists.samba.org
cc: linux-fsdevel@vger.kernel.org
cc: linux-hardening@vger.kernel.org
Link: https://lore.kernel.org/r/d2ad3a3d7bdd794c6efb562d2f2b655fb67756b9.camel@kernel.org/ [1]
Link: https://lore.kernel.org/r/20220517210230.864239-1-keescook@chromium.org/ [2]
Link: https://lore.kernel.org/r/20220518202212.2322058-1-keescook@chromium.org/ [3]
Link: https://lore.kernel.org/r/20220524101205.GI2306852@dread.disaster.area/ [4]
Link: https://lore.kernel.org/r/165296786831.3591209.12111293034669289733.stgit@warthog.procyon.org.uk/ # v1
Link: https://lore.kernel.org/r/165305805651.4094995.7763502506786714216.stgit@warthog.procyon.org.uk # v2
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
		
	
		
			
				
	
	
		
			283 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			283 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0-or-later
 | |
| /* AFS silly rename handling
 | |
|  *
 | |
|  * Copyright (C) 2019 Red Hat, Inc. All Rights Reserved.
 | |
|  * Written by David Howells (dhowells@redhat.com)
 | |
|  * - Derived from NFS's sillyrename.
 | |
|  */
 | |
| 
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/fs.h>
 | |
| #include <linux/namei.h>
 | |
| #include <linux/fsnotify.h>
 | |
| #include "internal.h"
 | |
| 
 | |
| static void afs_silly_rename_success(struct afs_operation *op)
 | |
| {
 | |
| 	_enter("op=%08x", op->debug_id);
 | |
| 
 | |
| 	afs_check_dir_conflict(op, &op->file[0]);
 | |
| 	afs_vnode_commit_status(op, &op->file[0]);
 | |
| }
 | |
| 
 | |
| static void afs_silly_rename_edit_dir(struct afs_operation *op)
 | |
| {
 | |
| 	struct afs_vnode_param *dvp = &op->file[0];
 | |
| 	struct afs_vnode *dvnode = dvp->vnode;
 | |
| 	struct afs_vnode *vnode = AFS_FS_I(d_inode(op->dentry));
 | |
| 	struct dentry *old = op->dentry;
 | |
| 	struct dentry *new = op->dentry_2;
 | |
| 
 | |
| 	spin_lock(&old->d_lock);
 | |
| 	old->d_flags |= DCACHE_NFSFS_RENAMED;
 | |
| 	spin_unlock(&old->d_lock);
 | |
| 	if (dvnode->silly_key != op->key) {
 | |
| 		key_put(dvnode->silly_key);
 | |
| 		dvnode->silly_key = key_get(op->key);
 | |
| 	}
 | |
| 
 | |
| 	down_write(&dvnode->validate_lock);
 | |
| 	if (test_bit(AFS_VNODE_DIR_VALID, &dvnode->flags) &&
 | |
| 	    dvnode->status.data_version == dvp->dv_before + dvp->dv_delta) {
 | |
| 		afs_edit_dir_remove(dvnode, &old->d_name,
 | |
| 				    afs_edit_dir_for_silly_0);
 | |
| 		afs_edit_dir_add(dvnode, &new->d_name,
 | |
| 				 &vnode->fid, afs_edit_dir_for_silly_1);
 | |
| 	}
 | |
| 	up_write(&dvnode->validate_lock);
 | |
| }
 | |
| 
 | |
| static const struct afs_operation_ops afs_silly_rename_operation = {
 | |
| 	.issue_afs_rpc	= afs_fs_rename,
 | |
| 	.issue_yfs_rpc	= yfs_fs_rename,
 | |
| 	.success	= afs_silly_rename_success,
 | |
| 	.edit_dir	= afs_silly_rename_edit_dir,
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * Actually perform the silly rename step.
 | |
|  */
 | |
| static int afs_do_silly_rename(struct afs_vnode *dvnode, struct afs_vnode *vnode,
 | |
| 			       struct dentry *old, struct dentry *new,
 | |
| 			       struct key *key)
 | |
| {
 | |
| 	struct afs_operation *op;
 | |
| 
 | |
| 	_enter("%pd,%pd", old, new);
 | |
| 
 | |
| 	op = afs_alloc_operation(key, dvnode->volume);
 | |
| 	if (IS_ERR(op))
 | |
| 		return PTR_ERR(op);
 | |
| 
 | |
| 	afs_op_set_vnode(op, 0, dvnode);
 | |
| 	afs_op_set_vnode(op, 1, dvnode);
 | |
| 	op->file[0].dv_delta = 1;
 | |
| 	op->file[1].dv_delta = 1;
 | |
| 	op->file[0].modification = true;
 | |
| 	op->file[1].modification = true;
 | |
| 	op->file[0].update_ctime = true;
 | |
| 	op->file[1].update_ctime = true;
 | |
| 
 | |
| 	op->dentry		= old;
 | |
| 	op->dentry_2		= new;
 | |
| 	op->ops			= &afs_silly_rename_operation;
 | |
| 
 | |
| 	trace_afs_silly_rename(vnode, false);
 | |
| 	return afs_do_sync_operation(op);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Perform silly-rename of a dentry.
 | |
|  *
 | |
|  * AFS is stateless and the server doesn't know when the client is holding a
 | |
|  * file open.  To prevent application problems when a file is unlinked while
 | |
|  * it's still open, the client performs a "silly-rename".  That is, it renames
 | |
|  * the file to a hidden file in the same directory, and only performs the
 | |
|  * unlink once the last reference to it is put.
 | |
|  *
 | |
|  * The final cleanup is done during dentry_iput.
 | |
|  */
 | |
| int afs_sillyrename(struct afs_vnode *dvnode, struct afs_vnode *vnode,
 | |
| 		    struct dentry *dentry, struct key *key)
 | |
| {
 | |
| 	static unsigned int sillycounter;
 | |
| 	struct dentry *sdentry = NULL;
 | |
| 	unsigned char silly[16];
 | |
| 	int ret = -EBUSY;
 | |
| 
 | |
| 	_enter("");
 | |
| 
 | |
| 	/* We don't allow a dentry to be silly-renamed twice. */
 | |
| 	if (dentry->d_flags & DCACHE_NFSFS_RENAMED)
 | |
| 		return -EBUSY;
 | |
| 
 | |
| 	sdentry = NULL;
 | |
| 	do {
 | |
| 		int slen;
 | |
| 
 | |
| 		dput(sdentry);
 | |
| 		sillycounter++;
 | |
| 
 | |
| 		/* Create a silly name.  Note that the ".__afs" prefix is
 | |
| 		 * understood by the salvager and must not be changed.
 | |
| 		 */
 | |
| 		slen = scnprintf(silly, sizeof(silly), ".__afs%04X", sillycounter);
 | |
| 		sdentry = lookup_one_len(silly, dentry->d_parent, slen);
 | |
| 
 | |
| 		/* N.B. Better to return EBUSY here ... it could be dangerous
 | |
| 		 * to delete the file while it's in use.
 | |
| 		 */
 | |
| 		if (IS_ERR(sdentry))
 | |
| 			goto out;
 | |
| 	} while (!d_is_negative(sdentry));
 | |
| 
 | |
| 	ihold(&vnode->netfs.inode);
 | |
| 
 | |
| 	ret = afs_do_silly_rename(dvnode, vnode, dentry, sdentry, key);
 | |
| 	switch (ret) {
 | |
| 	case 0:
 | |
| 		/* The rename succeeded. */
 | |
| 		set_bit(AFS_VNODE_SILLY_DELETED, &vnode->flags);
 | |
| 		d_move(dentry, sdentry);
 | |
| 		break;
 | |
| 	case -ERESTARTSYS:
 | |
| 		/* The result of the rename is unknown. Play it safe by forcing
 | |
| 		 * a new lookup.
 | |
| 		 */
 | |
| 		d_drop(dentry);
 | |
| 		d_drop(sdentry);
 | |
| 	}
 | |
| 
 | |
| 	iput(&vnode->netfs.inode);
 | |
| 	dput(sdentry);
 | |
| out:
 | |
| 	_leave(" = %d", ret);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static void afs_silly_unlink_success(struct afs_operation *op)
 | |
| {
 | |
| 	_enter("op=%08x", op->debug_id);
 | |
| 	afs_check_dir_conflict(op, &op->file[0]);
 | |
| 	afs_vnode_commit_status(op, &op->file[0]);
 | |
| 	afs_vnode_commit_status(op, &op->file[1]);
 | |
| 	afs_update_dentry_version(op, &op->file[0], op->dentry);
 | |
| }
 | |
| 
 | |
| static void afs_silly_unlink_edit_dir(struct afs_operation *op)
 | |
| {
 | |
| 	struct afs_vnode_param *dvp = &op->file[0];
 | |
| 	struct afs_vnode *dvnode = dvp->vnode;
 | |
| 
 | |
| 	_enter("op=%08x", op->debug_id);
 | |
| 	down_write(&dvnode->validate_lock);
 | |
| 	if (test_bit(AFS_VNODE_DIR_VALID, &dvnode->flags) &&
 | |
| 	    dvnode->status.data_version == dvp->dv_before + dvp->dv_delta)
 | |
| 		afs_edit_dir_remove(dvnode, &op->dentry->d_name,
 | |
| 				    afs_edit_dir_for_unlink);
 | |
| 	up_write(&dvnode->validate_lock);
 | |
| }
 | |
| 
 | |
| static const struct afs_operation_ops afs_silly_unlink_operation = {
 | |
| 	.issue_afs_rpc	= afs_fs_remove_file,
 | |
| 	.issue_yfs_rpc	= yfs_fs_remove_file,
 | |
| 	.success	= afs_silly_unlink_success,
 | |
| 	.aborted	= afs_check_for_remote_deletion,
 | |
| 	.edit_dir	= afs_silly_unlink_edit_dir,
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * Tell the server to remove a sillyrename file.
 | |
|  */
 | |
| static int afs_do_silly_unlink(struct afs_vnode *dvnode, struct afs_vnode *vnode,
 | |
| 			       struct dentry *dentry, struct key *key)
 | |
| {
 | |
| 	struct afs_operation *op;
 | |
| 
 | |
| 	_enter("");
 | |
| 
 | |
| 	op = afs_alloc_operation(NULL, dvnode->volume);
 | |
| 	if (IS_ERR(op))
 | |
| 		return PTR_ERR(op);
 | |
| 
 | |
| 	afs_op_set_vnode(op, 0, dvnode);
 | |
| 	afs_op_set_vnode(op, 1, vnode);
 | |
| 	op->file[0].dv_delta = 1;
 | |
| 	op->file[0].modification = true;
 | |
| 	op->file[0].update_ctime = true;
 | |
| 	op->file[1].op_unlinked = true;
 | |
| 	op->file[1].update_ctime = true;
 | |
| 
 | |
| 	op->dentry	= dentry;
 | |
| 	op->ops		= &afs_silly_unlink_operation;
 | |
| 
 | |
| 	trace_afs_silly_rename(vnode, true);
 | |
| 	afs_begin_vnode_operation(op);
 | |
| 	afs_wait_for_operation(op);
 | |
| 
 | |
| 	/* If there was a conflict with a third party, check the status of the
 | |
| 	 * unlinked vnode.
 | |
| 	 */
 | |
| 	if (op->error == 0 && (op->flags & AFS_OPERATION_DIR_CONFLICT)) {
 | |
| 		op->file[1].update_ctime = false;
 | |
| 		op->fetch_status.which = 1;
 | |
| 		op->ops = &afs_fetch_status_operation;
 | |
| 		afs_begin_vnode_operation(op);
 | |
| 		afs_wait_for_operation(op);
 | |
| 	}
 | |
| 
 | |
| 	return afs_put_operation(op);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Remove sillyrename file on iput.
 | |
|  */
 | |
| int afs_silly_iput(struct dentry *dentry, struct inode *inode)
 | |
| {
 | |
| 	struct afs_vnode *dvnode = AFS_FS_I(d_inode(dentry->d_parent));
 | |
| 	struct afs_vnode *vnode = AFS_FS_I(inode);
 | |
| 	struct dentry *alias;
 | |
| 	int ret;
 | |
| 
 | |
| 	DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq);
 | |
| 
 | |
| 	_enter("%p{%pd},%llx", dentry, dentry, vnode->fid.vnode);
 | |
| 
 | |
| 	down_read(&dvnode->rmdir_lock);
 | |
| 
 | |
| 	alias = d_alloc_parallel(dentry->d_parent, &dentry->d_name, &wq);
 | |
| 	if (IS_ERR(alias)) {
 | |
| 		up_read(&dvnode->rmdir_lock);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	if (!d_in_lookup(alias)) {
 | |
| 		/* We raced with lookup...  See if we need to transfer the
 | |
| 		 * sillyrename information to the aliased dentry.
 | |
| 		 */
 | |
| 		ret = 0;
 | |
| 		spin_lock(&alias->d_lock);
 | |
| 		if (d_really_is_positive(alias) &&
 | |
| 		    !(alias->d_flags & DCACHE_NFSFS_RENAMED)) {
 | |
| 			alias->d_flags |= DCACHE_NFSFS_RENAMED;
 | |
| 			ret = 1;
 | |
| 		}
 | |
| 		spin_unlock(&alias->d_lock);
 | |
| 		up_read(&dvnode->rmdir_lock);
 | |
| 		dput(alias);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	/* Stop lock-release from complaining. */
 | |
| 	spin_lock(&vnode->lock);
 | |
| 	vnode->lock_state = AFS_VNODE_LOCK_DELETED;
 | |
| 	trace_afs_flock_ev(vnode, NULL, afs_flock_silly_delete, 0);
 | |
| 	spin_unlock(&vnode->lock);
 | |
| 
 | |
| 	afs_do_silly_unlink(dvnode, vnode, dentry, dvnode->silly_key);
 | |
| 	up_read(&dvnode->rmdir_lock);
 | |
| 	d_lookup_done(alias);
 | |
| 	dput(alias);
 | |
| 	return 1;
 | |
| }
 |