Pass a the inode explicit as an argument, as dentry->d_inode is not stable during RCU-walk, and also a new 'flags' argument which may (after further patches) contain LOOKUP_RCU. ->follow_link methods which cannot complete atomically must return -ECHILD when LOOKUP_RCU is set. Those which can complete atomically must use 'inode' rather than 'dentry->d_inode', and must only reference data structures that are freed using rcu_free(). Later patches will make some of these handle LOOKUP_RCU more gracefully. Signed-off-by: NeilBrown <neilb@xxxxxxx> --- Documentation/filesystems/Locking | 2 +- Documentation/filesystems/porting | 9 +++++++++ Documentation/filesystems/vfs.txt | 2 +- drivers/staging/lustre/lustre/llite/symlink.c | 7 +++++-- fs/9p/vfs_inode.c | 11 +++++++++-- fs/9p/vfs_inode_dotl.c | 10 ++++++++-- fs/autofs4/symlink.c | 6 ++++-- fs/befs/linuxvfs.c | 15 +++++++++------ fs/ceph/inode.c | 5 +++-- fs/cifs/cifsfs.h | 3 ++- fs/cifs/link.c | 5 +++-- fs/configfs/symlink.c | 10 ++++++++-- fs/debugfs/file.c | 5 +++-- fs/ecryptfs/inode.c | 12 +++++++++--- fs/exofs/symlink.c | 6 +++--- fs/ext2/symlink.c | 5 +++-- fs/ext3/symlink.c | 5 +++-- fs/ext4/symlink.c | 5 +++-- fs/freevxfs/vxfs_immed.c | 9 ++++++--- fs/fuse/dir.c | 5 ++++- fs/gfs2/inode.c | 9 +++++++-- fs/hostfs/hostfs_kern.c | 10 ++++++++-- fs/hppfs/hppfs.c | 9 ++++++--- fs/jffs2/symlink.c | 8 +++++--- fs/jfs/symlink.c | 5 +++-- fs/kernfs/symlink.c | 10 ++++++++-- fs/namei.c | 14 ++++++++++---- fs/nfs/symlink.c | 6 ++++-- fs/overlayfs/inode.c | 8 ++++++-- fs/proc/base.c | 6 ++++-- fs/proc/inode.c | 5 +++-- fs/proc/namespaces.c | 7 +++++-- fs/proc/self.c | 10 ++++++++-- fs/proc/thread_self.c | 13 ++++++++++--- fs/sysv/symlink.c | 5 +++-- fs/ubifs/file.c | 5 +++-- fs/ufs/symlink.c | 6 ++++-- fs/xfs/xfs_iops.c | 8 ++++++-- include/linux/fs.h | 4 ++-- mm/shmem.c | 14 ++++++++++---- 40 files changed, 211 insertions(+), 88 deletions(-) diff --git a/Documentation/filesystems/Locking b/Documentation/filesystems/Locking index bbce4914d209..c0289bae848f 100644 --- a/Documentation/filesystems/Locking +++ b/Documentation/filesystems/Locking @@ -50,7 +50,7 @@ prototypes: int (*rename2) (struct inode *, struct dentry *, struct inode *, struct dentry *, unsigned int); int (*readlink) (struct dentry *, char __user *,int); - void * (*follow_link) (struct dentry *); + void * (*follow_link) (struct dentry *, struct inode *, int); void (*put_link) (struct dentry *, char *, void *); void (*truncate) (struct inode *); int (*permission) (struct inode *, int, unsigned int); diff --git a/Documentation/filesystems/porting b/Documentation/filesystems/porting index 9996b4631a87..eba8dd0a13e3 100644 --- a/Documentation/filesystems/porting +++ b/Documentation/filesystems/porting @@ -481,3 +481,12 @@ in your dentry operations instead. ->follow_link() no longer receives 'struct nameidata *'. The nd is now attached to 'current' and nd_set_link() accesses it directly. +-- +[mandatory] + ->follow_link now receives 'struct inode *' and 'int flags' which + may contain LOOKUP_RCU. In this case -ECHILD must be + returned if the operation cannot be completed under + rcu_read_lock() conditions. + The passed inode must be used rather than dentry->d_inode, + particularly if LOOKUP_RCU is set. + If s_fs_info is used, it must be freed using RCU. diff --git a/Documentation/filesystems/vfs.txt b/Documentation/filesystems/vfs.txt index 11aac530931b..5557e9283d04 100644 --- a/Documentation/filesystems/vfs.txt +++ b/Documentation/filesystems/vfs.txt @@ -350,7 +350,7 @@ struct inode_operations { int (*rename2) (struct inode *, struct dentry *, struct inode *, struct dentry *, unsigned int); int (*readlink) (struct dentry *, char __user *,int); - void * (*follow_link) (struct dentry *); + void * (*follow_link) (struct dentry *, struct inode *, int); void (*put_link) (struct dentry *, char *, void *); int (*permission) (struct inode *, int); int (*get_acl)(struct inode *, int); diff --git a/drivers/staging/lustre/lustre/llite/symlink.c b/drivers/staging/lustre/lustre/llite/symlink.c index 63dd1a925c92..44d095c68ce7 100644 --- a/drivers/staging/lustre/lustre/llite/symlink.c +++ b/drivers/staging/lustre/lustre/llite/symlink.c @@ -118,14 +118,17 @@ failed: return rc; } -static void *ll_follow_link(struct dentry *dentry) +static void *ll_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { unsigned long avail_space; - struct inode *inode = dentry->d_inode; struct ptlrpc_request *request = NULL; int rc; char *symname = NULL; + if (flags & LOOKUP_RCU) + return ERR_PTR(-ECHILD); + CDEBUG(D_VFSTRACE, "VFS Op\n"); /* Limit the recursive symlink depth. * Previously limited to 5 instead of default 8 links when diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index ebf50c3e132c..112091a186a1 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -1274,13 +1274,20 @@ done: /** * v9fs_vfs_follow_link - follow a symlink path * @dentry: dentry for symlink + * @inode: inode for the symlink + * @flags: lookup flags * */ -static void *v9fs_vfs_follow_link(struct dentry *dentry) +static void *v9fs_vfs_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { int len = 0; - char *link = __getname(); + char *link; + + if (flags & LOOKUP_RCU) + return ERR_PTR(-ECHILD); + link = __getname(); p9_debug(P9_DEBUG_VFS, "%pd\n", dentry); diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c index dc35156aea6a..3971e265f788 100644 --- a/fs/9p/vfs_inode_dotl.c +++ b/fs/9p/vfs_inode_dotl.c @@ -905,17 +905,23 @@ error: /** * v9fs_vfs_follow_link_dotl - follow a symlink path * @dentry: dentry for symlink + * @inode: inode for symlink + * @flags: lookup flags * */ static void * -v9fs_vfs_follow_link_dotl(struct dentry *dentry) +v9fs_vfs_follow_link_dotl(struct dentry *dentry, struct inode *inode, + int flags) { int retval; struct p9_fid *fid; - char *link = __getname(); + char *link; char *target; + if (flags & LOOKUP_RCU) + return ERR_PTR(-ECHILD); + link = __getname(); p9_debug(P9_DEBUG_VFS, "%pd\n", dentry); if (!link) { diff --git a/fs/autofs4/symlink.c b/fs/autofs4/symlink.c index 37b4b561faa3..e87885a6ef4e 100644 --- a/fs/autofs4/symlink.c +++ b/fs/autofs4/symlink.c @@ -12,13 +12,15 @@ #include "autofs_i.h" -static void *autofs4_follow_link(struct dentry *dentry) +static void *autofs4_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb); struct autofs_info *ino = autofs4_dentry_ino(dentry); + if (ino && !autofs4_oz_mode(sbi)) ino->last_used = jiffies; - nd_set_link(dentry->d_inode->i_private); + nd_set_link(inode->i_private); return NULL; } diff --git a/fs/befs/linuxvfs.c b/fs/befs/linuxvfs.c index 339ac02c0e17..1151a6fbc74e 100644 --- a/fs/befs/linuxvfs.c +++ b/fs/befs/linuxvfs.c @@ -42,8 +42,8 @@ static struct inode *befs_iget(struct super_block *, unsigned long); static struct inode *befs_alloc_inode(struct super_block *sb); static void befs_destroy_inode(struct inode *inode); static void befs_destroy_inodecache(void); -static void *befs_follow_link(struct dentry *); -static void *befs_fast_follow_link(struct dentry *); +static void *befs_follow_link(struct dentry *, struct inode *, int); +static void *befs_fast_follow_link(struct dentry *, struct inode *, int); static int befs_utf2nls(struct super_block *sb, const char *in, int in_len, char **out, int *out_len); static int befs_nls2utf(struct super_block *sb, const char *in, int in_len, @@ -469,14 +469,17 @@ befs_destroy_inodecache(void) * flag is set. */ static void * -befs_follow_link(struct dentry *dentry) +befs_follow_link(struct dentry *dentry, struct inode *inode, int flags) { struct super_block *sb = dentry->d_sb; - befs_inode_info *befs_ino = BEFS_I(dentry->d_inode); + befs_inode_info *befs_ino = BEFS_I(inode); befs_data_stream *data = &befs_ino->i_data.ds; befs_off_t len = data->size; char *link; + if (flags & LOOKUP_RCU) + return ERR_PTR(-ECHILD); + if (len == 0) { befs_error(sb, "Long symlink with illegal length"); link = ERR_PTR(-EIO); @@ -500,9 +503,9 @@ befs_follow_link(struct dentry *dentry) static void * -befs_fast_follow_link(struct dentry *dentry) +befs_fast_follow_link(struct dentry *dentry, struct inode *inode, int flags) { - befs_inode_info *befs_ino = BEFS_I(dentry->d_inode); + befs_inode_info *befs_ino = BEFS_I(inode); nd_set_link(befs_ino->i_data.symlink); return NULL; } diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c index f8212d6945de..761b55e73491 100644 --- a/fs/ceph/inode.c +++ b/fs/ceph/inode.c @@ -1691,9 +1691,10 @@ retry: /* * symlinks */ -static void *ceph_sym_follow_link(struct dentry *dentry) +static void *ceph_sym_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { - struct ceph_inode_info *ci = ceph_inode(dentry->d_inode); + struct ceph_inode_info *ci = ceph_inode(inode); nd_set_link(ci->i_symlink); return NULL; } diff --git a/fs/cifs/cifsfs.h b/fs/cifs/cifsfs.h index e3a6ef52a3e4..e58e685565d8 100644 --- a/fs/cifs/cifsfs.h +++ b/fs/cifs/cifsfs.h @@ -120,7 +120,8 @@ extern struct vfsmount *cifs_dfs_d_automount(struct path *path); #endif /* Functions related to symlinks */ -extern void *cifs_follow_link(struct dentry *direntry); +extern void *cifs_follow_link(struct dentry *direntry, struct inode *inode, + int flags); extern int cifs_readlink(struct dentry *direntry, char __user *buffer, int buflen); extern int cifs_symlink(struct inode *inode, struct dentry *direntry, diff --git a/fs/cifs/link.c b/fs/cifs/link.c index ba3562198c33..0d3c14acbdfc 100644 --- a/fs/cifs/link.c +++ b/fs/cifs/link.c @@ -627,9 +627,8 @@ cifs_hl_exit: } void * -cifs_follow_link(struct dentry *direntry) +cifs_follow_link(struct dentry *direntry, struct inode *inode, int flags) { - struct inode *inode = direntry->d_inode; int rc = -ENOMEM; unsigned int xid; char *full_path = NULL; @@ -639,6 +638,8 @@ cifs_follow_link(struct dentry *direntry) struct cifs_tcon *tcon; struct TCP_Server_Info *server; + if (flags & LOOKUP_RCU) + return ERR_PTR(-ECHILD); xid = get_xid(); tlink = cifs_sb_tlink(cifs_sb); diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c index ff41712ffddd..443b11251b84 100644 --- a/fs/configfs/symlink.c +++ b/fs/configfs/symlink.c @@ -279,10 +279,16 @@ static int configfs_getlink(struct dentry *dentry, char * path) } -static void *configfs_follow_link(struct dentry *dentry) +static void *configfs_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { int error = -ENOMEM; - unsigned long page = get_zeroed_page(GFP_KERNEL); + unsigned long page; + + if (flags & LOOKUP_RCU) + return ERR_PTR(-ECHILD); + + page = get_zeroed_page(GFP_KERNEL); if (page) { error = configfs_getlink(dentry, (char *)page); diff --git a/fs/debugfs/file.c b/fs/debugfs/file.c index eeed1f1fed4f..720dfb983b93 100644 --- a/fs/debugfs/file.c +++ b/fs/debugfs/file.c @@ -43,9 +43,10 @@ const struct file_operations debugfs_file_operations = { .llseek = noop_llseek, }; -static void *debugfs_follow_link(struct dentry *dentry) +static void *debugfs_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { - nd_set_link(dentry->d_inode->i_private); + nd_set_link(inode->i_private); return NULL; } diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index 680cf30e9135..17c4321e6d40 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -673,13 +673,19 @@ out: return rc ? ERR_PTR(rc) : buf; } -static void *ecryptfs_follow_link(struct dentry *dentry) +static void *ecryptfs_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { size_t len; - char *buf = ecryptfs_readlink_lower(dentry, &len); + char *buf; + + if (flags & LOOKUP_RCU) + return ERR_PTR(-ECHILD); + + buf = ecryptfs_readlink_lower(dentry, &len); if (IS_ERR(buf)) goto out; - fsstack_copy_attr_atime(dentry->d_inode, + fsstack_copy_attr_atime(inode, ecryptfs_dentry_to_lower(dentry)->d_inode); buf[len] = '\0'; out: diff --git a/fs/exofs/symlink.c b/fs/exofs/symlink.c index e6d0467a0b5a..c8525b051811 100644 --- a/fs/exofs/symlink.c +++ b/fs/exofs/symlink.c @@ -35,10 +35,10 @@ #include "exofs.h" -static void *exofs_follow_link(struct dentry *dentry) +static void *exofs_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { - struct exofs_i_info *oi = exofs_i(dentry->d_inode); - + struct exofs_i_info *oi = exofs_i(inode); nd_set_link((char *)oi->i_data); return NULL; } diff --git a/fs/ext2/symlink.c b/fs/ext2/symlink.c index 063852432bd4..eb1820a875c9 100644 --- a/fs/ext2/symlink.c +++ b/fs/ext2/symlink.c @@ -21,9 +21,10 @@ #include "xattr.h" #include <linux/namei.h> -static void *ext2_follow_link(struct dentry *dentry) +static void *ext2_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { - struct ext2_inode_info *ei = EXT2_I(dentry->d_inode); + struct ext2_inode_info *ei = EXT2_I(inode); nd_set_link((char *)ei->i_data); return NULL; } diff --git a/fs/ext3/symlink.c b/fs/ext3/symlink.c index bf8acd9efaae..c048fedc13c4 100644 --- a/fs/ext3/symlink.c +++ b/fs/ext3/symlink.c @@ -21,9 +21,10 @@ #include "ext3.h" #include "xattr.h" -static void * ext3_follow_link(struct dentry *dentry) +static void * ext3_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { - struct ext3_inode_info *ei = EXT3_I(dentry->d_inode); + struct ext3_inode_info *ei = EXT3_I(inode); nd_set_link((char*)ei->i_data); return NULL; } diff --git a/fs/ext4/symlink.c b/fs/ext4/symlink.c index 0015e7f53d0f..da0790514769 100644 --- a/fs/ext4/symlink.c +++ b/fs/ext4/symlink.c @@ -23,9 +23,10 @@ #include "ext4.h" #include "xattr.h" -static void *ext4_follow_link(struct dentry *dentry) +static void *ext4_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { - struct ext4_inode_info *ei = EXT4_I(dentry->d_inode); + struct ext4_inode_info *ei = EXT4_I(inode); nd_set_link((char *) ei->i_data); return NULL; } diff --git a/fs/freevxfs/vxfs_immed.c b/fs/freevxfs/vxfs_immed.c index 058acefeb11c..bc8f83f1ff7d 100644 --- a/fs/freevxfs/vxfs_immed.c +++ b/fs/freevxfs/vxfs_immed.c @@ -39,7 +39,7 @@ #include "vxfs_inode.h" -static void * vxfs_immed_follow_link(struct dentry *); +static void * vxfs_immed_follow_link(struct dentry *, struct inode *, int); static int vxfs_immed_readpage(struct file *, struct page *); @@ -64,6 +64,8 @@ const struct address_space_operations vxfs_immed_aops = { /** * vxfs_immed_follow_link - follow immed symlink * @dp: dentry for the link + * @inode: inode for the link + * @flags: lookup flags * * Description: * vxfs_immed_follow_link restarts the pathname lookup with @@ -73,9 +75,10 @@ const struct address_space_operations vxfs_immed_aops = { * Zero on success, else a negative error code. */ static void * -vxfs_immed_follow_link(struct dentry *dp) +vxfs_immed_follow_link(struct dentry *dp, struct inode *inode, + int flags) { - struct vxfs_inode_info *vip = VXFS_INO(dp->d_inode); + struct vxfs_inode_info *vip = VXFS_INO(inode); nd_set_link(vip->vii_immed.vi_immed); return NULL; } diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 58e632be862b..e950b3c774c5 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1400,8 +1400,11 @@ static void free_link(char *link) free_page((unsigned long) link); } -static void *fuse_follow_link(struct dentry *dentry) +static void *fuse_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { + if (flags & LOOKUP_RCU) + return ERR_PTR(-ECHILD); nd_set_link(read_link(dentry)); return NULL; } diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 5b28009ea860..2e4caa76bf97 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -1541,21 +1541,26 @@ out: /** * gfs2_follow_link - Follow a symbolic link * @dentry: The dentry of the link + * @inode: The inode of the link + * @flags: Lookup flags * * This can handle symlinks of any size. * * Returns: 0 on success or error code */ -static void *gfs2_follow_link(struct dentry *dentry) +static void *gfs2_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { - struct gfs2_inode *ip = GFS2_I(dentry->d_inode); + struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_holder i_gh; struct buffer_head *dibh; unsigned int size; char *buf; int error; + if (flags & LOOKUP_RCU) + return ERR_PTR(-ECHILD); gfs2_holder_init(ip->i_gl, LM_ST_SHARED, 0, &i_gh); error = gfs2_glock_nq(&i_gh); if (error) { diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c index a862634bf98e..f40966f9fdaf 100644 --- a/fs/hostfs/hostfs_kern.c +++ b/fs/hostfs/hostfs_kern.c @@ -882,9 +882,15 @@ static const struct inode_operations hostfs_dir_iops = { .setattr = hostfs_setattr, }; -static void *hostfs_follow_link(struct dentry *dentry) +static void *hostfs_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { - char *link = __getname(); + char *link; + + if (flags & LOOKUP_RCU) + return ERR_PTR(-ECHILD); + + link = __getname(); if (link) { char *path = dentry_name(dentry); int err = -ENOMEM; diff --git a/fs/hppfs/hppfs.c b/fs/hppfs/hppfs.c index bf13cf24eec7..b6f68e22d0fb 100644 --- a/fs/hppfs/hppfs.c +++ b/fs/hppfs/hppfs.c @@ -642,11 +642,14 @@ static int hppfs_readlink(struct dentry *dentry, char __user *buffer, buflen); } -static void *hppfs_follow_link(struct dentry *dentry) +static void *hppfs_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { - struct dentry *proc_dentry = HPPFS_I(dentry->d_inode)->proc_dentry; + struct dentry *proc_dentry = HPPFS_I(inode)->proc_dentry; - return proc_dentry->d_inode->i_op->follow_link(proc_dentry); + return proc_dentry->d_inode->i_op->follow_link(proc_dentry, + proc_dentry->d_inode, + flags); } static void hppfs_put_link(struct dentry *dentry, char *link, diff --git a/fs/jffs2/symlink.c b/fs/jffs2/symlink.c index 6b58d7659fbd..18c00fbd7060 100644 --- a/fs/jffs2/symlink.c +++ b/fs/jffs2/symlink.c @@ -16,7 +16,8 @@ #include <linux/namei.h> #include "nodelist.h" -static void *jffs2_follow_link(struct dentry *dentry); +static void *jffs2_follow_link(struct dentry *dentry, struct inode *inode, + int flags); const struct inode_operations jffs2_symlink_inode_operations = { @@ -29,9 +30,10 @@ const struct inode_operations jffs2_symlink_inode_operations = .removexattr = jffs2_removexattr }; -static void *jffs2_follow_link(struct dentry *dentry) +static void *jffs2_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { - struct jffs2_inode_info *f = JFFS2_INODE_INFO(dentry->d_inode); + struct jffs2_inode_info *f = JFFS2_INODE_INFO(inode); char *p = (char *)f->target; /* diff --git a/fs/jfs/symlink.c b/fs/jfs/symlink.c index cefda45b8d3a..610f44fad05e 100644 --- a/fs/jfs/symlink.c +++ b/fs/jfs/symlink.c @@ -22,9 +22,10 @@ #include "jfs_inode.h" #include "jfs_xattr.h" -static void *jfs_follow_link(struct dentry *dentry) +static void *jfs_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { - char *s = JFS_IP(dentry->d_inode)->i_inline; + char *s = JFS_IP(inode)->i_inline; nd_set_link(s); return NULL; } diff --git a/fs/kernfs/symlink.c b/fs/kernfs/symlink.c index 63d08ecbb72c..1a40d5e6ac71 100644 --- a/fs/kernfs/symlink.c +++ b/fs/kernfs/symlink.c @@ -112,10 +112,16 @@ static int kernfs_getlink(struct dentry *dentry, char *path) return error; } -static void *kernfs_iop_follow_link(struct dentry *dentry) +static void *kernfs_iop_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { int error = -ENOMEM; - unsigned long page = get_zeroed_page(GFP_KERNEL); + unsigned long page; + + if (flags & LOOKUP_RCU) + return ERR_PTR(-ECHILD); + + page = get_zeroed_page(GFP_KERNEL); if (page) { error = kernfs_getlink(dentry, (char *) page); if (error < 0) diff --git a/fs/namei.c b/fs/namei.c index e7fab6886e29..784fca0e6c70 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -876,6 +876,7 @@ static __always_inline int follow_link(struct path *link, struct nameidata *nd, void **p) { struct dentry *dentry = link->dentry; + struct inode *inode = dentry->d_inode; int error; char *s; @@ -894,12 +895,13 @@ follow_link(struct path *link, struct nameidata *nd, void **p) touch_atime(link); nd_set_link(NULL); - error = security_inode_follow_link(link->dentry); + error = security_inode_follow_link(dentry); if (error) goto out_put_nd_path; nd->last_type = LAST_BIND; - *p = dentry->d_inode->i_op->follow_link(dentry); + *p = inode->i_op->follow_link(dentry, inode, + nd->flags & LOOKUP_RCU); error = PTR_ERR(*p); if (IS_ERR(*p)) goto out_put_nd_path; @@ -4464,7 +4466,8 @@ int generic_readlink(struct dentry *dentry, char __user *buffer, int buflen) int res; nd.depth = 0; - cookie = dentry->d_inode->i_op->follow_link(dentry); + cookie = dentry->d_inode->i_op->follow_link(dentry, + dentry->d_inode, 0); if (IS_ERR(cookie)) res = PTR_ERR(cookie); else { @@ -4506,9 +4509,12 @@ int page_readlink(struct dentry *dentry, char __user *buffer, int buflen) } EXPORT_SYMBOL(page_readlink); -void *page_follow_link_light(struct dentry *dentry) +void *page_follow_link_light(struct dentry *dentry, struct inode *inode, + int flags) { struct page *page = NULL; + if (flags & LOOKUP_RCU) + return ERR_PTR(-ECHILD); nd_set_link(page_getlink(dentry, &page)); return page; } diff --git a/fs/nfs/symlink.c b/fs/nfs/symlink.c index f3c44616b615..32bbac1bb4bc 100644 --- a/fs/nfs/symlink.c +++ b/fs/nfs/symlink.c @@ -43,12 +43,14 @@ error: return -EIO; } -static void *nfs_follow_link(struct dentry *dentry) +static void *nfs_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { - struct inode *inode = dentry->d_inode; struct page *page; void *err; + if (flags & LOOKUP_RCU) + return ERR_PTR(-ECHILD); err = ERR_PTR(nfs_revalidate_mapping(inode, inode->i_mapping)); if (err) goto read_failed; diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 0de7b87bd025..675efccd5c84 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -8,6 +8,7 @@ */ #include <linux/fs.h> +#include <linux/namei.h> #include <linux/slab.h> #include <linux/xattr.h> #include "overlayfs.h" @@ -140,13 +141,16 @@ struct ovl_link_data { void *cookie; }; -static void *ovl_follow_link(struct dentry *dentry) +static void *ovl_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { void *ret; struct dentry *realdentry; struct inode *realinode; struct ovl_link_data *data = NULL; + if (flags & LOOKUP_RCU) + return ERR_PTR(-ECHILD); realdentry = ovl_dentry_real(dentry); realinode = realdentry->d_inode; @@ -160,7 +164,7 @@ static void *ovl_follow_link(struct dentry *dentry) data->realdentry = realdentry; } - ret = realinode->i_op->follow_link(realdentry); + ret = realinode->i_op->follow_link(realdentry, realinode, flags); if (IS_ERR(ret)) { kfree(data); return ret; diff --git a/fs/proc/base.c b/fs/proc/base.c index a0c0b85aead3..203f8d8ceab1 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -1371,12 +1371,14 @@ static int proc_exe_link(struct dentry *dentry, struct path *exe_path) return -ENOENT; } -static void *proc_pid_follow_link(struct dentry *dentry) +static void *proc_pid_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { - struct inode *inode = dentry->d_inode; struct path path; int error = -EACCES; + if (flags & LOOKUP_RCU) + return ERR_PTR(-ECHILD); /* Are we allowed to snoop on the tasks file descriptors? */ if (!proc_fd_access_allowed(inode)) goto out; diff --git a/fs/proc/inode.c b/fs/proc/inode.c index 7bdaf1040f98..faf2c5400437 100644 --- a/fs/proc/inode.c +++ b/fs/proc/inode.c @@ -394,9 +394,10 @@ static const struct file_operations proc_reg_file_ops_no_compat = { }; #endif -static void *proc_follow_link(struct dentry *dentry) +static void *proc_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { - struct proc_dir_entry *pde = PDE(dentry->d_inode); + struct proc_dir_entry *pde = PDE(inode); if (unlikely(!use_pde(pde))) return ERR_PTR(-EINVAL); nd_set_link(pde->data); diff --git a/fs/proc/namespaces.c b/fs/proc/namespaces.c index 5e3394509c2c..a2578c44edeb 100644 --- a/fs/proc/namespaces.c +++ b/fs/proc/namespaces.c @@ -30,14 +30,17 @@ static const struct proc_ns_operations *ns_entries[] = { &mntns_operations, }; -static void *proc_ns_follow_link(struct dentry *dentry) +static void *proc_ns_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { - struct inode *inode = dentry->d_inode; const struct proc_ns_operations *ns_ops = PROC_I(inode)->ns_ops; struct task_struct *task; struct path ns_path; void *error = ERR_PTR(-EACCES); + if (flags & LOOKUP_RCU) + return ERR_PTR(-ECHILD); + task = get_proc_task(inode); if (!task) return error; diff --git a/fs/proc/self.c b/fs/proc/self.c index 639bd0afdc05..7fcb906c250a 100644 --- a/fs/proc/self.c +++ b/fs/proc/self.c @@ -19,11 +19,17 @@ static int proc_self_readlink(struct dentry *dentry, char __user *buffer, return readlink_copy(buffer, buflen, tmp); } -static void *proc_self_follow_link(struct dentry *dentry) +static void *proc_self_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { struct pid_namespace *ns = dentry->d_sb->s_fs_info; - pid_t tgid = task_tgid_nr_ns(current, ns); + pid_t tgid; char *name = ERR_PTR(-ENOENT); + + if (flags & LOOKUP_RCU) + return ERR_PTR(-ECHILD); + + tgid = task_tgid_nr_ns(current, ns); if (tgid) { /* 11 for max length of signed int in decimal + NULL term */ name = kmalloc(12, GFP_KERNEL); diff --git a/fs/proc/thread_self.c b/fs/proc/thread_self.c index 2036b051f53f..7a9af8a6baab 100644 --- a/fs/proc/thread_self.c +++ b/fs/proc/thread_self.c @@ -20,12 +20,19 @@ static int proc_thread_self_readlink(struct dentry *dentry, char __user *buffer, return readlink_copy(buffer, buflen, tmp); } -static void *proc_thread_self_follow_link(struct dentry *dentry) +static void *proc_thread_self_follow_link(struct dentry *dentry, + struct inode *inode, int flags) { struct pid_namespace *ns = dentry->d_sb->s_fs_info; - pid_t tgid = task_tgid_nr_ns(current, ns); - pid_t pid = task_pid_nr_ns(current, ns); + pid_t tgid; + pid_t pid; char *name = ERR_PTR(-ENOENT); + + if (flags & LOOKUP_RCU) + return ERR_PTR(-ECHILD); + + tgid = task_tgid_nr_ns(current, ns); + pid = task_pid_nr_ns(current, ns); if (pid) { name = kmalloc(PROC_NUMBUF + 6 + PROC_NUMBUF, GFP_KERNEL); if (!name) diff --git a/fs/sysv/symlink.c b/fs/sysv/symlink.c index 3f8154e6b27e..bdddd74831ac 100644 --- a/fs/sysv/symlink.c +++ b/fs/sysv/symlink.c @@ -8,9 +8,10 @@ #include "sysv.h" #include <linux/namei.h> -static void *sysv_follow_link(struct dentry *dentry) +static void *sysv_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { - nd_set_link((char *)SYSV_I(dentry->d_inode)->i_data); + nd_set_link((char *)SYSV_I(inode)->i_data); return NULL; } diff --git a/fs/ubifs/file.c b/fs/ubifs/file.c index 082958d62096..4872f14a88c1 100644 --- a/fs/ubifs/file.c +++ b/fs/ubifs/file.c @@ -1300,9 +1300,10 @@ static void ubifs_invalidatepage(struct page *page, unsigned int offset, ClearPageChecked(page); } -static void *ubifs_follow_link(struct dentry *dentry) +static void *ubifs_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { - struct ubifs_inode *ui = ubifs_inode(dentry->d_inode); + struct ubifs_inode *ui = ubifs_inode(inode); nd_set_link(ui->data); return NULL; diff --git a/fs/ufs/symlink.c b/fs/ufs/symlink.c index a8266ff60b0f..3b690a020627 100644 --- a/fs/ufs/symlink.c +++ b/fs/ufs/symlink.c @@ -32,9 +32,11 @@ #include "ufs.h" -static void *ufs_follow_link(struct dentry *dentry) +static void *ufs_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { - struct ufs_inode_info *p = UFS_I(dentry->d_inode); + struct ufs_inode_info *p = UFS_I(inode); + nd_set_link((char*)p->i_u1.i_symlink); return NULL; } diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index ac915d09de29..c2c136ef3a50 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -411,16 +411,20 @@ xfs_vn_rename( */ STATIC void * xfs_vn_follow_link( - struct dentry *dentry) + struct dentry *dentry, + struct inode *inode, + int flags) { char *link; int error = -ENOMEM; + if (flags & LOOKUP_RCU) + return ERR_PTR(-ECHILD); link = kmalloc(MAXPATHLEN+1, GFP_KERNEL); if (!link) goto out_err; - error = xfs_readlink(XFS_I(dentry->d_inode), link); + error = xfs_readlink(XFS_I(inode), link); if (unlikely(error)) goto out_kfree; diff --git a/include/linux/fs.h b/include/linux/fs.h index d78dd3ae1be9..dda92ac8ef41 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1573,7 +1573,7 @@ struct file_operations { struct inode_operations { struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int); - void * (*follow_link) (struct dentry *); + void * (*follow_link) (struct dentry *, struct inode *, int); int (*permission) (struct inode *, int); struct posix_acl * (*get_acl)(struct inode *, int); @@ -2648,7 +2648,7 @@ extern const struct file_operations generic_ro_fops; extern int readlink_copy(char __user *, int, const char *); extern int page_readlink(struct dentry *, char __user *, int); -extern void *page_follow_link_light(struct dentry *); +extern void *page_follow_link_light(struct dentry *, struct inode *, int); extern void page_put_link(struct dentry *, char *, void *); extern int __page_symlink(struct inode *inode, const char *symname, int len, int nofs); diff --git a/mm/shmem.c b/mm/shmem.c index 910b37f44a2b..1083e8c75536 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -2474,16 +2474,22 @@ static int shmem_symlink(struct inode *dir, struct dentry *dentry, const char *s return 0; } -static void *shmem_follow_short_symlink(struct dentry *dentry) +static void *shmem_follow_short_symlink(struct dentry *dentry, + struct inode *inode, int flags) { - nd_set_link(SHMEM_I(dentry->d_inode)->symlink); + nd_set_link(SHMEM_I(inode)->symlink); return NULL; } -static void *shmem_follow_link(struct dentry *dentry) +static void *shmem_follow_link(struct dentry *dentry, struct inode *inode, + int flags) { struct page *page = NULL; - int error = shmem_getpage(dentry->d_inode, 0, &page, SGP_READ, NULL); + int error; + + if (flags & LOOKUP_RCU) + return ERR_PTR(-ECHILD); + error = shmem_getpage(inode, 0, &page, SGP_READ, NULL); nd_set_link(error ? ERR_PTR(error) : kmap(page)); if (page) unlock_page(page); -- To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html