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 directory
  • x: 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), cifs d_delete and cifs_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 their d_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.

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.

Author: Aurélien Aptel <aaptel@suse.com>

Created: 2016-06-13 lun. 11:56

Emacs 24.5.1 (Org mode 8.2.10)

Validate