cifs: fix path comparison and hash calc
authorPaulo Alcantara <pc@cjr.nz>
Fri, 4 Jun 2021 22:25:31 +0000 (19:25 -0300)
committerSteve French <stfrench@microsoft.com>
Mon, 21 Jun 2021 02:28:17 +0000 (21:28 -0500)
Fix cache lookup and hash calculations when handling paths with
different cases.

Signed-off-by: Paulo Alcantara (SUSE) <pc@cjr.nz>
Reviewed-by: Aurelien Aptel <aaptel@suse.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/cifs/dfs_cache.c

index b516638..66be239 100644 (file)
@@ -424,12 +424,24 @@ out_destroy_wq:
        return rc;
 }
 
-static inline unsigned int cache_entry_hash(const void *data, int size)
+static int cache_entry_hash(const void *data, int size, unsigned int *hash)
 {
-       unsigned int h;
-
-       h = jhash(data, size, 0);
-       return h & (CACHE_HTABLE_SIZE - 1);
+       int i, clen;
+       const unsigned char *s = data;
+       wchar_t c;
+       unsigned int h = 0;
+
+       for (i = 0; i < size; i += clen) {
+               clen = cache_cp->char2uni(&s[i], size - i, &c);
+               if (unlikely(clen < 0)) {
+                       cifs_dbg(VFS, "%s: can't convert char\n", __func__);
+                       return clen;
+               }
+               c = cifs_toupper(c);
+               h = jhash(&c, sizeof(c), h);
+       }
+       *hash = h % CACHE_HTABLE_SIZE;
+       return 0;
 }
 
 /* Return target hint of a DFS cache entry */
@@ -511,9 +523,7 @@ static int copy_ref_data(const struct dfs_info3_param *refs, int numrefs,
 }
 
 /* Allocate a new cache entry */
-static struct cache_entry *alloc_cache_entry(const char *path,
-                                            const struct dfs_info3_param *refs,
-                                            int numrefs)
+static struct cache_entry *alloc_cache_entry(struct dfs_info3_param *refs, int numrefs)
 {
        struct cache_entry *ce;
        int rc;
@@ -522,11 +532,9 @@ static struct cache_entry *alloc_cache_entry(const char *path,
        if (!ce)
                return ERR_PTR(-ENOMEM);
 
-       ce->path = kstrdup(path, GFP_KERNEL);
-       if (!ce->path) {
-               kmem_cache_free(cache_slab, ce);
-               return ERR_PTR(-ENOMEM);
-       }
+       ce->path = refs[0].path_name;
+       refs[0].path_name = NULL;
+
        INIT_HLIST_NODE(&ce->hlist);
        INIT_LIST_HEAD(&ce->tlist);
 
@@ -568,12 +576,18 @@ static void remove_oldest_entry_locked(void)
 }
 
 /* Add a new DFS cache entry */
-static int add_cache_entry_locked(const char *path, unsigned int hash,
-                                 struct dfs_info3_param *refs, int numrefs)
+static int add_cache_entry_locked(struct dfs_info3_param *refs, int numrefs)
 {
+       int rc;
        struct cache_entry *ce;
+       unsigned int hash;
 
-       ce = alloc_cache_entry(path, refs, numrefs);
+       convert_delimiter(refs[0].path_name, '\\');
+       rc = cache_entry_hash(refs[0].path_name, strlen(refs[0].path_name), &hash);
+       if (rc)
+               return rc;
+
+       ce = alloc_cache_entry(refs, numrefs);
        if (IS_ERR(ce))
                return PTR_ERR(ce);
 
@@ -593,57 +607,69 @@ static int add_cache_entry_locked(const char *path, unsigned int hash,
        return 0;
 }
 
-static struct cache_entry *__lookup_cache_entry(const char *path)
+/* Check if two DFS paths are equal.  @s1 and @s2 are expected to be in @cache_cp's charset */
+static bool dfs_path_equal(const char *s1, int len1, const char *s2, int len2)
 {
-       struct cache_entry *ce;
-       unsigned int h;
-       bool found = false;
+       int i, l1, l2;
+       wchar_t c1, c2;
+
+       if (len1 != len2)
+               return false;
+
+       for (i = 0; i < len1; i += l1) {
+               l1 = cache_cp->char2uni(&s1[i], len1 - i, &c1);
+               l2 = cache_cp->char2uni(&s2[i], len2 - i, &c2);
+               if (unlikely(l1 < 0 && l2 < 0)) {
+                       if (s1[i] != s2[i])
+                               return false;
+                       l1 = 1;
+                       continue;
+               }
+               if (l1 != l2)
+                       return false;
+               if (cifs_toupper(c1) != cifs_toupper(c2))
+                       return false;
+       }
+       return true;
+}
 
-       h = cache_entry_hash(path, strlen(path));
+static struct cache_entry *__lookup_cache_entry(const char *path, unsigned int hash, int len)
+{
+       struct cache_entry *ce;
 
-       hlist_for_each_entry(ce, &cache_htable[h], hlist) {
-               if (!strcasecmp(path, ce->path)) {
-                       found = true;
+       hlist_for_each_entry(ce, &cache_htable[hash], hlist) {
+               if (dfs_path_equal(ce->path, strlen(ce->path), path, len)) {
                        dump_ce(ce);
-                       break;
+                       return ce;
                }
        }
-
-       if (!found)
-               ce = ERR_PTR(-ENOENT);
-       return ce;
+       return ERR_PTR(-EEXIST);
 }
 
 /*
- * Find a DFS cache entry in hash table and optionally check prefix path against
- * @path.
- * Use whole path components in the match.
- * Must be called with htable_rw_lock held.
+ * Find a DFS cache entry in hash table and optionally check prefix path against normalized @path.
+ *
+ * Use whole path components in the match.  Must be called with htable_rw_lock held.
  *
- * Return ERR_PTR(-ENOENT) if the entry is not found.
+ * Return ERR_PTR(-EEXIST) if the entry is not found.
  */
-static struct cache_entry *lookup_cache_entry(const char *path, unsigned int *hash)
+static struct cache_entry *lookup_cache_entry(const char *path)
 {
-       struct cache_entry *ce = ERR_PTR(-ENOENT);
-       unsigned int h;
+       struct cache_entry *ce;
        int cnt = 0;
-       char *npath;
-       char *s, *e;
-       char sep;
-
-       npath = kstrdup(path, GFP_KERNEL);
-       if (!npath)
-               return ERR_PTR(-ENOMEM);
+       const char *s = path, *e;
+       char sep = *s;
+       unsigned int hash;
+       int rc;
 
-       s = npath;
-       sep = *npath;
        while ((s = strchr(s, sep)) && ++cnt < 3)
                s++;
 
        if (cnt < 3) {
-               h = cache_entry_hash(path, strlen(path));
-               ce = __lookup_cache_entry(path);
-               goto out;
+               rc = cache_entry_hash(path, strlen(path), &hash);
+               if (rc)
+                       return ERR_PTR(rc);
+               return __lookup_cache_entry(path, hash, strlen(path));
        }
        /*
         * Handle paths that have more than two path components and are a complete prefix of the DFS
@@ -651,36 +677,29 @@ static struct cache_entry *lookup_cache_entry(const char *path, unsigned int *ha
         *
         * See MS-DFSC 3.2.5.5 "Receiving a Root Referral Request or Link Referral Request".
         */
-       h = cache_entry_hash(npath, strlen(npath));
-       e = npath + strlen(npath) - 1;
+       e = path + strlen(path) - 1;
        while (e > s) {
-               char tmp;
+               int len;
 
                /* skip separators */
                while (e > s && *e == sep)
                        e--;
                if (e == s)
-                       goto out;
-
-               tmp = *(e+1);
-               *(e+1) = 0;
-
-               ce = __lookup_cache_entry(npath);
-               if (!IS_ERR(ce)) {
-                       h = cache_entry_hash(npath, strlen(npath));
                        break;
-               }
 
-               *(e+1) = tmp;
+               len = e + 1 - path;
+               rc = cache_entry_hash(path, len, &hash);
+               if (rc)
+                       return ERR_PTR(rc);
+               ce = __lookup_cache_entry(path, hash, len);
+               if (!IS_ERR(ce))
+                       return ce;
+
                /* backward until separator */
                while (e > s && *e != sep)
                        e--;
        }
-out:
-       if (hash)
-               *hash = h;
-       kfree(npath);
-       return ce;
+       return ERR_PTR(-EEXIST);
 }
 
 /**
@@ -706,7 +725,7 @@ static int update_cache_entry_locked(const char *path, const struct dfs_info3_pa
        struct cache_entry *ce;
        char *s, *th = NULL;
 
-       ce = lookup_cache_entry(path, NULL);
+       ce = lookup_cache_entry(path);
        if (IS_ERR(ce))
                return PTR_ERR(ce);
 
@@ -756,7 +775,6 @@ static int get_dfs_referral(const unsigned int xid, struct cifs_ses *ses, const
 static int cache_refresh_path(const unsigned int xid, struct cifs_ses *ses, const char *path)
 {
        int rc;
-       unsigned int hash;
        struct cache_entry *ce;
        struct dfs_info3_param *refs = NULL;
        int numrefs = 0;
@@ -766,7 +784,7 @@ static int cache_refresh_path(const unsigned int xid, struct cifs_ses *ses, cons
 
        down_write(&htable_rw_lock);
 
-       ce = lookup_cache_entry(path, &hash);
+       ce = lookup_cache_entry(path);
        if (!IS_ERR(ce)) {
                if (!cache_entry_expired(ce)) {
                        dump_ce(ce);
@@ -797,7 +815,7 @@ static int cache_refresh_path(const unsigned int xid, struct cifs_ses *ses, cons
                remove_oldest_entry_locked();
        }
 
-       rc = add_cache_entry_locked(path, hash, refs, numrefs);
+       rc = add_cache_entry_locked(refs, numrefs);
        if (!rc)
                atomic_inc(&cache_count);
 
@@ -929,7 +947,7 @@ int dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, const struct nl
 
        down_read(&htable_rw_lock);
 
-       ce = lookup_cache_entry(npath, NULL);
+       ce = lookup_cache_entry(npath);
        if (IS_ERR(ce)) {
                up_read(&htable_rw_lock);
                rc = PTR_ERR(ce);
@@ -976,7 +994,7 @@ int dfs_cache_noreq_find(const char *path, struct dfs_info3_param *ref,
 
        down_read(&htable_rw_lock);
 
-       ce = lookup_cache_entry(path, NULL);
+       ce = lookup_cache_entry(path);
        if (IS_ERR(ce)) {
                rc = PTR_ERR(ce);
                goto out_unlock;
@@ -1033,7 +1051,7 @@ int dfs_cache_update_tgthint(const unsigned int xid, struct cifs_ses *ses,
 
        down_write(&htable_rw_lock);
 
-       ce = lookup_cache_entry(npath, NULL);
+       ce = lookup_cache_entry(npath);
        if (IS_ERR(ce)) {
                rc = PTR_ERR(ce);
                goto out_unlock;
@@ -1087,7 +1105,7 @@ int dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt_
 
        down_write(&htable_rw_lock);
 
-       ce = lookup_cache_entry(path, NULL);
+       ce = lookup_cache_entry(path);
        if (IS_ERR(ce)) {
                rc = PTR_ERR(ce);
                goto out_unlock;
@@ -1136,7 +1154,7 @@ int dfs_cache_get_tgt_referral(const char *path, const struct dfs_cache_tgt_iter
 
        down_read(&htable_rw_lock);
 
-       ce = lookup_cache_entry(path, NULL);
+       ce = lookup_cache_entry(path);
        if (IS_ERR(ce)) {
                rc = PTR_ERR(ce);
                goto out_unlock;