cifs: quirk for STATUS_OBJECT_NAME_INVALID returned for non-ASCII dfs refs
authorEugene Korenevsky <ekorenevsky@astralinux.ru>
Fri, 14 Jan 2022 19:53:40 +0000 (22:53 +0300)
committerSteve French <stfrench@microsoft.com>
Mon, 17 Jan 2022 19:28:25 +0000 (13:28 -0600)
Windows SMB server responds with STATUS_OBJECT_NAME_INVALID code to
SMB2 QUERY_INFO request for "\<server>\<dfsname>\<linkpath>" DFS reference,
where <dfsname> contains non-ASCII unicode symbols.

Check such DFS reference and emulate -EREMOTE if it is actual.

BugLink: https://bugzilla.kernel.org/show_bug.cgi?id=215440
Signed-off-by: Eugene Korenevsky <ekorenevsky@astralinux.ru>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/cifs/cifsproto.h
fs/cifs/connect.c
fs/cifs/inode.c
fs/cifs/misc.c

index e0dc147e69a85820b2c2a60cbba8496b2ab76cd8..f2029bc46215bf40c63566f5907b4986a2947581 100644 (file)
@@ -647,6 +647,11 @@ static inline int get_dfs_path(const unsigned int xid, struct cifs_ses *ses,
 int match_target_ip(struct TCP_Server_Info *server,
                    const char *share, size_t share_len,
                    bool *result);
+
+int cifs_dfs_query_info_nonascii_quirk(const unsigned int xid,
+                                      struct cifs_tcon *tcon,
+                                      struct cifs_sb_info *cifs_sb,
+                                      const char *dfs_link_path);
 #endif
 
 static inline int cifs_create_options(struct cifs_sb_info *cifs_sb, int options)
index 0f36deff790ec53f5460d2d9d729507861ce0648..accce1b351c6f784dac3bd4dcba81bb3b70ee0a3 100644 (file)
@@ -3374,6 +3374,11 @@ static int is_path_remote(struct mount_ctx *mnt_ctx)
 
        rc = server->ops->is_path_accessible(xid, tcon, cifs_sb,
                                             full_path);
+#ifdef CONFIG_CIFS_DFS_UPCALL
+       if (rc == -ENOENT && is_tcon_dfs(tcon))
+               rc = cifs_dfs_query_info_nonascii_quirk(xid, tcon, cifs_sb,
+                                                       full_path);
+#endif
        if (rc != 0 && rc != -EREMOTE) {
                kfree(full_path);
                return rc;
index 279622e4eb1c290b41d017bda10cb5cda93b5575..baa197edd8c59167478786967c8e51d24d8ee60c 100644 (file)
@@ -952,6 +952,12 @@ cifs_get_inode_info(struct inode **inode,
                rc = server->ops->query_path_info(xid, tcon, cifs_sb,
                                                 full_path, tmp_data,
                                                 &adjust_tz, &is_reparse_point);
+#ifdef CONFIG_CIFS_DFS_UPCALL
+               if (rc == -ENOENT && is_tcon_dfs(tcon))
+                       rc = cifs_dfs_query_info_nonascii_quirk(xid, tcon,
+                                                               cifs_sb,
+                                                               full_path);
+#endif
                data = tmp_data;
        }
 
index 5148d48d6a357e0f231ce1e278d0c67ec42bacc9..56598f7dbe00d8ba3001c52f31141096c85d226b 100644 (file)
@@ -1302,4 +1302,53 @@ int cifs_update_super_prepath(struct cifs_sb_info *cifs_sb, char *prefix)
        cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH;
        return 0;
 }
+
+/** cifs_dfs_query_info_nonascii_quirk
+ * Handle weird Windows SMB server behaviour. It responds with
+ * STATUS_OBJECT_NAME_INVALID code to SMB2 QUERY_INFO request
+ * for "\<server>\<dfsname>\<linkpath>" DFS reference,
+ * where <dfsname> contains non-ASCII unicode symbols.
+ *
+ * Check such DFS reference and emulate -ENOENT if it is actual.
+ */
+int cifs_dfs_query_info_nonascii_quirk(const unsigned int xid,
+                                      struct cifs_tcon *tcon,
+                                      struct cifs_sb_info *cifs_sb,
+                                      const char *linkpath)
+{
+       char *treename, *dfspath, sep;
+       int treenamelen, linkpathlen, rc;
+
+       treename = tcon->treeName;
+       /* MS-DFSC: All paths in REQ_GET_DFS_REFERRAL and RESP_GET_DFS_REFERRAL
+        * messages MUST be encoded with exactly one leading backslash, not two
+        * leading backslashes.
+        */
+       sep = CIFS_DIR_SEP(cifs_sb);
+       if (treename[0] == sep && treename[1] == sep)
+               treename++;
+       linkpathlen = strlen(linkpath);
+       treenamelen = strnlen(treename, MAX_TREE_SIZE + 1);
+       dfspath = kzalloc(treenamelen + linkpathlen + 1, GFP_KERNEL);
+       if (!dfspath)
+               return -ENOMEM;
+       if (treenamelen)
+               memcpy(dfspath, treename, treenamelen);
+       memcpy(dfspath + treenamelen, linkpath, linkpathlen);
+       rc = dfs_cache_find(xid, tcon->ses, cifs_sb->local_nls,
+                           cifs_remap(cifs_sb), dfspath, NULL, NULL);
+       if (rc == 0) {
+               cifs_dbg(FYI, "DFS ref '%s' is found, emulate -EREMOTE\n",
+                        dfspath);
+               rc = -EREMOTE;
+       } else if (rc == -EEXIST) {
+               cifs_dbg(FYI, "DFS ref '%s' is not found, emulate -ENOENT\n",
+                        dfspath);
+               rc = -ENOENT;
+       } else {
+               cifs_dbg(FYI, "%s: dfs_cache_find returned %d\n", __func__, rc);
+       }
+       kfree(dfspath);
+       return rc;
+}
 #endif