ksmbd: casefold utf-8 share names and fix ascii lowercase conversion
authorAtte Heikkilä <atteh.mailbox@gmail.com>
Thu, 15 Sep 2022 13:49:11 +0000 (22:49 +0900)
committerSteve French <stfrench@microsoft.com>
Wed, 5 Oct 2022 06:15:37 +0000 (01:15 -0500)
strtolower() corrupts all UTF-8 share names that have a byte in the C0
(À ISO8859-1) to DE (Þ ISO8859-1) range, since the non-ASCII part of
ISO8859-1 is incompatible with UTF-8. Prevent this by checking that a
byte is in the ASCII range with isascii(), before the conversion to
lowercase with tolower(). Properly handle case-insensitivity of UTF-8
share names by casefolding them, but fallback to ASCII lowercase
conversion on failure or if CONFIG_UNICODE is not set. Refactor to move
the share name casefolding immediately after the share name extraction.
Also, make the associated constness corrections.

Signed-off-by: Atte Heikkilä <atteh.mailbox@gmail.com>
Acked-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/ksmbd/connection.c
fs/ksmbd/connection.h
fs/ksmbd/mgmt/share_config.c
fs/ksmbd/mgmt/share_config.h
fs/ksmbd/mgmt/tree_connect.c
fs/ksmbd/mgmt/tree_connect.h
fs/ksmbd/misc.c
fs/ksmbd/misc.h
fs/ksmbd/smb2pdu.c
fs/ksmbd/unicode.h

index 756ad631c019a00612acd5e762c2ceef5c67dc7b..12be8386446a393aced9947013f410700bacebb0 100644 (file)
@@ -60,6 +60,12 @@ struct ksmbd_conn *ksmbd_conn_alloc(void)
        conn->local_nls = load_nls("utf8");
        if (!conn->local_nls)
                conn->local_nls = load_nls_default();
+       if (IS_ENABLED(CONFIG_UNICODE))
+               conn->um = utf8_load(UNICODE_AGE(12, 1, 0));
+       else
+               conn->um = ERR_PTR(-EOPNOTSUPP);
+       if (IS_ERR(conn->um))
+               conn->um = NULL;
        atomic_set(&conn->req_running, 0);
        atomic_set(&conn->r_count, 0);
        conn->total_credits = 1;
@@ -350,6 +356,8 @@ out:
        wait_event(conn->r_count_q, atomic_read(&conn->r_count) == 0);
 
 
+       if (IS_ENABLED(CONFIG_UNICODE))
+               utf8_unload(conn->um);
        unload_nls(conn->local_nls);
        if (default_conn_ops.terminate_fn)
                default_conn_ops.terminate_fn(conn);
index e7f7d570795196fb064f50d139bee3712c1c9c85..41d96f5cef0615ef0bf3ebe3b5d507b09e178a83 100644 (file)
@@ -46,6 +46,7 @@ struct ksmbd_conn {
        char                            *request_buf;
        struct ksmbd_transport          *transport;
        struct nls_table                *local_nls;
+       struct unicode_map              *um;
        struct list_head                conns_list;
        /* smb session 1 per user */
        struct xarray                   sessions;
index c9bca1c2c83490ebcd1b51de7b96f1126c46b23a..5d039704c23c3398a16d290fe1475605f79ea1ce 100644 (file)
@@ -26,7 +26,7 @@ struct ksmbd_veto_pattern {
        struct list_head        list;
 };
 
-static unsigned int share_name_hash(char *name)
+static unsigned int share_name_hash(const char *name)
 {
        return jhash(name, strlen(name), 0);
 }
@@ -72,7 +72,7 @@ __get_share_config(struct ksmbd_share_config *share)
        return share;
 }
 
-static struct ksmbd_share_config *__share_lookup(char *name)
+static struct ksmbd_share_config *__share_lookup(const char *name)
 {
        struct ksmbd_share_config *share;
        unsigned int key = share_name_hash(name);
@@ -119,7 +119,7 @@ static int parse_veto_list(struct ksmbd_share_config *share,
        return 0;
 }
 
-static struct ksmbd_share_config *share_config_request(char *name)
+static struct ksmbd_share_config *share_config_request(const char *name)
 {
        struct ksmbd_share_config_response *resp;
        struct ksmbd_share_config *share = NULL;
@@ -190,20 +190,10 @@ out:
        return share;
 }
 
-static void strtolower(char *share_name)
-{
-       while (*share_name) {
-               *share_name = tolower(*share_name);
-               share_name++;
-       }
-}
-
-struct ksmbd_share_config *ksmbd_share_config_get(char *name)
+struct ksmbd_share_config *ksmbd_share_config_get(const char *name)
 {
        struct ksmbd_share_config *share;
 
-       strtolower(name);
-
        down_read(&shares_table_lock);
        share = __share_lookup(name);
        if (share)
index 902f2cb1963a93b9f442341afd7a7d453fd81efc..7f7e89ecfe6166b275dbb8ae3d8648abd0a53968 100644 (file)
@@ -74,7 +74,7 @@ static inline void ksmbd_share_config_put(struct ksmbd_share_config *share)
        __ksmbd_share_config_put(share);
 }
 
-struct ksmbd_share_config *ksmbd_share_config_get(char *name);
+struct ksmbd_share_config *ksmbd_share_config_get(const char *name);
 bool ksmbd_share_veto_filename(struct ksmbd_share_config *share,
                               const char *filename);
 #endif /* __SHARE_CONFIG_MANAGEMENT_H__ */
index 97ab7987df6ebfa8b004b924513f560bedaaae6a..867c0286b901d76483e8bb6ec90b2eed9bd0a0e8 100644 (file)
@@ -17,7 +17,7 @@
 
 struct ksmbd_tree_conn_status
 ksmbd_tree_conn_connect(struct ksmbd_conn *conn, struct ksmbd_session *sess,
-                       char *share_name)
+                       const char *share_name)
 {
        struct ksmbd_tree_conn_status status = {-ENOENT, NULL};
        struct ksmbd_tree_connect_response *resp = NULL;
index 71e50271dccf0bdfbfce797f90815f7c175f14ff..0f97ddc1e39c07919ed5bc153a41cb77ea716316 100644 (file)
@@ -42,7 +42,7 @@ struct ksmbd_session;
 
 struct ksmbd_tree_conn_status
 ksmbd_tree_conn_connect(struct ksmbd_conn *conn, struct ksmbd_session *sess,
-                       char *share_name);
+                       const char *share_name);
 
 int ksmbd_tree_conn_disconnect(struct ksmbd_session *sess,
                               struct ksmbd_tree_connect *tree_conn);
index 364a0a463dfce55545ce22f7364f0cac866abd92..28459b1efaa88ede96ceb340b0ff38d02d8039d7 100644 (file)
@@ -7,6 +7,7 @@
 #include <linux/kernel.h>
 #include <linux/xattr.h>
 #include <linux/fs.h>
+#include <linux/unicode.h>
 
 #include "misc.h"
 #include "smb_common.h"
@@ -226,26 +227,53 @@ void ksmbd_conv_path_to_windows(char *path)
        strreplace(path, '/', '\\');
 }
 
+static char *casefold_sharename(struct unicode_map *um, const char *name)
+{
+       char *cf_name;
+       int cf_len;
+
+       cf_name = kzalloc(KSMBD_REQ_MAX_SHARE_NAME, GFP_KERNEL);
+       if (!cf_name)
+               return ERR_PTR(-ENOMEM);
+
+       if (IS_ENABLED(CONFIG_UNICODE) && um) {
+               const struct qstr q_name = {.name = name, .len = strlen(name)};
+
+               cf_len = utf8_casefold(um, &q_name, cf_name,
+                                      KSMBD_REQ_MAX_SHARE_NAME);
+               if (cf_len < 0)
+                       goto out_ascii;
+
+               return cf_name;
+       }
+
+out_ascii:
+       cf_len = strscpy(cf_name, name, KSMBD_REQ_MAX_SHARE_NAME);
+       if (cf_len < 0) {
+               kfree(cf_name);
+               return ERR_PTR(-E2BIG);
+       }
+
+       for (; *cf_name; ++cf_name)
+               *cf_name = isascii(*cf_name) ? tolower(*cf_name) : *cf_name;
+       return cf_name - cf_len;
+}
+
 /**
  * ksmbd_extract_sharename() - get share name from tree connect request
  * @treename:  buffer containing tree name and share name
  *
  * Return:      share name on success, otherwise error
  */
-char *ksmbd_extract_sharename(char *treename)
+char *ksmbd_extract_sharename(struct unicode_map *um, const char *treename)
 {
-       char *name = treename;
-       char *dst;
-       char *pos = strrchr(name, '\\');
+       const char *name = treename, *pos = strrchr(name, '\\');
 
        if (pos)
                name = (pos + 1);
 
        /* caller has to free the memory */
-       dst = kstrdup(name, GFP_KERNEL);
-       if (!dst)
-               return ERR_PTR(-ENOMEM);
-       return dst;
+       return casefold_sharename(um, name);
 }
 
 /**
index 5a0ae2f8e5e703e83ad22a88f7c013255a4a18e4..cc72f4e6baf23f212148d1fc42784b3ad43d01c2 100644 (file)
@@ -20,7 +20,7 @@ int get_nlink(struct kstat *st);
 void ksmbd_conv_path_to_unix(char *path);
 void ksmbd_strip_last_slash(char *path);
 void ksmbd_conv_path_to_windows(char *path);
-char *ksmbd_extract_sharename(char *treename);
+char *ksmbd_extract_sharename(struct unicode_map *um, const char *treename);
 char *convert_to_unix_name(struct ksmbd_share_config *share, const char *name);
 
 #define KSMBD_DIR_INFO_ALIGNMENT       8
index c505399483240be957eecdb15440c8bcd7e1146b..f33a04e9e458e867f345cbfdae445240cf22cb73 100644 (file)
@@ -1883,7 +1883,7 @@ int smb2_tree_connect(struct ksmbd_work *work)
                goto out_err1;
        }
 
-       name = ksmbd_extract_sharename(treename);
+       name = ksmbd_extract_sharename(conn->um, treename);
        if (IS_ERR(name)) {
                status.ret = KSMBD_TREE_CONN_STATUS_ERROR;
                goto out_err1;
index 5593024230ae29577576e213214b8906c3eac595..076f6034a7899b170a5a8231d402d0b9a7e334a2 100644 (file)
@@ -24,6 +24,7 @@
 #include <asm/byteorder.h>
 #include <linux/types.h>
 #include <linux/nls.h>
+#include <linux/unicode.h>
 
 #define  UNIUPR_NOLOWER                /* Example to not expand lower case tables */
 
@@ -69,7 +70,7 @@ char *smb_strndup_from_utf16(const char *src, const int maxlen,
                             const struct nls_table *codepage);
 int smbConvertToUTF16(__le16 *target, const char *source, int srclen,
                      const struct nls_table *cp, int mapchars);
-char *ksmbd_extract_sharename(char *treename);
+char *ksmbd_extract_sharename(struct unicode_map *um, const char *treename);
 #endif
 
 /*