From 0102c8c7fdfc4bd700971daff2b94470f4eafae4 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 17 Sep 2026 09:52:33 +0800 Subject: perf/x86/intel: Rename DMR offcore_rsp attribute to offmodule_rsp DMR introduces Offmodule Response events in place of the legacy Offcore Response events, but it still exposes the inherited offcore_rsp PMU attribute for programming the corresponding MSR data. Rename the DMR PMU attribute to offmodule_rsp so the sysfs interface matches the underlying event name and avoids user & tooling confusion. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260917015234.981153-12-dapeng1.mi@linux.intel.com --- fs/smb/server/mgmt/ksmbd_ida.c | 38 ++ fs/smb/server/mgmt/ksmbd_ida.h | 34 ++ fs/smb/server/mgmt/share_config.c | 335 ++++++++++++++ fs/smb/server/mgmt/share_config.h | 102 +++++ fs/smb/server/mgmt/tree_connect.c | 188 ++++++++ fs/smb/server/mgmt/tree_connect.h | 64 +++ fs/smb/server/mgmt/user_config.c | 112 +++++ fs/smb/server/mgmt/user_config.h | 70 +++ fs/smb/server/mgmt/user_session.c | 899 ++++++++++++++++++++++++++++++++++++++ fs/smb/server/mgmt/user_session.h | 130 ++++++ 10 files changed, 1972 insertions(+) create mode 100644 fs/smb/server/mgmt/ksmbd_ida.c create mode 100644 fs/smb/server/mgmt/ksmbd_ida.h create mode 100644 fs/smb/server/mgmt/share_config.c create mode 100644 fs/smb/server/mgmt/share_config.h create mode 100644 fs/smb/server/mgmt/tree_connect.c create mode 100644 fs/smb/server/mgmt/tree_connect.h create mode 100644 fs/smb/server/mgmt/user_config.c create mode 100644 fs/smb/server/mgmt/user_config.h create mode 100644 fs/smb/server/mgmt/user_session.c create mode 100644 fs/smb/server/mgmt/user_session.h (limited to 'fs/smb/server/mgmt') diff --git a/fs/smb/server/mgmt/ksmbd_ida.c b/fs/smb/server/mgmt/ksmbd_ida.c new file mode 100644 index 000000000..0e2ae994a --- /dev/null +++ b/fs/smb/server/mgmt/ksmbd_ida.c @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 Samsung Electronics Co., Ltd. + */ + +#include "ksmbd_ida.h" +#include "../glob.h" + +int ksmbd_acquire_smb2_tid(struct ida *ida) +{ + return ida_alloc_range(ida, 1, 0xFFFFFFFE, KSMBD_DEFAULT_GFP); +} + +int ksmbd_acquire_smb2_uid(struct ida *ida) +{ + int id; + + id = ida_alloc_min(ida, 1, KSMBD_DEFAULT_GFP); + if (id == 0xFFFE) + id = ida_alloc_min(ida, 1, KSMBD_DEFAULT_GFP); + + return id; +} + +int ksmbd_acquire_async_msg_id(struct ida *ida) +{ + return ida_alloc_min(ida, 1, KSMBD_DEFAULT_GFP); +} + +int ksmbd_acquire_id(struct ida *ida) +{ + return ida_alloc(ida, KSMBD_DEFAULT_GFP); +} + +void ksmbd_release_id(struct ida *ida, int id) +{ + ida_free(ida, id); +} diff --git a/fs/smb/server/mgmt/ksmbd_ida.h b/fs/smb/server/mgmt/ksmbd_ida.h new file mode 100644 index 000000000..2bc07b16c --- /dev/null +++ b/fs/smb/server/mgmt/ksmbd_ida.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2018 Samsung Electronics Co., Ltd. + */ + +#ifndef __KSMBD_IDA_MANAGEMENT_H__ +#define __KSMBD_IDA_MANAGEMENT_H__ + +#include +#include + +/* + * 2.2.1.6.7 TID Generation + * The value 0xFFFF MUST NOT be used as a valid TID. All other + * possible values for TID, including zero (0x0000), are valid. + * The value 0xFFFF is used to specify all TIDs or no TID, + * depending upon the context in which it is used. + */ +int ksmbd_acquire_smb2_tid(struct ida *ida); + +/* + * 2.2.1.6.8 UID Generation + * The value 0xFFFE was declared reserved in the LAN Manager 1.0 + * documentation, so a value of 0xFFFE SHOULD NOT be used as a + * valid UID.<21> All other possible values for a UID, excluding + * zero (0x0000), are valid. + */ +int ksmbd_acquire_smb2_uid(struct ida *ida); +int ksmbd_acquire_async_msg_id(struct ida *ida); + +int ksmbd_acquire_id(struct ida *ida); + +void ksmbd_release_id(struct ida *ida, int id); +#endif /* __KSMBD_IDA_MANAGEMENT_H__ */ diff --git a/fs/smb/server/mgmt/share_config.c b/fs/smb/server/mgmt/share_config.c new file mode 100644 index 000000000..cc9f18ede --- /dev/null +++ b/fs/smb/server/mgmt/share_config.c @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 Samsung Electronics Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "share_config.h" +#include "user_config.h" +#include "user_session.h" +#include "../connection.h" +#include "../transport_ipc.h" +#include "../misc.h" + +#define SHARE_HASH_BITS 12 +static DEFINE_HASHTABLE(shares_table, SHARE_HASH_BITS); +static DECLARE_RWSEM(shares_table_lock); + +struct ksmbd_veto_pattern { + char *pattern; + struct list_head list; +}; + +#ifdef CONFIG_PROC_FS +static const struct ksmbd_const_name ksmbd_share_flag_names[] = { + {KSMBD_SHARE_FLAG_AVAILABLE, "available"}, + {KSMBD_SHARE_FLAG_BROWSEABLE, "browseable"}, + {KSMBD_SHARE_FLAG_WRITEABLE, "writeable"}, + {KSMBD_SHARE_FLAG_READONLY, "read-only"}, + {KSMBD_SHARE_FLAG_GUEST_OK, "guest-ok"}, + {KSMBD_SHARE_FLAG_GUEST_ONLY, "guest-only"}, + {KSMBD_SHARE_FLAG_STORE_DOS_ATTRS, "store-dos-attrs"}, + {KSMBD_SHARE_FLAG_OPLOCKS, "oplocks"}, + {KSMBD_SHARE_FLAG_PIPE, "pipe"}, + {KSMBD_SHARE_FLAG_HIDE_DOT_FILES, "hide-dot-files"}, + {KSMBD_SHARE_FLAG_INHERIT_OWNER, "inherit-owner"}, + {KSMBD_SHARE_FLAG_STREAMS, "streams"}, + {KSMBD_SHARE_FLAG_FOLLOW_SYMLINKS, "follow-symlinks"}, + {KSMBD_SHARE_FLAG_ACL_XATTR, "acl-xattr"}, + {KSMBD_SHARE_FLAG_UPDATE, "update"}, + {KSMBD_SHARE_FLAG_CROSSMNT, "crossmnt"}, + {KSMBD_SHARE_FLAG_CONTINUOUS_AVAILABILITY, "continuous-availability"}, + {KSMBD_SHARE_FLAG_ENCRYPT_DATA, "encrypt-data"}, +}; + +static int proc_show_shares(struct seq_file *m, void *v) +{ + struct ksmbd_share_config *share; + int i; + + down_read(&shares_table_lock); + hash_for_each(shares_table, i, share, hlist) { + seq_printf(m, "name:\t%s\n", share->name); + seq_printf(m, "type:\t%s\n", + test_share_config_flag(share, KSMBD_SHARE_FLAG_PIPE) ? + "pipe" : "disk"); + seq_printf(m, "tree_connects:\t%d\n", + atomic_read(&share->tree_connections)); + seq_printf(m, "file_mask:\t0%07o\n", share->create_mask); + seq_printf(m, "directory_mask:\t0%07o\n", share->directory_mask); + seq_puts(m, "flags:\t"); + ksmbd_proc_show_flag_names(m, ksmbd_share_flag_names, + ARRAY_SIZE(ksmbd_share_flag_names), + share->flags); + seq_puts(m, "\n\n"); + } + up_read(&shares_table_lock); + return 0; +} + +int create_proc_shares(void) +{ + if (!ksmbd_proc_create("shares", proc_show_shares, NULL)) + return -ENOMEM; + return 0; +} +#else +int create_proc_shares(void) { return 0; } +#endif + +static unsigned int share_name_hash(const char *name) +{ + return jhash(name, strlen(name), 0); +} + +static void kill_share(struct ksmbd_share_config *share) +{ + while (!list_empty(&share->veto_list)) { + struct ksmbd_veto_pattern *p; + + p = list_entry(share->veto_list.next, + struct ksmbd_veto_pattern, + list); + list_del(&p->list); + kfree(p->pattern); + kfree(p); + } + + if (share->path) + path_put(&share->vfs_path); + kfree(share->name); + kfree(share->path); + kfree(share); +} + +void ksmbd_share_config_del(struct ksmbd_share_config *share) +{ + down_write(&shares_table_lock); + hash_del(&share->hlist); + up_write(&shares_table_lock); +} + +void __ksmbd_share_config_put(struct ksmbd_share_config *share) +{ + ksmbd_share_config_del(share); + kill_share(share); +} + +static struct ksmbd_share_config * +__get_share_config(struct ksmbd_share_config *share) +{ + if (!atomic_inc_not_zero(&share->refcount)) + return NULL; + return share; +} + +static struct ksmbd_share_config *__share_lookup(const char *name) +{ + struct ksmbd_share_config *share; + unsigned int key = share_name_hash(name); + + hash_for_each_possible(shares_table, share, hlist, key) { + if (!strcmp(name, share->name)) + return share; + } + return NULL; +} + +static int parse_veto_list(struct ksmbd_share_config *share, + char *veto_list, + size_t veto_list_sz) +{ + size_t sz; + + if (!veto_list_sz) + return 0; + + while (veto_list_sz > 0) { + struct ksmbd_veto_pattern *p; + + sz = strnlen(veto_list, veto_list_sz); + if (!sz) + break; + + p = kzalloc_obj(struct ksmbd_veto_pattern, KSMBD_DEFAULT_GFP); + if (!p) + return -ENOMEM; + + p->pattern = kstrndup(veto_list, sz, KSMBD_DEFAULT_GFP); + if (!p->pattern) { + kfree(p); + return -ENOMEM; + } + + list_add(&p->list, &share->veto_list); + + if (sz == veto_list_sz) + break; + + veto_list += sz + 1; + veto_list_sz -= (sz + 1); + } + + return 0; +} + +static struct ksmbd_share_config *share_config_request(struct ksmbd_work *work, + const char *name) +{ + struct ksmbd_share_config_response *resp; + struct ksmbd_share_config *share = NULL; + struct ksmbd_share_config *lookup; + struct unicode_map *um = work->conn->um; + int ret; + + resp = ksmbd_ipc_share_config_request(name); + if (!resp) + return NULL; + + if (resp->flags == KSMBD_SHARE_FLAG_INVALID) + goto out; + + if (*resp->share_name) { + char *cf_resp_name; + bool equal; + + cf_resp_name = ksmbd_casefold_sharename(um, resp->share_name); + if (IS_ERR(cf_resp_name)) + goto out; + equal = !strcmp(cf_resp_name, name); + kfree(cf_resp_name); + if (!equal) + goto out; + } + + share = kzalloc_obj(struct ksmbd_share_config, KSMBD_DEFAULT_GFP); + if (!share) + goto out; + + share->flags = resp->flags; + atomic_set(&share->refcount, 1); + ksmbd_share_tree_conn_init(share); + INIT_LIST_HEAD(&share->veto_list); + share->name = kstrdup(name, KSMBD_DEFAULT_GFP); + if (!share->name) { + kill_share(share); + share = NULL; + goto out; + } + + if (!test_share_config_flag(share, KSMBD_SHARE_FLAG_PIPE)) { + size_t path_len; + + if (resp->payload_sz <= resp->veto_list_sz) { + ret = -EINVAL; + } else { + path_len = resp->payload_sz - resp->veto_list_sz; + if (resp->veto_list_sz) + path_len--; + + if (!path_len) { + ret = -EINVAL; + } else { + share->path = kstrndup( + ksmbd_share_config_path(resp), + path_len, KSMBD_DEFAULT_GFP); + if (!share->path) + ret = -ENOMEM; + else + ret = 0; + } + } + if (share->path) { + share->path_sz = strlen(share->path); + while (share->path_sz > 1 && + share->path[share->path_sz - 1] == '/') + share->path[--share->path_sz] = '\0'; + } + share->create_mask = resp->create_mask; + share->directory_mask = resp->directory_mask; + share->force_create_mode = resp->force_create_mode; + share->force_directory_mode = resp->force_directory_mode; + share->force_uid = resp->force_uid; + share->force_gid = resp->force_gid; + if (!ret) + ret = parse_veto_list(share, + KSMBD_SHARE_CONFIG_VETO_LIST(resp), + resp->veto_list_sz); + if (!ret && share->path) { + if (__ksmbd_override_fsids(work, share)) { + kill_share(share); + share = NULL; + goto out; + } + + scoped_with_init_fs() + ret = kern_path(share->path, 0, &share->vfs_path); + ksmbd_revert_fsids(work); + if (ret) { + ksmbd_debug(SMB, "failed to access '%s'\n", + share->path); + /* Avoid put_path() */ + kfree(share->path); + share->path = NULL; + } + } + if (ret) { + kill_share(share); + share = NULL; + goto out; + } + } + + down_write(&shares_table_lock); + lookup = __share_lookup(name); + if (lookup) + lookup = __get_share_config(lookup); + if (!lookup) { + hash_add(shares_table, &share->hlist, share_name_hash(name)); + } else { + kill_share(share); + share = lookup; + } + up_write(&shares_table_lock); + +out: + kvfree(resp); + return share; +} + +struct ksmbd_share_config *ksmbd_share_config_get(struct ksmbd_work *work, + const char *name) +{ + struct ksmbd_share_config *share; + + down_read(&shares_table_lock); + share = __share_lookup(name); + if (share) + share = __get_share_config(share); + up_read(&shares_table_lock); + + if (share) + return share; + return share_config_request(work, name); +} + +bool ksmbd_share_veto_filename(struct ksmbd_share_config *share, + const char *filename) +{ + struct ksmbd_veto_pattern *p; + + list_for_each_entry(p, &share->veto_list, list) { + if (match_wildcard(p->pattern, filename)) + return true; + } + return false; +} diff --git a/fs/smb/server/mgmt/share_config.h b/fs/smb/server/mgmt/share_config.h new file mode 100644 index 000000000..d157545fe --- /dev/null +++ b/fs/smb/server/mgmt/share_config.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2018 Samsung Electronics Co., Ltd. + */ + +#ifndef __SHARE_CONFIG_MANAGEMENT_H__ +#define __SHARE_CONFIG_MANAGEMENT_H__ + +#include +#include +#include +#include + +struct ksmbd_work; + +struct ksmbd_share_config { + char *name; + char *path; + + unsigned int path_sz; + unsigned int flags; + struct list_head veto_list; + + struct path vfs_path; + + atomic_t refcount; +#ifdef CONFIG_PROC_FS + atomic_t tree_connections; +#endif + struct hlist_node hlist; + unsigned short create_mask; + unsigned short directory_mask; + unsigned short force_create_mode; + unsigned short force_directory_mode; + unsigned short force_uid; + unsigned short force_gid; +}; + +#define KSMBD_SHARE_INVALID_UID ((__u16)-1) +#define KSMBD_SHARE_INVALID_GID ((__u16)-1) + +static inline umode_t +share_config_create_mode(struct ksmbd_share_config *share, + umode_t posix_mode) +{ + umode_t mode = (posix_mode ?: (umode_t)-1) & share->create_mask; + + return mode | share->force_create_mode; +} + +static inline umode_t +share_config_directory_mode(struct ksmbd_share_config *share, + umode_t posix_mode) +{ + umode_t mode = (posix_mode ?: (umode_t)-1) & share->directory_mask; + + return mode | share->force_directory_mode; +} + +static inline int test_share_config_flag(struct ksmbd_share_config *share, + int flag) +{ + return share->flags & flag; +} + +#ifdef CONFIG_PROC_FS +static inline void ksmbd_share_tree_conn_init(struct ksmbd_share_config *share) +{ + atomic_set(&share->tree_connections, 0); +} + +static inline void ksmbd_share_tree_conn_inc(struct ksmbd_share_config *share) +{ + atomic_inc(&share->tree_connections); +} + +static inline void ksmbd_share_tree_conn_dec(struct ksmbd_share_config *share) +{ + atomic_dec(&share->tree_connections); +} +#else +static inline void ksmbd_share_tree_conn_init(struct ksmbd_share_config *share) {} +static inline void ksmbd_share_tree_conn_inc(struct ksmbd_share_config *share) {} +static inline void ksmbd_share_tree_conn_dec(struct ksmbd_share_config *share) {} +#endif + +void ksmbd_share_config_del(struct ksmbd_share_config *share); +void __ksmbd_share_config_put(struct ksmbd_share_config *share); + +static inline void ksmbd_share_config_put(struct ksmbd_share_config *share) +{ + if (!atomic_dec_and_test(&share->refcount)) + return; + __ksmbd_share_config_put(share); +} + +struct ksmbd_share_config *ksmbd_share_config_get(struct ksmbd_work *work, + const char *name); +bool ksmbd_share_veto_filename(struct ksmbd_share_config *share, + const char *filename); +int create_proc_shares(void); +#endif /* __SHARE_CONFIG_MANAGEMENT_H__ */ diff --git a/fs/smb/server/mgmt/tree_connect.c b/fs/smb/server/mgmt/tree_connect.c new file mode 100644 index 000000000..dd1db3554 --- /dev/null +++ b/fs/smb/server/mgmt/tree_connect.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 Samsung Electronics Co., Ltd. + */ + +#include +#include +#include + +#include "../transport_ipc.h" +#include "../connection.h" +#include "../stats.h" + +#include "tree_connect.h" +#include "user_config.h" +#include "share_config.h" +#include "user_session.h" + +struct ksmbd_tree_conn_status +ksmbd_tree_conn_connect(struct ksmbd_work *work, const char *share_name) +{ + struct ksmbd_tree_conn_status status = {-ENOENT, NULL}; + struct ksmbd_tree_connect_response *resp = NULL; + struct ksmbd_share_config *sc; + struct ksmbd_tree_connect *tree_conn = NULL; + struct sockaddr *peer_addr; + struct ksmbd_conn *conn = work->conn; + struct ksmbd_session *sess = work->sess; + int ret; + + sc = ksmbd_share_config_get(work, share_name); + if (!sc) + return status; + + tree_conn = kzalloc_obj(struct ksmbd_tree_connect, KSMBD_DEFAULT_GFP); + if (!tree_conn) { + status.ret = -ENOMEM; + goto out_error; + } + + tree_conn->id = ksmbd_acquire_tree_conn_id(sess); + if (tree_conn->id < 0) { + status.ret = -EINVAL; + goto out_error; + } + + peer_addr = KSMBD_TCP_PEER_SOCKADDR(conn); + resp = ksmbd_ipc_tree_connect_request(sess, + sc, + tree_conn, + peer_addr); + if (!resp) { + status.ret = -EINVAL; + goto out_error; + } + + status.ret = resp->status; + if (status.ret != KSMBD_TREE_CONN_STATUS_OK) + goto out_error; + + tree_conn->flags = resp->connection_flags; + if (test_tree_conn_flag(tree_conn, KSMBD_TREE_CONN_FLAG_UPDATE)) { + struct ksmbd_share_config *new_sc; + + ksmbd_share_config_del(sc); + new_sc = ksmbd_share_config_get(work, share_name); + if (!new_sc) { + pr_err("Failed to update stale share config\n"); + status.ret = -ESTALE; + goto out_error; + } + ksmbd_share_config_put(sc); + sc = new_sc; + } + + tree_conn->user = sess->user; + tree_conn->share_conf = sc; + tree_conn->t_state = TREE_NEW; + status.tree_conn = tree_conn; + atomic_set(&tree_conn->refcount, 1); + + down_write(&sess->tree_conns_lock); + ret = xa_err(xa_store(&sess->tree_conns, tree_conn->id, tree_conn, + KSMBD_DEFAULT_GFP)); + if (!ret) + atomic_inc(&tree_conn->refcount); + up_write(&sess->tree_conns_lock); + if (ret) { + status.ret = -ENOMEM; + goto out_error; + } + ksmbd_counter_inc(KSMBD_COUNTER_TREE_CONNS); + ksmbd_share_tree_conn_inc(sc); + kvfree(resp); + return status; + +out_error: + if (tree_conn) + ksmbd_release_tree_conn_id(sess, tree_conn->id); + ksmbd_share_config_put(sc); + kfree(tree_conn); + kvfree(resp); + return status; +} + +void ksmbd_tree_connect_put(struct ksmbd_tree_connect *tcon) +{ + if (atomic_dec_and_test(&tcon->refcount)) { + ksmbd_share_config_put(tcon->share_conf); + kfree(tcon); + } +} + +static int __ksmbd_tree_conn_disconnect(struct ksmbd_session *sess, + struct ksmbd_tree_connect *tree_conn) +{ + int ret; + + ret = ksmbd_ipc_tree_disconnect_request(sess->id, tree_conn->id); + ksmbd_release_tree_conn_id(sess, tree_conn->id); + ksmbd_counter_dec(KSMBD_COUNTER_TREE_CONNS); + ksmbd_share_tree_conn_dec(tree_conn->share_conf); + if (atomic_dec_and_test(&tree_conn->refcount)) { + ksmbd_share_config_put(tree_conn->share_conf); + kfree(tree_conn); + } + return ret; +} + +int ksmbd_tree_conn_disconnect(struct ksmbd_session *sess, + struct ksmbd_tree_connect *tree_conn) +{ + down_write(&sess->tree_conns_lock); + if (tree_conn->t_state == TREE_DISCONNECTED || + xa_load(&sess->tree_conns, tree_conn->id) != tree_conn) { + up_write(&sess->tree_conns_lock); + return -ENOENT; + } + tree_conn->t_state = TREE_DISCONNECTED; + xa_erase(&sess->tree_conns, tree_conn->id); + up_write(&sess->tree_conns_lock); + + return __ksmbd_tree_conn_disconnect(sess, tree_conn); +} + +struct ksmbd_tree_connect *ksmbd_tree_conn_lookup(struct ksmbd_session *sess, + unsigned int id) +{ + struct ksmbd_tree_connect *tcon; + + down_read(&sess->tree_conns_lock); + tcon = xa_load(&sess->tree_conns, id); + if (tcon) { + if (tcon->t_state != TREE_CONNECTED) + tcon = NULL; + else if (!atomic_inc_not_zero(&tcon->refcount)) + tcon = NULL; + } + up_read(&sess->tree_conns_lock); + + return tcon; +} + +int ksmbd_tree_conn_session_logoff(struct ksmbd_session *sess) +{ + int ret = 0; + struct ksmbd_tree_connect *tc; + unsigned long id; + + if (!sess) + return -EINVAL; + + down_write(&sess->tree_conns_lock); + xa_for_each(&sess->tree_conns, id, tc) { + if (tc->t_state == TREE_DISCONNECTED) { + ret = -ENOENT; + continue; + } + tc->t_state = TREE_DISCONNECTED; + + xa_erase(&sess->tree_conns, tc->id); + ret |= __ksmbd_tree_conn_disconnect(sess, tc); + } + xa_destroy(&sess->tree_conns); + up_write(&sess->tree_conns_lock); + + return ret; +} diff --git a/fs/smb/server/mgmt/tree_connect.h b/fs/smb/server/mgmt/tree_connect.h new file mode 100644 index 000000000..f0023d867 --- /dev/null +++ b/fs/smb/server/mgmt/tree_connect.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2018 Samsung Electronics Co., Ltd. + */ + +#ifndef __TREE_CONNECT_MANAGEMENT_H__ +#define __TREE_CONNECT_MANAGEMENT_H__ + +#include + +#include "../ksmbd_netlink.h" + +struct ksmbd_share_config; +struct ksmbd_user; +struct ksmbd_conn; +struct ksmbd_work; + +enum { + TREE_NEW = 0, + TREE_CONNECTED, + TREE_DISCONNECTED +}; + +struct ksmbd_tree_connect { + int id; + + unsigned int flags; + struct ksmbd_share_config *share_conf; + struct ksmbd_user *user; + + struct list_head list; + + int maximal_access; + bool posix_extensions; + atomic_t refcount; + unsigned int t_state; +}; + +struct ksmbd_tree_conn_status { + unsigned int ret; + struct ksmbd_tree_connect *tree_conn; +}; + +static inline int test_tree_conn_flag(struct ksmbd_tree_connect *tree_conn, + int flag) +{ + return tree_conn->flags & flag; +} + +struct ksmbd_session; + +struct ksmbd_tree_conn_status +ksmbd_tree_conn_connect(struct ksmbd_work *work, const char *share_name); +void ksmbd_tree_connect_put(struct ksmbd_tree_connect *tcon); + +int ksmbd_tree_conn_disconnect(struct ksmbd_session *sess, + struct ksmbd_tree_connect *tree_conn); + +struct ksmbd_tree_connect *ksmbd_tree_conn_lookup(struct ksmbd_session *sess, + unsigned int id); + +int ksmbd_tree_conn_session_logoff(struct ksmbd_session *sess); + +#endif /* __TREE_CONNECT_MANAGEMENT_H__ */ diff --git a/fs/smb/server/mgmt/user_config.c b/fs/smb/server/mgmt/user_config.c new file mode 100644 index 000000000..5efc3d745 --- /dev/null +++ b/fs/smb/server/mgmt/user_config.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 Samsung Electronics Co., Ltd. + */ + +#include +#include + +#include "user_config.h" +#include "../transport_ipc.h" + +struct ksmbd_user *ksmbd_login_user(const char *account) +{ + struct ksmbd_login_response *resp; + struct ksmbd_login_response_ext *resp_ext = NULL; + struct ksmbd_user *user = NULL; + + resp = ksmbd_ipc_login_request(account); + if (!resp) + return NULL; + + if (!(resp->status & KSMBD_USER_FLAG_OK)) + goto out; + + if (resp->status & KSMBD_USER_FLAG_EXTENSION) + resp_ext = ksmbd_ipc_login_request_ext(account); + + user = ksmbd_alloc_user(resp, resp_ext); + kvfree(resp_ext); +out: + kvfree_sensitive(resp, sizeof(*resp)); + return user; +} + +struct ksmbd_user *ksmbd_alloc_user(struct ksmbd_login_response *resp, + struct ksmbd_login_response_ext *resp_ext) +{ + struct ksmbd_user *user; + + /* + * resp->hash_sz is a __u16 taken from the mountd IPC login response but + * resp->hash[] is only KSMBD_REQ_MAX_HASH_SZ bytes. A malformed or + * malicious response can set hash_sz far beyond that (up to 65535), + * making the memcpy() below read past the response object + * (slab-out-of-bounds in ksmbd_alloc_user()). Reject any oversized + * hash rather than trust the length. + */ + if (resp->hash_sz > sizeof(resp->hash)) + return NULL; + + user = kmalloc_obj(struct ksmbd_user, KSMBD_DEFAULT_GFP); + if (!user) + return NULL; + + user->name = kstrdup(resp->account, KSMBD_DEFAULT_GFP); + user->flags = resp->status; + user->gid = resp->gid; + user->uid = resp->uid; + user->passkey_sz = resp->hash_sz; + user->passkey = kmalloc(resp->hash_sz, KSMBD_DEFAULT_GFP); + if (user->passkey) + memcpy(user->passkey, resp->hash, resp->hash_sz); + + user->ngroups = 0; + user->sgid = NULL; + + if (!user->name || !user->passkey) + goto err_free; + + if (resp_ext) { + user->sgid = kmemdup(resp_ext->____payload, + resp_ext->ngroups * sizeof(gid_t), + KSMBD_DEFAULT_GFP); + if (!user->sgid) + goto err_free; + + user->ngroups = resp_ext->ngroups; + ksmbd_debug(SMB, "supplementary groups : %d\n", user->ngroups); + } + + return user; + +err_free: + kfree(user->name); + kfree_sensitive(user->passkey); + kfree(user); + return NULL; +} + +void ksmbd_free_user(struct ksmbd_user *user) +{ + ksmbd_ipc_logout_request(user->name, user->flags); + kfree(user->sgid); + kfree(user->name); + kfree_sensitive(user->passkey); + kfree(user); +} + +bool ksmbd_anonymous_user(struct ksmbd_user *user) +{ + return user->name[0] == '\0'; +} + +bool ksmbd_compare_user(struct ksmbd_user *u1, struct ksmbd_user *u2) +{ + if (strcmp(u1->name, u2->name)) + return false; + if (memcmp(u1->passkey, u2->passkey, u1->passkey_sz)) + return false; + + return true; +} diff --git a/fs/smb/server/mgmt/user_config.h b/fs/smb/server/mgmt/user_config.h new file mode 100644 index 000000000..cc460b4ff --- /dev/null +++ b/fs/smb/server/mgmt/user_config.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2018 Samsung Electronics Co., Ltd. + */ + +#ifndef __USER_CONFIG_MANAGEMENT_H__ +#define __USER_CONFIG_MANAGEMENT_H__ + +#include "../glob.h" + +struct ksmbd_user { + unsigned short flags; + + unsigned int uid; + unsigned int gid; + + char *name; + + size_t passkey_sz; + char *passkey; + int ngroups; + gid_t *sgid; +}; + +static inline bool user_guest(struct ksmbd_user *user) +{ + return user->flags & KSMBD_USER_FLAG_GUEST_ACCOUNT; +} + +static inline void set_user_flag(struct ksmbd_user *user, int flag) +{ + user->flags |= flag; +} + +static inline int test_user_flag(struct ksmbd_user *user, int flag) +{ + return user->flags & flag; +} + +static inline void set_user_guest(struct ksmbd_user *user) +{ +} + +static inline char *user_passkey(struct ksmbd_user *user) +{ + return user->passkey; +} + +static inline char *user_name(struct ksmbd_user *user) +{ + return user->name; +} + +static inline unsigned int user_uid(struct ksmbd_user *user) +{ + return user->uid; +} + +static inline unsigned int user_gid(struct ksmbd_user *user) +{ + return user->gid; +} + +struct ksmbd_user *ksmbd_login_user(const char *account); +struct ksmbd_user *ksmbd_alloc_user(struct ksmbd_login_response *resp, + struct ksmbd_login_response_ext *resp_ext); +void ksmbd_free_user(struct ksmbd_user *user); +bool ksmbd_anonymous_user(struct ksmbd_user *user); +bool ksmbd_compare_user(struct ksmbd_user *u1, struct ksmbd_user *u2); +#endif /* __USER_CONFIG_MANAGEMENT_H__ */ diff --git a/fs/smb/server/mgmt/user_session.c b/fs/smb/server/mgmt/user_session.c new file mode 100644 index 000000000..44dc3f800 --- /dev/null +++ b/fs/smb/server/mgmt/user_session.c @@ -0,0 +1,899 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 Samsung Electronics Co., Ltd. + */ + +#include +#include +#include +#include + +#include "ksmbd_ida.h" +#include "user_session.h" +#include "user_config.h" +#include "tree_connect.h" +#include "share_config.h" +#include "../transport_ipc.h" +#include "../connection.h" +#include "../vfs_cache.h" +#include "../misc.h" +#include "../stats.h" + +static DEFINE_IDA(session_ida); + +#define SESSION_HASH_BITS 12 +#define KSMBD_MAX_PENDING_SESSIONS 1 +static DEFINE_HASHTABLE(sessions_table, SESSION_HASH_BITS); +static DECLARE_RWSEM(sessions_table_lock); + +struct ksmbd_session_rpc { + int id; + unsigned int method; +}; + +#ifdef CONFIG_PROC_FS + +static const struct ksmbd_const_name ksmbd_sess_cap_const_names[] = { + {SMB2_GLOBAL_CAP_DFS, "dfs"}, + {SMB2_GLOBAL_CAP_LEASING, "lease"}, + {SMB2_GLOBAL_CAP_LARGE_MTU, "large-mtu"}, + {SMB2_GLOBAL_CAP_MULTI_CHANNEL, "multi-channel"}, + {SMB2_GLOBAL_CAP_PERSISTENT_HANDLES, "persistent-handles"}, + {SMB2_GLOBAL_CAP_DIRECTORY_LEASING, "dir-lease"}, + {SMB2_GLOBAL_CAP_ENCRYPTION, "encryption"} +}; + +static const struct ksmbd_const_name ksmbd_cipher_const_names[] = { + {le16_to_cpu(SMB2_ENCRYPTION_AES128_CCM), "aes128-ccm"}, + {le16_to_cpu(SMB2_ENCRYPTION_AES128_GCM), "aes128-gcm"}, + {le16_to_cpu(SMB2_ENCRYPTION_AES256_CCM), "aes256-ccm"}, + {le16_to_cpu(SMB2_ENCRYPTION_AES256_GCM), "aes256-gcm"}, +}; + +static const struct ksmbd_const_name ksmbd_signing_const_names[] = { + {SIGNING_ALG_HMAC_SHA256, "hmac-sha256"}, + {SIGNING_ALG_AES_CMAC, "aes-cmac"}, + {SIGNING_ALG_AES_GMAC, "aes-gmac"}, +}; + +static const char *session_state_string(struct ksmbd_session *session) +{ + switch (session->state) { + case SMB2_SESSION_VALID: + return "valid"; + case SMB2_SESSION_IN_PROGRESS: + return "progress"; + case SMB2_SESSION_EXPIRED: + return "expired"; + default: + return ""; + } +} + +static const char *session_user_name(struct ksmbd_session *session) +{ + if (user_guest(session->user)) + return "(Guest)"; + else if (ksmbd_anonymous_user(session->user)) + return "(Anonymous)"; + return session->user->name; +} + +static const char *session_account_type(struct ksmbd_session *session) +{ + if (user_guest(session->user)) + return "guest"; + if (ksmbd_anonymous_user(session->user)) + return "anonymous"; + return "user"; +} + +static unsigned int session_open_file_count(struct ksmbd_session *session) +{ + struct ksmbd_file *fp; + unsigned int count = 0; + unsigned int id; + + read_lock(&session->file_table.lock); + idr_for_each_entry(session->file_table.idr, fp, id) + count++; + read_unlock(&session->file_table.lock); + return count; +} + +static int show_proc_session(struct seq_file *m, void *v) +{ + struct ksmbd_session *sess; + struct ksmbd_tree_connect *tree_conn; + struct ksmbd_share_config *share_conf; + struct channel *chan; + unsigned long id; + int i = 0; + + sess = (struct ksmbd_session *)m->private; + ksmbd_user_session_get(sess); + + seq_printf(m, "user:\t%s\n", session_user_name(sess)); + seq_printf(m, "account_type:\t%s\n", + session_account_type(sess)); + seq_printf(m, "id:\t%llu\n", sess->id); + seq_printf(m, "state:\t%s\n", session_state_string(sess)); + seq_printf(m, "dialect:\t0x%04x\n", sess->dialect); + seq_printf(m, "last_active_seconds:\t%lu\n", + jiffies_to_msecs(jiffies - sess->last_active) / MSEC_PER_SEC); + seq_printf(m, "open_files:\t%u\n", + session_open_file_count(sess)); + + i = 0; + down_read(&sess->chann_lock); + xa_for_each(&sess->ksmbd_chann_list, id, chan) { + const char *name; + +#if IS_ENABLED(CONFIG_IPV6) + if (chan->conn->inet_addr) + seq_printf(m, "client:\t%pI4\n", + &chan->conn->inet_addr); + else + seq_printf(m, "client:\t%pI6c\n", + &chan->conn->inet6_addr); +#else + seq_printf(m, "client:\t%pI4\n", + &chan->conn->inet_addr); +#endif + seq_puts(m, "capabilities:\t"); + ksmbd_proc_show_flag_names(m, + ksmbd_sess_cap_const_names, + ARRAY_SIZE(ksmbd_sess_cap_const_names), + chan->conn->vals->req_capabilities); + seq_putc(m, '\n'); + seq_printf(m, "posix_extensions:\t%s\n", + chan->conn->posix_ext_supported ? "yes" : "no"); + + if (sess->sign) { + unsigned int algorithm = + le16_to_cpu(chan->conn->signing_algorithm); + + name = ksmbd_proc_const_name(ksmbd_signing_const_names, + ARRAY_SIZE(ksmbd_signing_const_names), + algorithm); + if (name) + seq_printf(m, "signing:\t%s\n", name); + else + seq_printf(m, "signing:\t0x%04x\n", + algorithm); + } + if (sess->enc) { + unsigned int cipher = le16_to_cpu(chan->conn->cipher_type); + + name = ksmbd_proc_const_name(ksmbd_cipher_const_names, + ARRAY_SIZE(ksmbd_cipher_const_names), + cipher); + if (name) + seq_printf(m, "encryption:\t%s\n", name); + else + seq_printf(m, "encryption:\t0x%04x\n", + cipher); + } + i++; + } + up_read(&sess->chann_lock); + + seq_printf(m, "channels:\t%d\n", i); + + i = 0; + down_read(&sess->tree_conns_lock); + xa_for_each(&sess->tree_conns, id, tree_conn) { + share_conf = tree_conn->share_conf; + seq_printf(m, "share:\t%s\n", share_conf->name); + seq_printf(m, "tree_id:\t%d\n", tree_conn->id); + seq_printf(m, "share_type:\t%s\n", + test_share_config_flag(share_conf, KSMBD_SHARE_FLAG_PIPE) ? + "pipe" : "disk"); + i++; + } + up_read(&sess->tree_conns_lock); + seq_printf(m, "tree_connects:\t%d\n", i); + + ksmbd_user_session_put(sess); + return 0; +} + +static int create_proc_session(struct ksmbd_session *sess) +{ + char name[30]; + + snprintf(name, sizeof(name), "sessions/%llu", sess->id); + sess->proc_entry = ksmbd_proc_create(name, + show_proc_session, sess); + if (!sess->proc_entry) + return -ENOMEM; + return 0; +} + +static void delete_proc_session(struct ksmbd_session *sess) +{ + if (sess->proc_entry) + proc_remove(sess->proc_entry); +} + +static int show_proc_sessions(struct seq_file *m, void *v) +{ + struct ksmbd_session *session; + struct channel *chan; + int i; + unsigned long id; + + down_read(&sessions_table_lock); + hash_for_each(sessions_table, i, session, hlist) { + down_read(&session->chann_lock); + xa_for_each(&session->ksmbd_chann_list, id, chan) { + down_read(&chan->conn->session_lock); + ksmbd_user_session_get(session); + +#if IS_ENABLED(CONFIG_IPV6) + if (!chan->conn->inet_addr) + seq_printf(m, "client:\t%pI6c\n", &chan->conn->inet6_addr); + else +#endif + seq_printf(m, "client:\t%pI4\n", &chan->conn->inet_addr); + seq_printf(m, "user:\t%s\n", session_user_name(session)); + seq_printf(m, "id:\t%llu\n", session->id); + seq_printf(m, "state:\t%s\n\n", + session_state_string(session)); + + ksmbd_user_session_put(session); + up_read(&chan->conn->session_lock); + } + up_read(&session->chann_lock); + } + up_read(&sessions_table_lock); + return 0; +} + +int create_proc_sessions(void) +{ + if (!ksmbd_proc_create("sessions/sessions", + show_proc_sessions, NULL)) + return -ENOMEM; + return 0; +} +#else +int create_proc_sessions(void) { return 0; } +static int create_proc_session(struct ksmbd_session *sess) { return 0; } +static void delete_proc_session(struct ksmbd_session *sess) {} +#endif + +static void free_channel_list(struct ksmbd_session *sess) +{ + struct channel *chann; + unsigned long index; + + down_write(&sess->chann_lock); + xa_for_each(&sess->ksmbd_chann_list, index, chann) { + xa_erase(&sess->ksmbd_chann_list, index); + kfree_sensitive(chann); + } + + xa_destroy(&sess->ksmbd_chann_list); + up_write(&sess->chann_lock); +} + +static void __session_rpc_close(struct ksmbd_session *sess, + struct ksmbd_session_rpc *entry) +{ + struct ksmbd_rpc_command *resp; + + resp = ksmbd_rpc_close(sess, entry->id); + if (!resp) + pr_err("Unable to close RPC pipe %d\n", entry->id); + + kvfree(resp); + ksmbd_rpc_id_free(entry->id); + kfree(entry); +} + +static void ksmbd_session_rpc_clear_list(struct ksmbd_session *sess) +{ + struct ksmbd_session_rpc *entry; + long index; + + down_write(&sess->rpc_lock); + xa_for_each(&sess->rpc_handle_list, index, entry) { + xa_erase(&sess->rpc_handle_list, index); + __session_rpc_close(sess, entry); + } + up_write(&sess->rpc_lock); + + xa_destroy(&sess->rpc_handle_list); +} + +static int __rpc_method(char *rpc_name) +{ + if (!strcmp(rpc_name, "\\srvsvc") || !strcmp(rpc_name, "srvsvc")) + return KSMBD_RPC_SRVSVC_METHOD_INVOKE; + + if (!strcmp(rpc_name, "\\wkssvc") || !strcmp(rpc_name, "wkssvc")) + return KSMBD_RPC_WKSSVC_METHOD_INVOKE; + + if (!strcmp(rpc_name, "LANMAN") || !strcmp(rpc_name, "lanman")) + return KSMBD_RPC_RAP_METHOD; + + if (!strcmp(rpc_name, "\\samr") || !strcmp(rpc_name, "samr")) + return KSMBD_RPC_SAMR_METHOD_INVOKE; + + if (!strcmp(rpc_name, "\\lsarpc") || !strcmp(rpc_name, "lsarpc")) + return KSMBD_RPC_LSARPC_METHOD_INVOKE; + + if (!strcmp(rpc_name, "\\mdssvc") || !strcmp(rpc_name, "mdssvc")) + return -ENOENT; + + pr_err("Unsupported RPC: %s\n", rpc_name); + return -ENOENT; +} + +int ksmbd_session_rpc_open(struct ksmbd_session *sess, char *rpc_name) +{ + struct ksmbd_session_rpc *entry, *old; + struct ksmbd_rpc_command *resp; + int method, id; + + method = __rpc_method(rpc_name); + if (method < 0) + return method; + + entry = kzalloc_obj(struct ksmbd_session_rpc, KSMBD_DEFAULT_GFP); + if (!entry) + return -ENOMEM; + + entry->method = method; + entry->id = id = ksmbd_ipc_id_alloc(); + if (id < 0) + goto free_entry; + + down_write(&sess->rpc_lock); + old = xa_store(&sess->rpc_handle_list, id, entry, KSMBD_DEFAULT_GFP); + if (xa_is_err(old)) { + up_write(&sess->rpc_lock); + goto free_id; + } + + resp = ksmbd_rpc_open(sess, id); + if (!resp) { + xa_erase(&sess->rpc_handle_list, entry->id); + up_write(&sess->rpc_lock); + goto free_id; + } + + up_write(&sess->rpc_lock); + kvfree(resp); + return id; +free_id: + ksmbd_rpc_id_free(entry->id); +free_entry: + kfree(entry); + return -EINVAL; +} + +void ksmbd_session_rpc_close(struct ksmbd_session *sess, int id) +{ + struct ksmbd_session_rpc *entry; + + down_write(&sess->rpc_lock); + entry = xa_erase(&sess->rpc_handle_list, id); + if (entry) + __session_rpc_close(sess, entry); + up_write(&sess->rpc_lock); +} + +int ksmbd_session_rpc_method(struct ksmbd_session *sess, int id) +{ + struct ksmbd_session_rpc *entry; + + lockdep_assert_held(&sess->rpc_lock); + entry = xa_load(&sess->rpc_handle_list, id); + + return entry ? entry->method : 0; +} + +void ksmbd_session_destroy(struct ksmbd_session *sess) +{ + if (!sess) + return; + + delete_proc_session(sess); + ksmbd_tree_conn_session_logoff(sess); + ksmbd_destroy_file_table(sess); + if (sess->user) + ksmbd_free_user(sess->user); + ksmbd_launch_ksmbd_durable_scavenger(); + ksmbd_session_rpc_clear_list(sess); + free_channel_list(sess); + kfree_sensitive(sess->Preauth_HashValue); + ksmbd_release_id(&session_ida, sess->id); + ida_destroy(&sess->tree_conn_ida); + kfree_sensitive(sess); +} + +static void ksmbd_session_remove_from_table(struct ksmbd_session *sess) +{ + hash_del(&sess->hlist); + ksmbd_counter_dec(KSMBD_COUNTER_SESSIONS); +} + +struct ksmbd_session *__session_lookup(unsigned long long id) +{ + struct ksmbd_session *sess; + + hash_for_each_possible(sessions_table, sess, hlist, id) { + if (id == sess->id) { + sess->last_active = jiffies; + return sess; + } + } + return NULL; +} + +static bool ksmbd_too_many_session_setups(struct ksmbd_conn *conn) +{ + unsigned long id; + struct ksmbd_session *sess; + unsigned int pending = 0; + + down_write(&sessions_table_lock); + down_write(&conn->session_lock); + xa_for_each(&conn->sessions, id, sess) { + if (READ_ONCE(sess->state) != SMB2_SESSION_IN_PROGRESS) + continue; + + if (atomic_read(&sess->refcnt) <= 1 && + time_after(jiffies, sess->last_active + + KSMBD_UNAUTHENTICATED_CONN_TIMEOUT)) { + xa_erase(&conn->sessions, sess->id); + ksmbd_session_remove_from_table(sess); + ksmbd_session_destroy(sess); + continue; + } + pending++; + } + up_write(&conn->session_lock); + up_write(&sessions_table_lock); + return pending >= KSMBD_MAX_PENDING_SESSIONS; +} + +int ksmbd_session_register(struct ksmbd_conn *conn, + struct ksmbd_session *sess) +{ + int ret; + + sess->dialect = conn->dialect; + memcpy(sess->ClientGUID, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE); + /* Bound abandoned SessionId-zero authentication exchanges. */ + if (ksmbd_too_many_session_setups(conn)) + ret = -ENOSPC; + else + ret = xa_err(xa_store(&conn->sessions, sess->id, sess, + KSMBD_DEFAULT_GFP)); + if (ret) { + down_write(&sessions_table_lock); + ksmbd_session_remove_from_table(sess); + up_write(&sessions_table_lock); + ksmbd_user_session_put(sess); + } + + return ret; +} + +void ksmbd_session_unregister(struct ksmbd_conn *conn, + struct ksmbd_session *sess) +{ + struct ksmbd_conn *session_conns[KSMBD_MAX_CHANNELS]; + struct channel *chann; + unsigned long index; + unsigned int nr_conns = 0, i; + bool removed = false; + + down_write(&sessions_table_lock); + if (!hlist_unhashed(&sess->hlist)) { + /* Keep each channel connection stable under sessions_table_lock. */ + down_read(&sess->chann_lock); + xa_for_each(&sess->ksmbd_chann_list, index, chann) { + if (nr_conns == ARRAY_SIZE(session_conns)) + break; + session_conns[nr_conns++] = chann->conn; + } + up_read(&sess->chann_lock); + + ksmbd_session_remove_from_table(sess); + removed = true; + } + + down_write(&conn->session_lock); + if (xa_load(&conn->sessions, sess->id) == sess) + xa_erase(&conn->sessions, sess->id); + up_write(&conn->session_lock); + for (i = 0; i < nr_conns; i++) { + if (session_conns[i] == conn) + continue; + down_write(&session_conns[i]->session_lock); + if (xa_load(&session_conns[i]->sessions, sess->id) == sess) + xa_erase(&session_conns[i]->sessions, sess->id); + up_write(&session_conns[i]->session_lock); + } + up_write(&sessions_table_lock); + + if (removed) + ksmbd_user_session_put(sess); +} + +bool ksmbd_conn_has_valid_or_expired_session(struct ksmbd_conn *conn) +{ + struct ksmbd_session *sess; + unsigned long id; + int state, bkt; + bool found = false; + + down_read(&conn->session_lock); + xa_for_each(&conn->sessions, id, sess) { + state = READ_ONCE(sess->state); + if (state == SMB2_SESSION_VALID || + state == SMB2_SESSION_EXPIRED) { + found = true; + break; + } + } + up_read(&conn->session_lock); + if (found) + return true; + + /* A session bound through SMB3 multichannel is not in conn->sessions. */ + down_read(&sessions_table_lock); + hash_for_each(sessions_table, bkt, sess, hlist) { + state = READ_ONCE(sess->state); + if (state != SMB2_SESSION_VALID && + state != SMB2_SESSION_EXPIRED) + continue; + + down_read(&sess->chann_lock); + found = xa_load(&sess->ksmbd_chann_list, (long)conn); + up_read(&sess->chann_lock); + if (found) + break; + } + up_read(&sessions_table_lock); + return found; +} + +void ksmbd_expire_sessions(void) +{ + struct ksmbd_session *sess; + u64 now = ktime_get_real_seconds(); + int bkt; + + down_read(&sessions_table_lock); + hash_for_each(sessions_table, bkt, sess, hlist) { + if (READ_ONCE(sess->state) != SMB2_SESSION_VALID || + !sess->kerberos_expiry || now < sess->kerberos_expiry) + continue; + + if (cmpxchg(&sess->state, SMB2_SESSION_VALID, + SMB2_SESSION_EXPIRED) == SMB2_SESSION_VALID) + ksmbd_counter_inc(KSMBD_COUNTER_SESSION_TIMEOUTS); + } + up_read(&sessions_table_lock); +} + +static int ksmbd_chann_del(struct ksmbd_conn *conn, struct ksmbd_session *sess) +{ + struct channel *chann; + + down_write(&sess->chann_lock); + chann = xa_erase(&sess->ksmbd_chann_list, (long)conn); + up_write(&sess->chann_lock); + if (!chann) + return -ENOENT; + + kfree_sensitive(chann); + return 0; +} + +void ksmbd_conn_sessions_cleanup(struct ksmbd_conn *conn) +{ + struct ksmbd_session *sess; + unsigned long id; + struct hlist_node *tmp; + int bkt; + + down_write(&sessions_table_lock); + hash_for_each_safe(sessions_table, bkt, tmp, sess, hlist) { + if (!ksmbd_chann_del(conn, sess) && + xa_empty(&sess->ksmbd_chann_list)) { + ksmbd_session_remove_from_table(sess); + down_write(&conn->session_lock); + xa_erase(&conn->sessions, sess->id); + up_write(&conn->session_lock); + if (atomic_dec_and_test(&sess->refcnt)) + ksmbd_session_destroy(sess); + } + } + + down_write(&conn->session_lock); + xa_for_each(&conn->sessions, id, sess) { + ksmbd_chann_del(conn, sess); + if (xa_empty(&sess->ksmbd_chann_list)) { + xa_erase(&conn->sessions, sess->id); + ksmbd_session_remove_from_table(sess); + if (atomic_dec_and_test(&sess->refcnt)) + ksmbd_session_destroy(sess); + } + } + up_write(&conn->session_lock); + up_write(&sessions_table_lock); +} + +bool is_ksmbd_session_in_connection(struct ksmbd_conn *conn, + unsigned long long id) +{ + struct ksmbd_session *sess; + + down_read(&conn->session_lock); + sess = xa_load(&conn->sessions, id); + if (sess) { + up_read(&conn->session_lock); + return true; + } + up_read(&conn->session_lock); + + return false; +} + +struct ksmbd_session *ksmbd_session_lookup(struct ksmbd_conn *conn, + unsigned long long id) +{ + struct ksmbd_session *sess; + + down_read(&conn->session_lock); + sess = xa_load(&conn->sessions, id); + if (sess) { + sess->last_active = jiffies; + ksmbd_user_session_get(sess); + } + up_read(&conn->session_lock); + return sess; +} + +struct ksmbd_session *ksmbd_session_lookup_slowpath(unsigned long long id) +{ + struct ksmbd_session *sess; + + down_read(&sessions_table_lock); + sess = __session_lookup(id); + if (sess) + ksmbd_user_session_get(sess); + up_read(&sessions_table_lock); + + return sess; +} + +struct ksmbd_session *ksmbd_session_lookup_all_states(struct ksmbd_conn *conn, + unsigned long long id) +{ + struct ksmbd_session *sess; + bool channel_found; + + sess = ksmbd_session_lookup(conn, id); + if (!sess) { + sess = ksmbd_session_lookup_slowpath(id); + if (!sess) + return NULL; + + down_read(&sess->chann_lock); + channel_found = xa_load(&sess->ksmbd_chann_list, (long)conn); + up_read(&sess->chann_lock); + if (!channel_found) { + ksmbd_user_session_put(sess); + sess = NULL; + } + } + return sess; +} + +struct ksmbd_session *ksmbd_session_lookup_all(struct ksmbd_conn *conn, + unsigned long long id) +{ + struct ksmbd_session *sess; + + sess = ksmbd_session_lookup_all_states(conn, id); + if (sess && sess->state != SMB2_SESSION_VALID) { + ksmbd_user_session_put(sess); + sess = NULL; + } + return sess; +} + +void ksmbd_user_session_get(struct ksmbd_session *sess) +{ + atomic_inc(&sess->refcnt); +} + +void ksmbd_user_session_put(struct ksmbd_session *sess) +{ + if (!sess) + return; + + if (atomic_read(&sess->refcnt) <= 0) + WARN_ON(1); + else if (atomic_dec_and_test(&sess->refcnt)) + ksmbd_session_destroy(sess); +} + +struct preauth_session *ksmbd_preauth_session_alloc(struct ksmbd_conn *conn, + u64 sess_id) +{ + struct preauth_session *sess; + + sess = kmalloc_obj(struct preauth_session, KSMBD_DEFAULT_GFP); + if (!sess) + return NULL; + + sess->id = sess_id; + memcpy(sess->Preauth_HashValue, conn->preauth_info->Preauth_HashValue, + PREAUTH_HASHVALUE_SIZE); + list_add(&sess->preauth_entry, &conn->preauth_sess_table); + + return sess; +} + +void ksmbd_preauth_session_destroy(struct ksmbd_conn *conn) +{ + struct preauth_session *sess, *tmp; + + list_for_each_entry_safe(sess, tmp, &conn->preauth_sess_table, + preauth_entry) { + list_del(&sess->preauth_entry); + kfree(sess); + } +} + +void destroy_previous_session(struct ksmbd_conn *conn, + struct ksmbd_user *user, u64 id) +{ + struct ksmbd_session *prev_sess; + struct ksmbd_user *prev_user; + int err; + + down_write(&sessions_table_lock); + down_write(&conn->session_lock); + prev_sess = __session_lookup(id); + if (!prev_sess || prev_sess->state == SMB2_SESSION_EXPIRED) + goto out; + + prev_user = prev_sess->user; + if (!prev_user || + strcmp(user->name, prev_user->name) || + user->passkey_sz != prev_user->passkey_sz || + memcmp(user->passkey, prev_user->passkey, user->passkey_sz)) + goto out; + + down_write(&prev_sess->chann_lock); + if (prev_sess->tearing_down) { + up_write(&prev_sess->chann_lock); + goto out; + } + prev_sess->tearing_down = true; + up_write(&prev_sess->chann_lock); + + ksmbd_all_conn_set_status(prev_sess, KSMBD_SESS_NEED_RECONNECT); + err = ksmbd_conn_wait_idle_sess(conn, prev_sess); + if (err) { + down_write(&prev_sess->chann_lock); + prev_sess->tearing_down = false; + up_write(&prev_sess->chann_lock); + ksmbd_all_conn_set_status(prev_sess, KSMBD_SESS_GOOD); + goto out; + } + + ksmbd_destroy_file_table(prev_sess); + prev_sess->kerberos_expiry = 0; + prev_sess->state = SMB2_SESSION_EXPIRED; + ksmbd_all_conn_set_status(prev_sess, KSMBD_SESS_NEED_SETUP); + ksmbd_launch_ksmbd_durable_scavenger(); +out: + up_write(&conn->session_lock); + up_write(&sessions_table_lock); +} + +static bool ksmbd_preauth_session_id_match(struct preauth_session *sess, + unsigned long long id) +{ + return sess->id == id; +} + +struct preauth_session *ksmbd_preauth_session_lookup(struct ksmbd_conn *conn, + unsigned long long id) +{ + struct preauth_session *sess = NULL; + + list_for_each_entry(sess, &conn->preauth_sess_table, preauth_entry) { + if (ksmbd_preauth_session_id_match(sess, id)) + return sess; + } + return NULL; +} + +static int __init_smb2_session(struct ksmbd_session *sess) +{ + int id = ksmbd_acquire_smb2_uid(&session_ida); + + if (id < 0) + return -EINVAL; + sess->id = id; + return 0; +} + +static struct ksmbd_session *__session_create(int protocol) +{ + struct ksmbd_session *sess; + int ret; + + if (protocol != CIFDS_SESSION_FLAG_SMB2) + return NULL; + + sess = kzalloc_obj(struct ksmbd_session, KSMBD_DEFAULT_GFP); + if (!sess) + return NULL; + + ida_init(&sess->tree_conn_ida); + + if (ksmbd_init_file_table(&sess->file_table)) + goto error; + + sess->last_active = jiffies; + sess->state = SMB2_SESSION_IN_PROGRESS; + set_session_flag(sess, protocol); + xa_init(&sess->tree_conns); + xa_init(&sess->ksmbd_chann_list); + xa_init(&sess->rpc_handle_list); + sess->sequence_number = 1; + atomic_set(&sess->refcnt, 2); + init_rwsem(&sess->tree_conns_lock); + init_rwsem(&sess->rpc_lock); + init_rwsem(&sess->chann_lock); + + ret = __init_smb2_session(sess); + if (ret) + goto error; + + down_write(&sessions_table_lock); + hash_add(sessions_table, &sess->hlist, sess->id); + ksmbd_counter_inc(KSMBD_COUNTER_SESSIONS); + up_write(&sessions_table_lock); + + if (create_proc_session(sess)) + pr_warn_ratelimited("Unable to create session %llu procfs entry\n", sess->id); + return sess; + +error: + ksmbd_session_destroy(sess); + return NULL; +} + +struct ksmbd_session *ksmbd_smb2_session_create(void) +{ + return __session_create(CIFDS_SESSION_FLAG_SMB2); +} + +int ksmbd_acquire_tree_conn_id(struct ksmbd_session *sess) +{ + int id = -EINVAL; + + if (test_session_flag(sess, CIFDS_SESSION_FLAG_SMB2)) + id = ksmbd_acquire_smb2_tid(&sess->tree_conn_ida); + + return id; +} + +void ksmbd_release_tree_conn_id(struct ksmbd_session *sess, int id) +{ + if (id >= 0) + ksmbd_release_id(&sess->tree_conn_ida, id); +} diff --git a/fs/smb/server/mgmt/user_session.h b/fs/smb/server/mgmt/user_session.h new file mode 100644 index 000000000..217258551 --- /dev/null +++ b/fs/smb/server/mgmt/user_session.h @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2018 Samsung Electronics Co., Ltd. + */ + +#ifndef __USER_SESSION_MANAGEMENT_H__ +#define __USER_SESSION_MANAGEMENT_H__ + +#include +#include + +#include "../smb_common.h" +#include "../ntlmssp.h" + +#define CIFDS_SESSION_FLAG_SMB2 BIT(1) + +#define PREAUTH_HASHVALUE_SIZE 64 + +struct ksmbd_file_table; + +struct channel { + char sess_key[CIFS_KEY_SIZE]; + __u8 smb3signingkey[SMB3_SIGN_KEY_SIZE]; + struct ksmbd_conn *conn; +}; + +struct preauth_session { + __u8 Preauth_HashValue[PREAUTH_HASHVALUE_SIZE]; + u64 id; + struct list_head preauth_entry; +}; + +struct ksmbd_session { + u64 id; + + __u16 dialect; + char ClientGUID[SMB2_CLIENT_GUID_SIZE]; + + struct ksmbd_user *user; + unsigned int sequence_number; + unsigned int flags; + + bool sign; + bool enc; + bool tearing_down; + + int state; + __u8 *Preauth_HashValue; + + char sess_key[CIFS_KEY_SIZE]; + u64 kerberos_expiry; + + struct hlist_node hlist; + struct rw_semaphore chann_lock; + struct xarray ksmbd_chann_list; + struct xarray tree_conns; + struct ida tree_conn_ida; + struct xarray rpc_handle_list; + + __u8 smb3encryptionkey[SMB3_ENC_DEC_KEY_SIZE]; + __u8 smb3decryptionkey[SMB3_ENC_DEC_KEY_SIZE]; + __u8 smb3signingkey[SMB3_SIGN_KEY_SIZE]; + + struct ksmbd_file_table file_table; + unsigned long last_active; + struct rw_semaphore tree_conns_lock; + +#ifdef CONFIG_PROC_FS + struct proc_dir_entry *proc_entry; +#endif + atomic_t refcnt; + struct rw_semaphore rpc_lock; +}; + +#define KSMBD_MAX_CHANNELS 32 + +static inline int test_session_flag(struct ksmbd_session *sess, int bit) +{ + return sess->flags & bit; +} + +static inline void set_session_flag(struct ksmbd_session *sess, int bit) +{ + sess->flags |= bit; +} + +static inline void clear_session_flag(struct ksmbd_session *sess, int bit) +{ + sess->flags &= ~bit; +} + +struct ksmbd_session *ksmbd_smb2_session_create(void); + +void ksmbd_session_destroy(struct ksmbd_session *sess); + +struct ksmbd_session *ksmbd_session_lookup_slowpath(unsigned long long id); +struct ksmbd_session *ksmbd_session_lookup(struct ksmbd_conn *conn, + unsigned long long id); +bool is_ksmbd_session_in_connection(struct ksmbd_conn *conn, + unsigned long long id); +int ksmbd_session_register(struct ksmbd_conn *conn, + struct ksmbd_session *sess); +void ksmbd_session_unregister(struct ksmbd_conn *conn, + struct ksmbd_session *sess); +void ksmbd_conn_sessions_cleanup(struct ksmbd_conn *conn); +bool ksmbd_conn_has_valid_or_expired_session(struct ksmbd_conn *conn); +void ksmbd_expire_sessions(void); +struct ksmbd_session *__session_lookup(unsigned long long id); +struct ksmbd_session *ksmbd_session_lookup_all(struct ksmbd_conn *conn, + unsigned long long id); +struct ksmbd_session *ksmbd_session_lookup_all_states(struct ksmbd_conn *conn, + unsigned long long id); +void destroy_previous_session(struct ksmbd_conn *conn, + struct ksmbd_user *user, u64 id); +struct preauth_session *ksmbd_preauth_session_alloc(struct ksmbd_conn *conn, + u64 sess_id); +void ksmbd_preauth_session_destroy(struct ksmbd_conn *conn); +struct preauth_session *ksmbd_preauth_session_lookup(struct ksmbd_conn *conn, + unsigned long long id); + +int ksmbd_acquire_tree_conn_id(struct ksmbd_session *sess); +void ksmbd_release_tree_conn_id(struct ksmbd_session *sess, int id); + +int ksmbd_session_rpc_open(struct ksmbd_session *sess, char *rpc_name); +void ksmbd_session_rpc_close(struct ksmbd_session *sess, int id); +int ksmbd_session_rpc_method(struct ksmbd_session *sess, int id); +void ksmbd_user_session_get(struct ksmbd_session *sess); +void ksmbd_user_session_put(struct ksmbd_session *sess); +int create_proc_sessions(void); +#endif /* __USER_SESSION_MANAGEMENT_H__ */ -- cgit v1.3.1