Making shares unaccessible at root level mountable (aka The Bug)
Table of Contents
This is the story of a 5 years old cifs bug. This has been filled on the samba bugzilla as #8950. You can read the comments there but there are a lot of them and they are confusing. Here's a summary of it.
1 Problem description
Since commit f87d39d
, it's impossible to directly mount a sub-dir of
a share if we do not have permission to do QUERY_PATH_INFO
queries
on all the hierarchy from the share root to the sub-dir we are
interested in.
For example:
- you have a share
//HOST/share
- the share has a
sub/dir/
path which you,HOST\user
, has full access to - you want to mount that path
- the shared folder (or the directory
sub
) has permissions on the server that doesn't allow metadata requests (QUERY_PATH_INFO
requests) but doesn't prevent you from traversing it.
Well since that commit, you cannot mount //HOST/share/sub/dir
anymore.
The commit in question:
commit f87d39d Author: Steve French <sfrench@us.ibm.com> Date: Fri May 27 03:50:55 2011 +0000 [CIFS] Migrate from prefixpath logic Now we point superblock to a server share root and set a root dentry appropriately. This let us share superblock between mounts like //server/sharename/foo/bar and //server/sharename/foo further. Reviewed-by: Jeff Layton <jlayton@redhat.com> Signed-off-by: Pavel Shilovsky <piastry@etersoft.ru> Signed-off-by: Steve French <sfrench@us.ibm.com>
1.1 Before that commit
The subdir path we were mounting was stored in the superblock structure as a string and was used as a prefix for all queries.
diff --git a/fs/cifs/cifs_fs_sb.h b/fs/cifs/cifs_fs_sb.h index a9d5692..c96b44b 100644 --- a/fs/cifs/cifs_fs_sb.h +++ b/fs/cifs/cifs_fs_sb.h @@ -56,8 +56,6 @@ struct cifs_sb_info { mode_t mnt_file_mode; mode_t mnt_dir_mode; unsigned int mnt_cifs_flags; - int prepathlen; - char *prepath; /* relative path under the share to mount to */ char *mountdata; /* options received at mount time or via DFS refs */ struct backing_dev_info bdi; struct delayed_work prune_tlinks;
Here is the filesystem layout in memory:
+------------------+ +-------------------------+ |rootfs super_block| | cifs super_block | +------------------+ +-------------------------+ | s_root | | prepath = "/dir1/dir11" | +------------------+ | s_root | v +-------------------------+ [dentry "/"] | v v [dentry "mnt"]--magic-->[ dentry "/" ] | vvv [ content of "/dir1/dir11" ]
Sample network trace of mounting //LURCH/sspshare/dir1/dir11
as user LURCH\bill
and calling ls
:
10.160.64.127 -> 10.160.5.42 SMB 139 Negotiate Protocol Request 10.160.5.42 -> 10.160.64.127 SMB 171 Negotiate Protocol Response 10.160.64.127 -> 10.160.5.42 SMB 298 Session Setup AndX Request, User: LURCH\bill 10.160.5.42 -> 10.160.64.127 SMB 269 Session Setup AndX Response 10.160.64.127 -> 10.160.5.42 SMB 154 Tree Connect AndX Request, Path: \\lutze\sspshare 10.160.5.42 -> 10.160.64.127 SMB 132 Tree Connect AndX Response 10.160.64.127 -> 10.160.5.42 SMB 138 Trans2 Request, QUERY_FS_INFO, Query FS Device Info 10.160.5.42 -> 10.160.64.127 SMB 134 Trans2 Response, QUERY_FS_INFO 10.160.64.127 -> 10.160.5.42 SMB 138 Trans2 Request, QUERY_FS_INFO, Query FS Attribute Info 10.160.5.42 -> 10.160.64.127 SMB 146 Trans2 Response, QUERY_FS_INFO 10.160.64.127 -> 10.160.5.42 SMB 158 Tree Connect AndX Request, Path: \\10.160.5.42\IPC$ 10.160.5.42 -> 10.160.64.127 SMB 126 Tree Connect AndX Response 10.160.64.127 -> 10.160.5.42 SMB 172 Trans2 Request, GET_DFS_REFERRAL, File: \lutze\sspshare 10.160.5.42 -> 10.160.64.127 SMB 105 Trans2 Response, GET_DFS_REFERRAL, Error: STATUS_NOT_FOUND 10.160.64.127 -> 10.160.5.42 SMB 166 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: \dir1\dir11 10.160.5.42 -> 10.160.64.127 SMB 242 Trans2 Response, QUERY_PATH_INFO 10.160.64.127 -> 10.160.5.42 SMB 166 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: \dir1\dir11 10.160.5.42 -> 10.160.64.127 SMB 242 Trans2 Response, QUERY_PATH_INFO 10.160.64.127 -> 10.160.5.42 SMB 166 Trans2 Request, QUERY_PATH_INFO, Query File Internal Info, Path: \dir1\dir11 10.160.5.42 -> 10.160.64.127 SMB 138 Trans2 Response, QUERY_PATH_INFO 10.160.64.127 -> 10.160.5.42 SMB 166 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: \dir1\dir11 10.160.5.42 -> 10.160.64.127 SMB 242 Trans2 Response, QUERY_PATH_INFO 10.160.64.127 -> 10.160.5.42 SMB 176 Trans2 Request, FIND_FIRST2, Pattern: \dir1\dir11\* 10.160.5.42 -> 10.160.64.127 SMB 502 Trans2 Response, FIND_FIRST2, Files: . .. dir111 file111
1.2 After that commit
- The
prepath
struct member was dumped - The whole dentry/inode hierarchy from the root of the share to the
subdir is now fetched, one path component at a time
e.g.
//HOST/share/sub/dir
will end up querying/
,/sub/
and finally/sub/dir/
.
@@ -585,7 +677,10 @@ cifs_do_mount(struct file_system_type *fs_type, sb->s_flags |= MS_ACTIVE; - root = dget(sb->s_root); + root = cifs_get_root(volume_info, sb); + if (root == NULL) + goto out_super; + cFYI(1, "dentry root is: %p", root); goto out; out_super:
cifs_do_mount
was previously doing a simple dget
(single lookup),
now the new function cifs_get_root
does the whole hierarchy
fetching.
The filesystem layout is now this:
+------------------+ | cifs super_block | +------------------+ +------------------+ |rootfs super_block| | s_root | +------------------+ +------------------+ | s_root | | +------------------+ v | [ dentry "/" ] | | | v v [ dentry "dir1" ] [dentry "/"] | v v [dentry "mnt"] -----magic------> [ dentry "dir11" ] | vvv [ content of "/dir1/dir11" ]
The benefit of doing this is that we can now mount //HOST/share/a/b
on /mnt/b
and //HOST/share/a/c
on /mnt/c
and share dentry/inode
between the two mount points, which results in less wire lookups
(better performance) and less sync problem between the mount points.
Sample network trace of mounting //LURCH/sspshare/dir1/dir11
as an
Admin (full rights) and calling ls
:
Technically right after the commit it fails because of a
regression. The dir separator /
are not changed to \
.
10.160.64.4 -> 10.160.5.42 SMB 166 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: /dir1/dir11 10.160.5.42 -> 10.160.64.4 SMB 105 Trans2 Response, QUERY_PATH_INFO, Error: STATUS_OBJECT_NAME_INVALID
But this is not relevant to the issue. The first commit where we can
properly see the new behaviour is f9e8c45002c
(80975d2
fixes the
NULL
pointer dereference that happens when using the restricted user
credentials).
Using the admin account on a Windows 2012R2 setup:
10.160.67.86 -> 10.160.5.42 SMB 139 Negotiate Protocol Request 10.160.5.42 -> 10.160.67.86 SMB 171 Negotiate Protocol Response 10.160.67.86 -> 10.160.5.42 SMB 314 Session Setup AndX Request, User: LURCH\administrator 10.160.5.42 -> 10.160.67.86 SMB 269 Session Setup AndX Response 10.160.67.86 -> 10.160.5.42 SMB 154 Tree Connect AndX Request, Path: \\lutze\sspshare 10.160.5.42 -> 10.160.67.86 SMB 132 Tree Connect AndX Response 10.160.67.86 -> 10.160.5.42 SMB 138 Trans2 Request, QUERY_FS_INFO, Query FS Device Info 10.160.5.42 -> 10.160.67.86 SMB 134 Trans2 Response, QUERY_FS_INFO 10.160.67.86 -> 10.160.5.42 SMB 138 Trans2 Request, QUERY_FS_INFO, Query FS Attribute Info 10.160.5.42 -> 10.160.67.86 SMB 146 Trans2 Response, QUERY_FS_INFO 10.160.67.86 -> 10.160.5.42 SMB 158 Tree Connect AndX Request, Path: \\10.160.5.42\IPC$ 10.160.5.42 -> 10.160.67.86 SMB 126 Tree Connect AndX Response 10.160.67.86 -> 10.160.5.42 SMB 172 Trans2 Request, GET_DFS_REFERRAL, File: \lutze\sspshare 10.160.5.42 -> 10.160.67.86 SMB 105 Trans2 Response, GET_DFS_REFERRAL, Error: STATUS_NOT_FOUND (1) 10.160.67.86 -> 10.160.5.42 SMB 166 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: \dir1\dir11 10.160.5.42 -> 10.160.67.86 SMB 242 Trans2 Response, QUERY_PATH_INFO (2) 10.160.67.86 -> 10.160.5.42 SMB 144 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: 10.160.5.42 -> 10.160.67.86 SMB 220 Trans2 Response, QUERY_PATH_INFO 10.160.67.86 -> 10.160.5.42 SMB 144 Trans2 Request, QUERY_PATH_INFO, Query File Internal Info, Path: 10.160.5.42 -> 10.160.67.86 SMB 138 Trans2 Response, QUERY_PATH_INFO 10.160.67.86 -> 10.160.5.42 SMB 154 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: \dir1 10.160.5.42 -> 10.160.67.86 SMB 230 Trans2 Response, QUERY_PATH_INFO 10.160.67.86 -> 10.160.5.42 SMB 154 Trans2 Request, QUERY_PATH_INFO, Query File Internal Info, Path: \dir1 10.160.5.42 -> 10.160.67.86 SMB 138 Trans2 Response, QUERY_PATH_INFO 10.160.67.86 -> 10.160.5.42 SMB 166 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: \dir1\dir11 10.160.5.42 -> 10.160.67.86 SMB 242 Trans2 Response, QUERY_PATH_INFO 10.160.67.86 -> 10.160.5.42 SMB 166 Trans2 Request, QUERY_PATH_INFO, Query File Internal Info, Path: \dir1\dir11 10.160.5.42 -> 10.160.67.86 SMB 138 Trans2 Response, QUERY_PATH_INFO
At step (1) we check whether path is accessible, at step (2) we walk
the path and do queries on each components. These queries are made to
give an inode to the components dentry
.
Code for (2) is the cifs_get_root()
function:
/* * Get root dentry from superblock according to prefix path mount option. * Return dentry with refcount + 1 on success and NULL otherwise. */ static struct dentry * cifs_get_root(struct smb_vol *vol, struct super_block *sb) { struct dentry *dentry; struct cifs_sb_info *cifs_sb = CIFS_SB(sb); char *full_path = NULL; char *s, *p; char sep; full_path = cifs_build_path_to_root(vol, cifs_sb, cifs_sb_master_tcon(cifs_sb)); if (full_path == NULL) return ERR_PTR(-ENOMEM); cifs_dbg(FYI, "Get root dentry for %s\n", full_path); sep = CIFS_DIR_SEP(cifs_sb); dentry = dget(sb->s_root); p = s = full_path; do { struct inode *dir = d_inode(dentry); // (2.1) struct dentry *child; if (!dir) { dput(dentry); dentry = ERR_PTR(-ENOENT); break; } if (!S_ISDIR(dir->i_mode)) { dput(dentry); dentry = ERR_PTR(-ENOTDIR); break; } /* skip separators */ while (*s == sep) s++; if (!*s) break; p = s++; /* next separator */ while (*s && *s != sep) s++; child = lookup_one_len_unlocked(p, dentry, s - p); // (2.2) dput(dentry); dentry = child; } while (!IS_ERR(dentry)); kfree(full_path); return dentry; }
At step (2.1) we look at the dentry associated inode. At the first
iteration, dir
is the inode of the share root which we successfully
got earlier. In (2.2) we query the next path component (triggers
QUERY_PATH_INFO
requests on the wire) and loop again.
1.3 When it fails
The issue is that you can set up permissions on a Windows servers where a restricted user is not allowed to query paths above his, so the mounting process fails because you can't query a path above yours which you were not interested in in the first place.
Here's a sample network trace of the problem (same setup, mounting
//LUTZE/sspshare/dir1/dir11
as user LURCH/bill
):
10.160.64.22 -> 10.160.5.42 SMB 139 Negotiate Protocol Request 10.160.5.42 -> 10.160.64.22 SMB 171 Negotiate Protocol Response 10.160.64.22 -> 10.160.5.42 SMB 296 Session Setup AndX Request, User: LURCH\bill 10.160.5.42 -> 10.160.64.22 SMB 269 Session Setup AndX Response 10.160.64.22 -> 10.160.5.42 SMB 154 Tree Connect AndX Request, Path: \\lutze\sspshare 10.160.5.42 -> 10.160.64.22 SMB 132 Tree Connect AndX Response 10.160.64.22 -> 10.160.5.42 SMB 138 Trans2 Request, QUERY_FS_INFO, Query FS Device Info 10.160.5.42 -> 10.160.64.22 SMB 134 Trans2 Response, QUERY_FS_INFO 10.160.64.22 -> 10.160.5.42 SMB 138 Trans2 Request, QUERY_FS_INFO, Query FS Attribute Info 10.160.5.42 -> 10.160.64.22 SMB 146 Trans2 Response, QUERY_FS_INFO 10.160.64.22 -> 10.160.5.42 SMB 158 Tree Connect AndX Request, Path: \\10.160.5.42\IPC$ 10.160.5.42 -> 10.160.64.22 SMB 126 Tree Connect AndX Response 10.160.64.22 -> 10.160.5.42 SMB 172 Trans2 Request, GET_DFS_REFERRAL, File: \lutze\sspshare 10.160.5.42 -> 10.160.64.22 SMB 105 Trans2 Response, GET_DFS_REFERRAL, Error: STATUS_NOT_FOUND 10.160.64.22 -> 10.160.5.42 SMB 166 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: \dir1\dir11 10.160.5.42 -> 10.160.64.22 SMB 242 Trans2 Response, QUERY_PATH_INFO 10.160.64.22 -> 10.160.5.42 SMB 144 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: 10.160.5.42 -> 10.160.64.22 SMB 220 Trans2 Response, QUERY_PATH_INFO 10.160.64.22 -> 10.160.5.42 SMB 144 Trans2 Request, QUERY_PATH_INFO, Query File Internal Info, Path: 10.160.5.42 -> 10.160.64.22 SMB 138 Trans2 Response, QUERY_PATH_INFO 10.160.64.22 -> 10.160.5.42 SMB 154 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: \dir1 10.160.5.42 -> 10.160.64.22 SMB 105 Trans2 Response, QUERY_PATH_INFO, Error: STATUS_OBJECT_NAME_NOT_FOUND 10.160.64.22 -> 10.160.5.42 SMB 105 Tree Disconnect Request 10.160.5.42 -> 10.160.64.22 SMB 105 Tree Disconnect Response 10.160.64.22 -> 10.160.5.42 SMB 109 Logoff AndX Request 10.160.5.42 -> 10.160.64.22 SMB 109 Logoff AndX Response
Since the query on dir1
fails, we cannot create the respective
inode, we exit early from cifs_get_root()
and the whole mounting fails.
This is has been broken since 2011.
2 Windows Server 2012 R2 setup
The setup on LUTZE (Windows Server 2012 R2) is the following.
C:\sspshare (shared as "sspshare") | + dir1 | + dir11 (only accessible dir for LURCH\bill)
The permissions set on the server are neither C:\sspshare
nor
C:\sspshare\dir1
are accessible to LURCH\bill
. LURCH\bill
only
has full access to sspshare\dir1\dir11
.
Here is the icacls
dump of all the directories involved:
sspshare LURCH\bill:(N) LURCH\administrator:(OI)(CI)(F) BUILTIN\Administrators:(OI)(CI)(F) sspshare/dir1 LURCH\bill:(N) LURCH\administrator:(OI)(CI)(F) BUILTIN\Administrators:(OI)(CI)(F) sspshare/dir1/dir11 LURCH\bill:(OI)(CI)(F) LURCH\administrator:(OI)(CI)(F) BUILTIN\Administrators:(OI)(CI)(F)
2.1 Mounting as Administrator
If we mount as Administrator, it works. Here's the corresponding trace:
10.160.5.42 -> 10.160.64.110 SMB 275 Negotiate Protocol Response 10.160.64.110 -> 10.160.5.42 SMB 256 Session Setup AndX Request, NTLMSSP_NEGOTIATE 10.160.5.42 -> 10.160.64.110 SMB 471 Session Setup AndX Response, NTLMSSP_CHALLENGE, Error: STATUS_MORE_PROCESSING_REQUIRED 10.160.64.110 -> 10.160.5.42 SMB 532 Session Setup AndX Request, NTLMSSP_AUTH, User: LURCH\administrator 10.160.5.42 -> 10.160.64.110 SMB 259 Session Setup AndX Response 10.160.64.110 -> 10.160.5.42 SMB 154 Tree Connect AndX Request, Path: \\lutze\sspshare 10.160.5.42 -> 10.160.64.110 SMB 132 Tree Connect AndX Response 10.160.64.110 -> 10.160.5.42 SMB 138 Trans2 Request, QUERY_FS_INFO, Query FS Device Info 10.160.5.42 -> 10.160.64.110 SMB 134 Trans2 Response, QUERY_FS_INFO 10.160.64.110 -> 10.160.5.42 SMB 138 Trans2 Request, QUERY_FS_INFO, Query FS Attribute Info 10.160.5.42 -> 10.160.64.110 SMB 146 Trans2 Response, QUERY_FS_INFO 10.160.64.110 -> 10.160.5.42 SMB 158 Tree Connect AndX Request, Path: \\10.160.5.42\IPC$ 10.160.5.42 -> 10.160.64.110 SMB 126 Tree Connect AndX Response 10.160.64.110 -> 10.160.5.42 SMB 172 Trans2 Request, GET_DFS_REFERRAL, File: \lutze\sspshare 10.160.5.42 -> 10.160.64.110 SMB 105 Trans2 Response, GET_DFS_REFERRAL, Error: STATUS_NOT_FOUND (1) 10.160.64.110 -> 10.160.5.42 SMB 166 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: \dir1\dir11 10.160.5.42 -> 10.160.64.110 SMB 242 Trans2 Response, QUERY_PATH_INFO 10.160.64.110 -> 10.160.5.42 SMB 144 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: 10.160.5.42 -> 10.160.64.110 SMB 220 Trans2 Response, QUERY_PATH_INFO 10.160.64.110 -> 10.160.5.42 SMB 144 Trans2 Request, QUERY_PATH_INFO, Query File Internal Info, Path: 10.160.5.42 -> 10.160.64.110 SMB 138 Trans2 Response, QUERY_PATH_INFO (2) 10.160.64.110 -> 10.160.5.42 SMB 154 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: \dir1 10.160.5.42 -> 10.160.64.110 SMB 230 Trans2 Response, QUERY_PATH_INFO 10.160.64.110 -> 10.160.5.42 SMB 154 Trans2 Request, QUERY_PATH_INFO, Query File Internal Info, Path: \dir1 10.160.5.42 -> 10.160.64.110 SMB 138 Trans2 Response, QUERY_PATH_INFO 10.160.64.110 -> 10.160.5.42 SMB 166 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: \dir1\dir11 10.160.5.42 -> 10.160.64.110 SMB 242 Trans2 Response, QUERY_PATH_INFO 10.160.64.110 -> 10.160.5.42 SMB 166 Trans2 Request, QUERY_PATH_INFO, Query File Internal Info, Path: \dir1\dir11 10.160.5.42 -> 10.160.64.110 SMB 138 Trans2 Response, QUERY_PATH_INFO
cifs.ko
does a query on the final path (1) first and then walks on
each path component (2), from the root to the final path (/
,
/dir1
, /dir1/dir11/
).
2.2 Mounting as restricted user
dir11
should be mountable using bill credentials, but it's not:
mount.cifs kernel mount options: ip=10.160.5.42,unc=\\LUTZE\sspshare,user=bill,,domain=LURCH,prefixpath=dir1/dir11,pass=******** mount error(2): No such file or directory Refer to the mount.cifs(8) manual page (e.g. man mount.cifs)
The corresponding network trace:
10.160.66.120 -> 10.160.5.42 SMB 139 Negotiate Protocol Request 10.160.5.42 -> 10.160.66.120 SMB 275 Negotiate Protocol Response 10.160.66.120 -> 10.160.5.42 SMB 256 Session Setup AndX Request, NTLMSSP_NEGOTIATE 10.160.5.42 -> 10.160.66.120 SMB 471 Session Setup AndX Response, NTLMSSP_CHALLENGE, Error: STATUS_MORE_PROCESSING_REQUIRED 10.160.66.120 -> 10.160.5.42 SMB 514 Session Setup AndX Request, NTLMSSP_AUTH, User: LURCH\bill 10.160.5.42 -> 10.160.66.120 SMB 259 Session Setup AndX Response 10.160.66.120 -> 10.160.5.42 SMB 154 Tree Connect AndX Request, Path: \\lutze\sspshare 10.160.5.42 -> 10.160.66.120 SMB 132 Tree Connect AndX Response 10.160.66.120 -> 10.160.5.42 SMB 138 Trans2 Request, QUERY_FS_INFO, Query FS Device Info 10.160.5.42 -> 10.160.66.120 SMB 134 Trans2 Response, QUERY_FS_INFO 10.160.66.120 -> 10.160.5.42 SMB 138 Trans2 Request, QUERY_FS_INFO, Query FS Attribute Info 10.160.5.42 -> 10.160.66.120 SMB 146 Trans2 Response, QUERY_FS_INFO 10.160.66.120 -> 10.160.5.42 SMB 158 Tree Connect AndX Request, Path: \\10.160.5.42\IPC$ 10.160.5.42 -> 10.160.66.120 SMB 126 Tree Connect AndX Response 10.160.66.120 -> 10.160.5.42 SMB 172 Trans2 Request, GET_DFS_REFERRAL, File: \lutze\sspshare 10.160.5.42 -> 10.160.66.120 SMB 105 Trans2 Response, GET_DFS_REFERRAL, Error: STATUS_NOT_FOUND (1) 10.160.66.120 -> 10.160.5.42 SMB 166 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: \dir1\dir11 10.160.5.42 -> 10.160.66.120 SMB 242 Trans2 Response, QUERY_PATH_INFO 10.160.66.120 -> 10.160.5.42 SMB 144 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: 10.160.5.42 -> 10.160.66.120 SMB 220 Trans2 Response, QUERY_PATH_INFO 10.160.66.120 -> 10.160.5.42 SMB 144 Trans2 Request, QUERY_PATH_INFO, Query File Internal Info, Path: 10.160.5.42 -> 10.160.66.120 SMB 138 Trans2 Response, QUERY_PATH_INFO (2) 10.160.66.120 -> 10.160.5.42 SMB 154 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: \dir1 10.160.5.42 -> 10.160.66.120 SMB 105 Trans2 Response, QUERY_PATH_INFO, Error: STATUS_OBJECT_NAME_NOT_FOUND 10.160.66.120 -> 10.160.5.42 SMB 105 Tree Disconnect Request 10.160.5.42 -> 10.160.66.120 SMB 105 Tree Disconnect Response 10.160.66.120 -> 10.160.5.42 SMB 109 Logoff AndX Request 10.160.5.42 -> 10.160.66.120 SMB 109 Logoff AndX Response
cifs.ko
does a query on the final path (1) first and then walks on
each path component (2), from the root to the final path (/
,
/dir1
, /dir1/dir11/
). But here it stops at the query of the
component /dir1
and mouting fails. Note the error status:
STATUS_OBJECT_NAME_NOT_FOUND
instead of the expected "access
denied".
2.3 Listing with smbclient as restricted user
Listing dir1/dir11
as bill
with smbclient
works properly:
% smbclient //lutze/sspshare -U 'LURCH\bill%xxxx' -D dir1/dir11 -c ls Domain=[LURCH] OS=[Windows Server 2012 R2 Standard 9600] Server=[Windows Server 2012 R2 Standard 6.3] . D 0 Fri Apr 29 16:01:44 2016 .. D 0 Fri Apr 29 16:01:44 2016 dir111 D 0 Tue Sep 2 04:57:29 2014 file111 A 20 Tue Sep 2 04:57:22 2014 17830399 blocks of size 4096. 5417534 blocks available
Corresponding network trace:
10.160.64.110 -> 10.160.5.42 SMB 280 Negotiate Protocol Request 10.160.5.42 -> 10.160.64.110 SMB 295 Negotiate Protocol Response 10.160.64.110 -> 10.160.5.42 SMB 246 Session Setup AndX Request, NTLMSSP_NEGOTIATE 10.160.5.42 -> 10.160.64.110 SMB 522 Session Setup AndX Response, NTLMSSP_CHALLENGE, Error: STATUS_MORE_PROCESSING_REQUIRED 10.160.64.110 -> 10.160.5.42 SMB 664 Session Setup AndX Request, NTLMSSP_AUTH, User: LURCH\bill 10.160.5.42 -> 10.160.64.110 SMB 308 Session Setup AndX Response 10.160.64.110 -> 10.160.5.42 SMB 164 Tree Connect AndX Request, Path: \\LUTZE\IPC$ 10.160.5.42 -> 10.160.64.110 SMB 146 Tree Connect AndX Response 10.160.64.110 -> 10.160.5.42 SMB 194 Trans2 Request, GET_DFS_REFERRAL, File: \lutze\sspshare 10.160.5.42 -> 10.160.64.110 SMB 125 Trans2 Response, GET_DFS_REFERRAL, Error: STATUS_NOT_FOUND 10.160.64.110 -> 10.160.5.42 SMB 125 Tree Disconnect Request 10.160.5.42 -> 10.160.64.110 SMB 125 Tree Disconnect Response 10.160.64.110 -> 10.160.5.42 SMB 174 Tree Connect AndX Request, Path: \\LUTZE\SSPSHARE 10.160.5.42 -> 10.160.64.110 SMB 152 Tree Connect AndX Response (1) 10.160.64.110 -> 10.160.5.42 SMB 190 Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path: \dir1\dir11\ 10.160.5.42 -> 10.160.64.110 SMB 190 Trans2 Response, QUERY_PATH_INFO 10.160.64.110 -> 10.160.5.42 SMB 198 Trans2 Request, FIND_FIRST2, Pattern: \dir1\dir11\* 10.160.5.42 -> 10.160.64.110 SMB 586 Trans2 Response, FIND_FIRST2, Files: . .. dir111 file111 10.160.64.110 -> 10.160.5.42 SMB 162 Trans2 Request, QUERY_FS_INFO, Query Full FS Size Info 10.160.5.42 -> 10.160.64.110 SMB 178 Trans2 Response, QUERY_FS_INFO 10.160.64.110 -> 10.160.5.42 SMB 125 Tree Disconnect Request 10.160.5.42 -> 10.160.64.110 SMB 125 Tree Disconnect Response
smbclient
directly queries the final path (1) and has no problem listing it.
3 Windows 10 setup
I've tried to reproduce it on a Windows 10 server (ZOMBOCOM):
C:\share
is shared the same way as LUTZE's share.aaptel
has admin rights.test
is the restricted user.
The ACL are identical:
share ZOMBOCOM\test:(N) ZOMBOCOM\Administrator:(OI)(CI)(F) BUILTIN\Administrators:(OI)(CI)(F) ZOMBOCOM\aaptel:(OI)(CI)(F) share/dir1 ZOMBOCOM\test:(N) ZOMBOCOM\aaptel:(OI)(CI)(F) share/dir1/dir11 ZOMBOCOM\test:(OI)(CI)(F) ZOMBOCOM\aaptel:(OI)(CI)(F)
3.1 Mounting as Administrator
Works, as expected. Same trace as with Windows Server 2012 R2.
10.160.65.51 -> 10.160.64.239 SMB 139 Negotiate Protocol Request 10.160.64.239 -> 10.160.65.51 SMB 432 Negotiate Protocol Response 10.160.65.51 -> 10.160.64.239 SMB 256 Session Setup AndX Request, NTLMSSP_NEGOTIATE 10.160.64.239 -> 10.160.65.51 SMB 389 Session Setup AndX Response, NTLMSSP_CHALLENGE, Error: STATUS_MORE_PROCESSING_REQUIRED 10.160.65.51 -> 10.160.64.239 SMB 474 Session Setup AndX Request, NTLMSSP_AUTH, User: ZOMBOCOM\aaptel 10.160.64.239 -> 10.160.65.51 SMB 221 Session Setup AndX Response 10.160.65.51 -> 10.160.64.239 SMB 154 Tree Connect AndX Request, Path: \\zombocom\share 10.160.64.239 -> 10.160.65.51 SMB 132 Tree Connect AndX Response 10.160.65.51 -> 10.160.64.239 SMB 138 Trans2 Request, QUERY_FS_INFO, Query FS Device Info 10.160.64.239 -> 10.160.65.51 SMB 134 Trans2 Response, QUERY_FS_INFO 10.160.65.51 -> 10.160.64.239 SMB 138 Trans2 Request, QUERY_FS_INFO, Query FS Attribute Info 10.160.64.239 -> 10.160.65.51 SMB 146 Trans2 Response, QUERY_FS_INFO 10.160.65.51 -> 10.160.64.239 SMB 162 Tree Connect AndX Request, Path: \\10.160.64.239\IPC$ 10.160.64.239 -> 10.160.65.51 SMB 126 Tree Connect AndX Response 10.160.65.51 -> 10.160.64.239 SMB 172 Trans2 Request, GET_DFS_REFERRAL, File: \zombocom\share 10.160.64.239 -> 10.160.65.51 SMB 105 Trans2 Response, GET_DFS_REFERRAL, Error: STATUS_FS_DRIVER_REQUIRED (1) 10.160.65.51 -> 10.160.64.239 SMB 166 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: \dir1\dir11 10.160.64.239 -> 10.160.65.51 SMB 236 Trans2 Response, QUERY_PATH_INFO 10.160.65.51 -> 10.160.64.239 SMB 144 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: 10.160.64.239 -> 10.160.65.51 SMB 214 Trans2 Response, QUERY_PATH_INFO 10.160.65.51 -> 10.160.64.239 SMB 144 Trans2 Request, QUERY_PATH_INFO, Query File Internal Info, Path: 10.160.64.239 -> 10.160.65.51 SMB 138 Trans2 Response, QUERY_PATH_INFO (2) 10.160.65.51 -> 10.160.64.239 SMB 154 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: \dir1 10.160.64.239 -> 10.160.65.51 SMB 224 Trans2 Response, QUERY_PATH_INFO 10.160.65.51 -> 10.160.64.239 SMB 154 Trans2 Request, QUERY_PATH_INFO, Query File Internal Info, Path: \dir1 10.160.64.239 -> 10.160.65.51 SMB 138 Trans2 Response, QUERY_PATH_INFO 10.160.65.51 -> 10.160.64.239 SMB 166 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: \dir1\dir11 10.160.64.239 -> 10.160.65.51 SMB 236 Trans2 Response, QUERY_PATH_INFO 10.160.65.51 -> 10.160.64.239 SMB 166 Trans2 Request, QUERY_PATH_INFO, Query File Internal Info, Path: \dir1\dir11 10.160.64.239 -> 10.160.65.51 SMB 138 Trans2 Response, QUERY_PATH_INFO
3.2 Mounting as restricted user
Here it also fails.
mount.cifs kernel mount options: ip=10.160.64.239,unc=\\zombocom\share,user=test,prefixpath=dir1/dir11,pass=******** mount error(13): Permission denied Refer to the mount.cifs(8) manual page (e.g. man mount.cifs)
10.160.64.241 -> 10.160.64.239 SMB 139 Negotiate Protocol Request 10.160.64.239 -> 10.160.64.241 SMB 432 Negotiate Protocol Response 10.160.64.241 -> 10.160.64.239 SMB 256 Session Setup AndX Request, NTLMSSP_NEGOTIATE 10.160.64.239 -> 10.160.64.241 SMB 389 Session Setup AndX Response, NTLMSSP_CHALLENGE, Error: STATUS_MORE_PROCESSING_REQUIRED 10.160.64.241 -> 10.160.64.239 SMB 470 Session Setup AndX Request, NTLMSSP_AUTH, User: ZOMBOCOM\test 10.160.64.239 -> 10.160.64.241 SMB 221 Session Setup AndX Response 10.160.64.241 -> 10.160.64.239 SMB 154 Tree Connect AndX Request, Path: \\zombocom\share 10.160.64.239 -> 10.160.64.241 SMB 132 Tree Connect AndX Response 10.160.64.241 -> 10.160.64.239 SMB 138 Trans2 Request, QUERY_FS_INFO, Query FS Device Info 10.160.64.239 -> 10.160.64.241 SMB 134 Trans2 Response, QUERY_FS_INFO 10.160.64.241 -> 10.160.64.239 SMB 138 Trans2 Request, QUERY_FS_INFO, Query FS Attribute Info 10.160.64.239 -> 10.160.64.241 SMB 146 Trans2 Response, QUERY_FS_INFO 10.160.64.241 -> 10.160.64.239 SMB 162 Tree Connect AndX Request, Path: \\10.160.64.239\IPC$ 10.160.64.239 -> 10.160.64.241 SMB 126 Tree Connect AndX Response 10.160.64.241 -> 10.160.64.239 SMB 172 Trans2 Request, GET_DFS_REFERRAL, File: \zombocom\share 10.160.64.239 -> 10.160.64.241 SMB 105 Trans2 Response, GET_DFS_REFERRAL, Error: STATUS_FS_DRIVER_REQUIRED (1) 10.160.64.241 -> 10.160.64.239 SMB 166 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: \dir1\dir11 10.160.64.239 -> 10.160.64.241 SMB 236 Trans2 Response, QUERY_PATH_INFO 10.160.64.241 -> 10.160.64.239 SMB 144 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: 10.160.64.239 -> 10.160.64.241 SMB 214 Trans2 Response, QUERY_PATH_INFO 10.160.64.241 -> 10.160.64.239 SMB 144 Trans2 Request, QUERY_PATH_INFO, Query File Internal Info, Path: 10.160.64.239 -> 10.160.64.241 SMB 138 Trans2 Response, QUERY_PATH_INFO (2) 10.160.64.241 -> 10.160.64.239 SMB 154 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: \dir1 10.160.64.239 -> 10.160.64.241 SMB 105 Trans2 Response, QUERY_PATH_INFO, Error: STATUS_ACCESS_DENIED 10.160.64.241 -> 10.160.64.239 SMB 105 Tree Disconnect Request 10.160.64.239 -> 10.160.64.241 SMB 105 Tree Disconnect Response 10.160.64.241 -> 10.160.64.239 SMB 109 Logoff AndX Request 10.160.64.239 -> 10.160.64.241 SMB 109 Logoff AndX Response
Same pattern: final path query (1) then path components queries (2). Stops at first failure. The error status however is the expected "access denied" unlike Windows Server 2012 R2 "object not found" status.
4 Samba setup
Traditional UNIX permissions work like this on directories:
r
: you can list the content of the directory (file names but no metadata)w
: you can rename/add/delete files in the directoryx
: you can =cd=/enter/traverse the directory (this affects the subdirs)
So the corresponding setup would be:
# cd /tmp # mkdir -p share/dir1/dir11 # touch share/dir1/dir11/{a,b,c} # chmod 777 share/dir1/dir11 # chmod 711 share/dir1 share # useradd test # smbpasswd -a test # echo -e "[share]\npath = /tmp/share\n" >> /etc/samba/smb.conf # systemctl restart smb
But Samba lets clients do query on intermediary paths (mount + ls as user test
):
10.160.65.48 -> 10.160.66.105 SMB 166 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: \dir1\dir11 10.160.66.105 -> 10.160.65.48 SMB 224 Trans2 Response, QUERY_PATH_INFO 10.160.65.48 -> 10.160.66.105 SMB 144 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: 10.160.66.105 -> 10.160.65.48 SMB 204 Trans2 Response, QUERY_PATH_INFO 10.160.65.48 -> 10.160.66.105 SMB 144 Trans2 Request, QUERY_PATH_INFO, Query File Internal Info, Path: 10.160.66.105 -> 10.160.65.48 SMB 138 Trans2 Response, QUERY_PATH_INFO 10.160.65.48 -> 10.160.66.105 SMB 154 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: \dir1 10.160.66.105 -> 10.160.65.48 SMB 212 Trans2 Response, QUERY_PATH_INFO 10.160.65.48 -> 10.160.66.105 SMB 154 Trans2 Request, QUERY_PATH_INFO, Query File Internal Info, Path: \dir1 10.160.66.105 -> 10.160.65.48 SMB 138 Trans2 Response, QUERY_PATH_INFO 10.160.65.48 -> 10.160.66.105 SMB 166 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: \dir1\dir11 10.160.66.105 -> 10.160.65.48 SMB 224 Trans2 Response, QUERY_PATH_INFO 10.160.65.48 -> 10.160.66.105 SMB 166 Trans2 Request, QUERY_PATH_INFO, Query File Internal Info, Path: \dir1\dir11 10.160.66.105 -> 10.160.65.48 SMB 138 Trans2 Response, QUERY_PATH_INFO 10.160.65.48 -> 10.160.66.105 SMB 166 Trans2 Request, QUERY_PATH_INFO, Query File All Info, Path: \dir1\dir11 10.160.66.105 -> 10.160.65.48 SMB 224 Trans2 Response, QUERY_PATH_INFO 10.160.65.48 -> 10.160.66.105 SMB 176 Trans2 Request, FIND_FIRST2, Pattern: \dir1\dir11\* 10.160.66.105 -> 10.160.65.48 SMB 558 Trans2 Response, FIND_FIRST2, Files: . .. c b a
If you remove the x
bit of any of the parents directory of dir11
it fails i.e. you cannot reach dir11
: (smbclient + cd + ls):
15 0.005000000 10.160.4.17 -> 10.160.66.105 SMB 170 Trans2 Request, QUERY_PATH_INFO, Query File Basic Info, Path: \dir1\dir11\ 16 0.005057000 10.160.66.105 -> 10.160.4.17 SMB 105 Trans2 Response, QUERY_PATH_INFO, Error: STATUS_ACCESS_DENIED
What we wanted was to forbid access only on the root but that forbids it on the whole path.
Conclusion: it seems we cannot use Samba to reproduce the problem. We could write a VFS to reproduce it, maybe.
4.1 Script to reproduce the setup
I've written a PowerShell script to reproduce the problematic setup on a Windows machine.
On the window server:
- Edit
$Dir
(script will create parent dirs) - Edit
$LimitedUser
and$AdminUser
to existing ones - Run the script as admin
On the linux client:
- Mount the share sub dir with the limited user credentials:
mount //lutze/bug8950/sub/dir' /mnt \ -o 'domain=LURCH,ip=10.160.5.42,username=bill,password=*****,rw'
- The expected result is a successful mount (you can ls
/mnt
and see the file). - Instead we currently get a mount error (permission denied or file not found).
5 The fix
5.1 Solution 1: create a disconnected root dentry
What Shirish Pargaonkar proposed (2014-03-12 17:42:32 UTC):
If during mounting a share, if the share path is accessible but if any of the intermediate paths to the share is inaccessible i.e. returns error EACCES,
- get inode info for the share path using query path info
- look for a dentry and if not found, add - using
d_obtain_alias()
- if server does not support unique ids/inode numbers, add to the begining of the list maintained in superblock, an element consisting of full path, pointer to this dentry, and a pointer to the inode.
During lookup, for a case where server does support unique ids/inode numbers,
- obtain inode info using query path info
- splice (
d_splice_alias()
) the dentry corrosponding to this inode if any. if spliced, remove the element from the list maintained in the superblock.for a case where server does not support unique id/inode number,
- search for an entry for inode based on the full path if no such entry, obtain inode info using query path info
- splice (
d_splice_alias()
) the dentry corrosponding to this inode if any if successful, remove the element from the list maintained in the superblock.That is one way an element will come off of the list maintained in the superblock. If the dentry does not get ever get spliced, whenever either it is deleted with zero
d_count
(unmount e.g.) or it is released (superblock is being deleted), cifsd_delete
andcifs_d_release
respectively, whichever applies first, will take the element off the list and free it.If the superblock is intact and anonymous root dentry does not get deleted during unmount (
d_count
is not zero i.e. has child dentries with referenece to the parent) i.e.d_delete
does not get called, the dentries would remain till vfs code deletes them as needed (as theird_count
starts approaching zero).Whenever we have the same mount again with a anonymous dentry, the elements are added to the head of the list maintained in the superblock, so stale elements will not be reachable.
I took Shirish's patch and made it work:
- patch
build_path_from_dentry()
to look for disconnected dentry and use their stored path. - trigger the patch on
ENOENT
errors - when searching by dentry in the disconnected list, don't remove the element if it is found.
- needs
noserverino
mount options.
- fix1/0001-cifs-create-a-root-dentry-inode-for-EACCES-error.patch
- fix1/0002-cifs-shared_superblock-data-structures-and-functions.patch
- fix1/0003-cifs-shared_superblock-various-routines-to-search-di.patch
- fix1/0004-cifs-shared_superblock-create-a-dentry-as-a-disconne.patch
- fix1/0005-cifs-shared_superblock-splice-disconnected-dentry.patch
- fix1/0006-cifs-create-disconnected-dentry-on-ENOENT.patch
- fix1/0007-cifs-add-find_rdelem_by_dentry_no_del.patch
- fix1/0008-cifs-prefix-path-with-disconnected-root-paths.patch
5.2 Solution 2: revert to old prefix-path method when needed
When the normal method fails (where superblock root == share root) switch back to the previous memory layout.
if, when mounting //HOST/share/sub/dir/foo we can query /sub/dir/foo but not any of the path components above:
- store the /sub/dir/foo prefix in the cifs super_block info
- in the superblock, set root dentry to the subpath dentry (instead of the share root), same for the inode
- set a flag in the superblock to remember it
- use prefixpath when building path from a dentry
This method is much simpler.