From 92520bbeef256957c150df01f946ad8890140a78 Mon Sep 17 00:00:00 2001 From: Aurelien Aptel Date: Thu, 5 Nov 2015 15:24:20 +0100 Subject: [PATCH] cifs: make shares unaccessible at root level mountable Based on Shirish Pargaonkar's disconnected root patch [1]. The initial patch was modified to create a disconnected root when any intermediary path is unaccessible (EACCESS) but *also* when "not found" (ENOENT). The other change was to modify build_path_from_dentry() in dir.c so that if it has to build a path for a dentry that uses a disconnected root, it prefixes that path with the disconnected root actual path. Previously, when called with a dentry that had a disconnected root, build_path_path() would stop when it reached that disconnected root, which is not the same as the root of the share, resulting in a wrong output path. 1: https://bugzilla.samba.org/show_bug.cgi?id=8950 Signed-off-by: Aurelien Aptel --- fs/cifs/cifs_fs_sb.h | 10 +++ fs/cifs/cifsfs.c | 84 +++++++++++++++++++- fs/cifs/cifsproto.h | 1 + fs/cifs/connect.c | 3 + fs/cifs/dir.c | 217 ++++++++++++++++++++++++++++++++++++++++++++++----- fs/cifs/inode.c | 36 ++++++++- 6 files changed, 325 insertions(+), 26 deletions(-) diff --git a/fs/cifs/cifs_fs_sb.h b/fs/cifs/cifs_fs_sb.h index 3182273..b2b65f6 100644 --- a/fs/cifs/cifs_fs_sb.h +++ b/fs/cifs/cifs_fs_sb.h @@ -67,5 +67,15 @@ struct cifs_sb_info { struct backing_dev_info bdi; struct delayed_work prune_tlinks; struct rcu_head rcu; + struct list_head rtdislist; /* list of disconnected root dentries */ + spinlock_t rtdislock; /* lock for disconnected root dentry list */ +}; + +struct cifs_rdelem { + int rdcount; + struct list_head rdlist; + char * rdname; + struct dentry * rdentry; + struct inode * rdinode; }; #endif /* _CIFS_FS_SB_H */ diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c index 6a1119e..55160df 100644 --- a/fs/cifs/cifsfs.c +++ b/fs/cifs/cifsfs.c @@ -584,6 +584,80 @@ static const struct super_operations cifs_super_ops = { #endif }; +void +cifs_free_rdelem(struct cifs_rdelem *rdelem) +{ + kfree(rdelem->rdname); + kfree(rdelem); +} + +struct cifs_rdelem * +cifs_alloc_rdelem(char *full_path, struct dentry *rdentry, + struct inode *rdinode) +{ + struct cifs_rdelem *rdelem; + rdelem = kmalloc(sizeof(struct cifs_rdelem), GFP_KERNEL); + if (!rdelem) { + cifs_dbg(FYI, "%s Can't allocate root dentry\n", __func__); + return ERR_PTR(-ENOMEM); + } + + rdelem->rdname = kstrdup(full_path, GFP_KERNEL); + + if (!rdelem->rdname) { + cifs_dbg(FYI, "%s Can't allocate root dentry name\n", __func__); + kfree(rdelem); + return ERR_PTR(-ENOMEM); + } + + rdelem->rdinode = rdinode; + rdelem->rdentry = rdentry; + + return rdelem; +} + +static struct dentry * +create_root_dis_dentry(struct super_block *sb, struct inode *rinode, + char *fpath) +{ + int rc; + unsigned int xid; + struct dentry *dentry = NULL; + struct cifs_rdelem *rdelem = NULL; + struct cifs_sb_info *cifs_sb = CIFS_SB(sb); + struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb); + + xid = get_xid(); + if (tcon->unix_ext) + rc = cifs_get_inode_info_unix(&rinode, fpath, sb, xid); + else + rc = cifs_get_inode_info(&rinode, fpath, NULL, sb, xid, NULL); + free_xid(xid); + + if ((rc == 0) && (rinode != NULL)) { + dentry = d_obtain_alias(rinode); + if (IS_ERR(dentry)) { + iput(rinode); + goto rdelem_ret; + } + + if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SERVER_INUM)) { + + rdelem = + cifs_alloc_rdelem(fpath, dentry, rinode); + if (IS_ERR(rdelem)) + goto rdelem_ret; + spin_lock(&cifs_sb->rtdislock); + list_add(&rdelem->rdlist, &cifs_sb->rtdislist); + spin_unlock(&cifs_sb->rtdislock); + } + return dentry; + } + +rdelem_ret: + return ERR_PTR(-EACCES); +} + /* * Get root dentry from superblock according to prefix path mount option. * Return dentry with refcount + 1 on success and NULL otherwise. @@ -596,9 +670,10 @@ cifs_get_root(struct smb_vol *vol, struct super_block *sb) char *full_path = NULL; char *s, *p; char sep; + struct inode *rinode = NULL; + struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb); - full_path = cifs_build_path_to_root(vol, cifs_sb, - cifs_sb_master_tcon(cifs_sb)); + full_path = cifs_build_path_to_root(vol, cifs_sb, tcon); if (full_path == NULL) return ERR_PTR(-ENOMEM); @@ -639,6 +714,11 @@ cifs_get_root(struct smb_vol *vol, struct super_block *sb) dput(dentry); dentry = child; } while (!IS_ERR(dentry)); + + if (IS_ERR(dentry) && (PTR_ERR(dentry) == -EACCES || PTR_ERR(dentry) == -ENOENT) && *s) { + dentry = create_root_dis_dentry(sb, rinode, full_path); + } + kfree(full_path); return dentry; } diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h index c63fd1d..b0a813f 100644 --- a/fs/cifs/cifsproto.h +++ b/fs/cifs/cifsproto.h @@ -205,6 +205,7 @@ extern void cifs_add_pending_open_locked(struct cifs_fid *fid, struct tcon_link *tlink, struct cifs_pending_open *open); extern void cifs_del_pending_open(struct cifs_pending_open *open); +extern void cifs_free_rdelem(struct cifs_rdelem *); #if IS_ENABLED(CONFIG_CIFS_DFS_UPCALL) extern void cifs_dfs_release_automount_timer(void); diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 773f4dc..018c156 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -3190,6 +3190,9 @@ void cifs_setup_cifs_sb(struct smb_vol *pvolume_info, spin_lock_init(&cifs_sb->tlink_tree_lock); cifs_sb->tlink_tree = RB_ROOT; + spin_lock_init(&cifs_sb->rtdislock); + INIT_LIST_HEAD(&cifs_sb->rtdislist); + /* * Temporarily set r/wsize for matching superblock. If we end up using * new sb then client will later negotiate it downward if needed. diff --git a/fs/cifs/dir.c b/fs/cifs/dir.c index c3eb998..bc02265 100644 --- a/fs/cifs/dir.c +++ b/fs/cifs/dir.c @@ -77,6 +77,9 @@ cifs_build_path_to_root(struct smb_vol *vol, struct cifs_sb_info *cifs_sb, return full_path; } +static struct cifs_rdelem * find_rdelem_by_dentry_no_del(const struct dentry *rdentry, + struct cifs_sb_info * cifs_sb); + /* Note: caller must free return buffer */ char * build_path_from_dentry(struct dentry *direntry) @@ -89,6 +92,25 @@ build_path_from_dentry(struct dentry *direntry) struct cifs_sb_info *cifs_sb = CIFS_SB(direntry->d_sb); struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb); unsigned seq; + struct cifs_rdelem *dis_root = NULL; + size_t dis_root_len = 0; + + /* + * First look if the dentry has a disconnected root in its + * parent hierarchy. If that's the case we must append that + * root path as a prefix. + */ + + rcu_read_lock(); + for (temp = direntry; temp && !IS_ROOT(temp); temp = temp->d_parent); + if (temp && (dis_root = find_rdelem_by_dentry_no_del(temp, cifs_sb))) + dis_root_len = strlen(dis_root->rdname); + rcu_read_unlock(); + + /* + * Now let's compute the length of the path so that we can + * allocate the right size + */ dirsep = CIFS_DIR_SEP(cifs_sb); if (tcon->Flags & SMB_SHARE_IS_IN_DFS) @@ -108,8 +130,19 @@ cifs_bp_rename_retry: return NULL; } } + if (dis_root && temp == dis_root->rdentry) { + /* + * Account for the disconnected root prefix + * Note: leading dir separator already in the length + */ + namelen += dis_root_len; + } rcu_read_unlock(); + /* + * Next step is to actually fill the full_path string + */ + full_path = kmalloc(namelen+1, GFP_KERNEL); if (full_path == NULL) return full_path; @@ -136,7 +169,12 @@ cifs_bp_rename_retry: return NULL; } } + if (dis_root && temp == dis_root->rdentry) { + namelen -= dis_root_len; + strncpy(full_path + namelen, dis_root->rdname, dis_root_len); + } rcu_read_unlock(); + if (namelen != dfsplen || read_seqretry(&rename_lock, seq)) { cifs_dbg(FYI, "did not end path lookup where expected. namelen=%ddfsplen=%d\n", namelen, dfsplen); @@ -704,6 +742,73 @@ mknod_out: return rc; } +static struct cifs_rdelem * +find_rdelem_by_inode(struct inode *rdinode, struct cifs_sb_info * cifs_sb) +{ + struct cifs_rdelem *rdelem; + spin_lock(&cifs_sb->rtdislock); + list_for_each_entry(rdelem, &cifs_sb->rtdislist, rdlist) { + if (rdelem->rdinode == rdinode) { + list_del(&rdelem->rdlist); + spin_unlock(&cifs_sb->rtdislock); + return rdelem; + } + } + spin_unlock(&cifs_sb->rtdislock); + return NULL; +} + +static struct cifs_rdelem * +find_rdelem_by_dentry(const struct dentry *rdentry, + struct cifs_sb_info * cifs_sb) +{ + struct cifs_rdelem *rdelem; + spin_lock(&cifs_sb->rtdislock); + list_for_each_entry(rdelem, &cifs_sb->rtdislist, rdlist) { + if (rdelem->rdentry == rdentry) { + list_del(&rdelem->rdlist); + spin_unlock(&cifs_sb->rtdislock); + return rdelem; + } + } + spin_unlock(&cifs_sb->rtdislock); + return NULL; +} + +static struct cifs_rdelem * +find_rdelem_by_dentry_no_del(const struct dentry *rdentry, + struct cifs_sb_info * cifs_sb) +{ + struct cifs_rdelem *rdelem; + struct cifs_rdelem *found = NULL; + spin_lock(&cifs_sb->rtdislock); + list_for_each_entry(rdelem, &cifs_sb->rtdislist, rdlist) { + if (rdelem->rdentry == rdentry) { + found = rdelem; + break; + } + } + spin_unlock(&cifs_sb->rtdislock); + return found; +} + + +static void +find_rdelem_by_path(char *full_path, struct inode **newInode, + struct cifs_sb_info * cifs_sb) +{ + struct cifs_rdelem *rdelem; + spin_lock(&cifs_sb->rtdislock); + list_for_each_entry(rdelem, &cifs_sb->rtdislist, rdlist) { + if (!strcmp(rdelem->rdname, full_path)) { + *newInode = ilookup(rdelem->rdinode->i_sb, + rdelem->rdinode->i_ino); + break; + } + } + spin_unlock(&cifs_sb->rtdislock); +} + struct dentry * cifs_lookup(struct inode *parent_dir_inode, struct dentry *direntry, unsigned int flags) @@ -715,6 +820,9 @@ cifs_lookup(struct inode *parent_dir_inode, struct dentry *direntry, struct cifs_tcon *pTcon; struct inode *newInode = NULL; char *full_path = NULL; + struct dentry *ret = NULL; + struct cifs_rdelem *rdelem; + struct qstr dname; xid = get_xid(); @@ -751,20 +859,66 @@ cifs_lookup(struct inode *parent_dir_inode, struct dentry *direntry, } cifs_dbg(FYI, "Full path: %s inode = 0x%p\n", full_path, d_inode(direntry)); + if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SERVER_INUM)) { + /* + * Looking for an existing disconnected root dentry if any, + * before sending out a lookup on the wire. + */ + find_rdelem_by_path(full_path, &newInode, cifs_sb); + } - if (pTcon->unix_ext) { - rc = cifs_get_inode_info_unix(&newInode, full_path, - parent_dir_inode->i_sb, xid); - } else { - rc = cifs_get_inode_info(&newInode, full_path, NULL, - parent_dir_inode->i_sb, xid, NULL); + if (!newInode) { + if (pTcon->unix_ext) { + rc = cifs_get_inode_info_unix(&newInode, full_path, + parent_dir_inode->i_sb, xid); + } else + rc = cifs_get_inode_info(&newInode, full_path, NULL, + parent_dir_inode->i_sb, xid, NULL); } + /* else, found an anonymous root dentry with an inode */ + + if ((rc == 0) && (newInode != NULL)) { - d_add(direntry, newInode); + /* since paths are not looked up by component - the parent directories are presumed to be good here */ - renew_parental_timestamps(direntry); + if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SERVER_INUM)) { + dname.name = direntry->d_name.name; + dname.len = strlen(direntry->d_name.name) + 1; + /* + * Perhaps another lookup beat us to this. + */ + spin_lock(&cifs_sb->rtdislock); + ret = d_lookup(direntry->d_parent, &dname); + if (ret && !IS_ERR(ret)) { + dput(ret); + spin_unlock(&cifs_sb->rtdislock); + goto lookup_out; + } else + ret = d_splice_alias(newInode, direntry); + spin_unlock(&cifs_sb->rtdislock); + } else + ret = d_splice_alias(newInode, direntry); + if (!ret) + renew_parental_timestamps(direntry); + + else { + if (!IS_ERR(ret)) { + if (!(cifs_sb->mnt_cifs_flags & + CIFS_MOUNT_SERVER_INUM)) { + rdelem = + find_rdelem_by_inode(newInode, cifs_sb); + if (rdelem) + cifs_free_rdelem(rdelem); + } + renew_parental_timestamps(ret); + dput(ret); + goto lookup_out; + } else + rc = PTR_ERR(ret); + } + } else if (rc == -ENOENT) { rc = 0; @@ -777,12 +931,41 @@ cifs_lookup(struct inode *parent_dir_inode, struct dentry *direntry, /* We special case check for Access Denied - since that is a common return code */ } - + ret = ERR_PTR(rc); lookup_out: kfree(full_path); cifs_put_tlink(tlink); free_xid(xid); - return ERR_PTR(rc); + return ret; +} + +static void +cifs_d_common_releasedelete(const struct dentry *dentry) +{ + struct cifs_rdelem *rdelem; + struct cifs_sb_info *cifs_sb; + + cifs_sb = CIFS_SB(dentry->d_sb); + + /* disconnected root dentries that did not get spliced */ + if (IS_ROOT(dentry) && dentry->d_flags & DCACHE_DISCONNECTED) { + rdelem = find_rdelem_by_dentry(dentry, cifs_sb); + if (rdelem) + cifs_free_rdelem(rdelem); + } +} + +static int +cifs_d_delete(const struct dentry *dentry) +{ + cifs_d_common_releasedelete(dentry); + return 0; +} + +static void +cifs_d_release(struct dentry *dentry) +{ + cifs_d_common_releasedelete(dentry); } static int @@ -834,19 +1017,11 @@ cifs_d_revalidate(struct dentry *direntry, unsigned int flags) return 1; } -/* static int cifs_d_delete(struct dentry *direntry) -{ - int rc = 0; - - cifs_dbg(FYI, "In cifs d_delete, name = %pd\n", direntry); - - return rc; -} */ - const struct dentry_operations cifs_dentry_ops = { .d_revalidate = cifs_d_revalidate, .d_automount = cifs_dfs_d_automount, -/* d_delete: cifs_d_delete, */ /* not needed except for debugging */ + .d_delete = cifs_d_delete, + .d_release = cifs_d_release, }; static int cifs_ci_hash(const struct dentry *dentry, struct qstr *q) @@ -921,4 +1096,6 @@ const struct dentry_operations cifs_ci_dentry_ops = { .d_hash = cifs_ci_hash, .d_compare = cifs_ci_compare, .d_automount = cifs_dfs_d_automount, + .d_delete = cifs_d_delete, + .d_release = cifs_d_release, }; diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c index f621b44..7d17911 100644 --- a/fs/cifs/inode.c +++ b/fs/cifs/inode.c @@ -923,8 +923,10 @@ inode_has_hashed_dentries(struct inode *inode) spin_lock(&inode->i_lock); hlist_for_each_entry(dentry, &inode->i_dentry, d_u.d_alias) { if (!d_unhashed(dentry) || IS_ROOT(dentry)) { - spin_unlock(&inode->i_lock); - return true; + if (!(dentry->d_flags & DCACHE_DISCONNECTED)) { + spin_unlock(&inode->i_lock); + return true; + } } } spin_unlock(&inode->i_lock); @@ -974,6 +976,23 @@ retry_iget5_locked: return inode; } +void +fill_phfattr(struct cifs_fattr *cf, struct super_block *sb) +{ + + struct cifs_sb_info *cifs_sb = CIFS_SB(sb); + + memset(cf, 0, sizeof(*cf)); + cf->cf_uniqueid = ROOT_I; + cf->cf_nlink = 1; + cf->cf_atime = CURRENT_TIME; + cf->cf_ctime = CURRENT_TIME; + cf->cf_mtime = CURRENT_TIME; + cf->cf_mode = S_IFDIR | S_IXUGO | S_IRWXU; + cf->cf_uid = cifs_sb->mnt_uid; + cf->cf_gid = cifs_sb->mnt_gid; +} + /* gets root inode */ struct inode *cifs_root_iget(struct super_block *sb) { @@ -982,6 +1001,7 @@ struct inode *cifs_root_iget(struct super_block *sb) struct inode *inode = NULL; long rc; struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb); + struct cifs_fattr phfattr; xid = get_xid(); if (tcon->unix_ext) { @@ -997,8 +1017,16 @@ struct inode *cifs_root_iget(struct super_block *sb) iget_no_retry: if (!inode) { - inode = ERR_PTR(rc); - goto out; + if (rc == -EACCES) { + fill_phfattr(&phfattr, sb); + inode = cifs_iget(sb, &phfattr); + if (inode) + rc = 0; + } + if (rc) { + inode = ERR_PTR(rc); + goto out; + } } #ifdef CONFIG_CIFS_FSCACHE -- 2.1.4