mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs.git
synced 2026-10-31 14:23:49 +01:00
ksmbd_session_register() destroys valid sessions after ten seconds of
inactivity whenever a client starts another SessionSetup exchange. This
confuses Session.IdleTime with Session.ExpirationTime. Windows can create
additional authenticated sessions on an existing connection, so deleting
the older session invalidates its tree connects and makes mapped drives
fail with STATUS_NETWORK_NAME_DELETED.
The session expiration rules require the server to change a valid session
to expired only after its credential expiration time passes. A valid or
expired session otherwise keeps its connection from being scavenged.
Connections that have not negotiated a dialect, have no sessions, or have
only InProgress sessions are disconnected after an implementation-specific
timeout. Use the Windows-compatible 45 second value and run the expiration
check periodically for both TCP and SMB Direct.
Keep zero as an infinite credential expiration time, and count each
Valid-to-Expired transition. Set expired sessions to InProgress when they
reauthenticate. If authentication fails, remove the session from the
global and per-connection tables immediately, including SMB3 multichannel
connections.
Retain protection against abandoned SessionId-zero exchanges by allowing
only one InProgress authentication per connection. Additional exchanges
fail with STATUS_INSUFFICIENT_RESOURCES, and stale InProgress sessions are
reaped after the same 45 second setup timeout. This bounds the original
unauthenticated memory-exhaustion path without evicting established
sessions.
Fixes: ea174a9189 ("ksmbd: destroy expired sessions")
Reported-by: Mobin Aydinfar <[email protected]>
Signed-off-by: Namjae Jeon <[email protected]>
273 lines
8.0 KiB
C
273 lines
8.0 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (C) 2025, LG Electronics.
|
|
* Author(s): Hyunchul Lee <[email protected]>
|
|
* Copyright (C) 2025, Samsung Electronics.
|
|
* Author(s): Vedansh Bhardwaj <[email protected]>
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/seq_file.h>
|
|
|
|
#include "misc.h"
|
|
#include "connection.h"
|
|
#include "server.h"
|
|
#include "stats.h"
|
|
#include "smb_common.h"
|
|
#include "smb2pdu.h"
|
|
#include "vfs_cache.h"
|
|
|
|
static struct proc_dir_entry *ksmbd_proc_fs;
|
|
struct ksmbd_counters ksmbd_counters;
|
|
|
|
struct proc_dir_entry *ksmbd_proc_create(const char *name,
|
|
int (*show)(struct seq_file *m, void *v),
|
|
void *v)
|
|
{
|
|
return proc_create_single_data(name, 0400, ksmbd_proc_fs,
|
|
show, v);
|
|
}
|
|
|
|
void ksmbd_proc_show_flag_names(struct seq_file *m,
|
|
const struct ksmbd_const_name *table,
|
|
int count, unsigned int flags)
|
|
{
|
|
unsigned int remaining = flags;
|
|
bool separator = false;
|
|
int i;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
unsigned int flag = table[i].const_value;
|
|
|
|
if (!flag || (remaining & flag) != flag)
|
|
continue;
|
|
seq_printf(m, "%s%s", separator ? "," : "", table[i].name);
|
|
separator = true;
|
|
remaining &= ~flag;
|
|
}
|
|
|
|
if (remaining)
|
|
seq_printf(m, "%s0x%08x", separator ? "," : "", remaining);
|
|
else if (!separator)
|
|
seq_puts(m, "none");
|
|
}
|
|
|
|
const char *ksmbd_proc_const_name(const struct ksmbd_const_name *table,
|
|
int count, unsigned int const_value)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
if (table[i].const_value == const_value)
|
|
return table[i].name;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
struct ksmbd_const_smb2_process_req {
|
|
unsigned int const_value;
|
|
const char *name;
|
|
};
|
|
|
|
static const struct ksmbd_const_smb2_process_req smb2_process_req[KSMBD_COUNTER_MAX_REQS] = {
|
|
{le16_to_cpu(SMB2_NEGOTIATE), "SMB2_NEGOTIATE"},
|
|
{le16_to_cpu(SMB2_SESSION_SETUP), "SMB2_SESSION_SETUP"},
|
|
{le16_to_cpu(SMB2_LOGOFF), "SMB2_LOGOFF"},
|
|
{le16_to_cpu(SMB2_TREE_CONNECT), "SMB2_TREE_CONNECT"},
|
|
{le16_to_cpu(SMB2_TREE_DISCONNECT), "SMB2_TREE_DISCONNECT"},
|
|
{le16_to_cpu(SMB2_CREATE), "SMB2_CREATE"},
|
|
{le16_to_cpu(SMB2_CLOSE), "SMB2_CLOSE"},
|
|
{le16_to_cpu(SMB2_FLUSH), "SMB2_FLUSH"},
|
|
{le16_to_cpu(SMB2_READ), "SMB2_READ"},
|
|
{le16_to_cpu(SMB2_WRITE), "SMB2_WRITE"},
|
|
{le16_to_cpu(SMB2_LOCK), "SMB2_LOCK"},
|
|
{le16_to_cpu(SMB2_IOCTL), "SMB2_IOCTL"},
|
|
{le16_to_cpu(SMB2_CANCEL), "SMB2_CANCEL"},
|
|
{le16_to_cpu(SMB2_ECHO), "SMB2_ECHO"},
|
|
{le16_to_cpu(SMB2_QUERY_DIRECTORY), "SMB2_QUERY_DIRECTORY"},
|
|
{le16_to_cpu(SMB2_CHANGE_NOTIFY), "SMB2_CHANGE_NOTIFY"},
|
|
{le16_to_cpu(SMB2_QUERY_INFO), "SMB2_QUERY_INFO"},
|
|
{le16_to_cpu(SMB2_SET_INFO), "SMB2_SET_INFO"},
|
|
{le16_to_cpu(SMB2_OPLOCK_BREAK), "SMB2_OPLOCK_BREAK"},
|
|
};
|
|
|
|
static const char *ksmbd_server_state_string(void)
|
|
{
|
|
switch (READ_ONCE(server_conf.state)) {
|
|
case SERVER_STATE_STARTING_UP:
|
|
return "starting";
|
|
case SERVER_STATE_RUNNING:
|
|
return "running";
|
|
case SERVER_STATE_RESETTING:
|
|
return "resetting";
|
|
case SERVER_STATE_SHUTTING_DOWN:
|
|
return "shutdown";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
static const char *ksmbd_signing_mode_string(void)
|
|
{
|
|
switch (server_conf.signing) {
|
|
case KSMBD_CONFIG_OPT_DISABLED:
|
|
return "disabled";
|
|
case KSMBD_CONFIG_OPT_MANDATORY:
|
|
return "mandatory";
|
|
case KSMBD_CONFIG_OPT_AUTO:
|
|
return "auto";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
static void proc_show_runtime_totals(struct seq_file *m)
|
|
{
|
|
struct ksmbd_conn *conn;
|
|
unsigned int clients = 0;
|
|
unsigned int open_files = 0;
|
|
int i;
|
|
|
|
down_read(&conn_list_lock);
|
|
hash_for_each(conn_list, i, conn, hlist) {
|
|
clients++;
|
|
open_files += atomic_read(&conn->stats.open_files_count);
|
|
}
|
|
up_read(&conn_list_lock);
|
|
|
|
seq_printf(m, "clients:\t%u\n", clients);
|
|
seq_printf(m, "open_files:\t%u\n", open_files);
|
|
}
|
|
|
|
static int proc_show_ksmbd_stats(struct seq_file *m, void *v)
|
|
{
|
|
int i;
|
|
|
|
seq_puts(m, "Server\n");
|
|
seq_printf(m, "state:\t%s\n", ksmbd_server_state_string());
|
|
seq_printf(m, "name:\t%s\n", ksmbd_server_string());
|
|
seq_printf(m, "netbios:\t%s\n", ksmbd_netbios_name());
|
|
seq_printf(m, "work_group:\t%s\n", ksmbd_work_group());
|
|
seq_printf(m, "min_protocol:\t%s\n", ksmbd_get_protocol_string(server_conf.min_protocol));
|
|
seq_printf(m, "max_protocol:\t%s\n", ksmbd_get_protocol_string(server_conf.max_protocol));
|
|
seq_printf(m, "flags:\t0x%08x\n", server_conf.flags);
|
|
seq_printf(m, "tcp_port:\t%u\n", server_conf.tcp_port);
|
|
seq_printf(m, "signing:\t%s\n", ksmbd_signing_mode_string());
|
|
seq_printf(m, "signing_enforced:\t%s\n",
|
|
server_conf.enforced_signing ? "yes" : "no");
|
|
seq_printf(m, "bind_interfaces_only:\t%s\n",
|
|
server_conf.bind_interfaces_only ? "yes" : "no");
|
|
seq_printf(m, "max_connections:\t%u\n", server_conf.max_connections);
|
|
seq_printf(m, "max_connections_per_ip:\t%u\n",
|
|
server_conf.max_ip_connections);
|
|
seq_printf(m, "max_inflight_requests:\t%u\n",
|
|
server_conf.max_inflight_req);
|
|
seq_printf(m, "deadtime_seconds:\t%lu\n", server_conf.deadtime / HZ);
|
|
seq_printf(m, "ipc_timeout_seconds:\t%u\n", server_conf.ipc_timeout / HZ);
|
|
if (server_conf.ipc_last_active)
|
|
seq_printf(m, "ipc_last_active_seconds:\t%lu\n",
|
|
jiffies_to_msecs(jiffies - server_conf.ipc_last_active) /
|
|
MSEC_PER_SEC);
|
|
else
|
|
seq_puts(m, "ipc_last_active_seconds:\tnever\n");
|
|
seq_printf(m, "durable_scavenger:\t%s\n",
|
|
ksmbd_durable_scavenger_active() ? "running" : "stopped");
|
|
seq_printf(m, "share_fake_fscaps:\t0x%08x\n",
|
|
server_conf.share_fake_fscaps);
|
|
proc_show_runtime_totals(m);
|
|
seq_printf(m, "sessions:\t%lld\n",
|
|
ksmbd_counter_sum(KSMBD_COUNTER_SESSIONS));
|
|
seq_printf(m, "session_timeouts:\t%lld\n",
|
|
ksmbd_counter_sum(KSMBD_COUNTER_SESSION_TIMEOUTS));
|
|
seq_printf(m, "tree_connects:\t%lld\n",
|
|
ksmbd_counter_sum(KSMBD_COUNTER_TREE_CONNS));
|
|
seq_printf(m, "requests:\t%lld\n",
|
|
ksmbd_counter_sum(KSMBD_COUNTER_REQUESTS));
|
|
seq_printf(m, "read_bytes:\t%lld\n",
|
|
ksmbd_counter_sum(KSMBD_COUNTER_READ_BYTES));
|
|
seq_printf(m, "written_bytes:\t%lld\n",
|
|
ksmbd_counter_sum(KSMBD_COUNTER_WRITE_BYTES));
|
|
|
|
seq_puts(m, "\nSMB2\n");
|
|
for (i = 0; i < KSMBD_COUNTER_MAX_REQS; i++)
|
|
seq_printf(m, "%s:\t%lld\n", smb2_process_req[i].name,
|
|
ksmbd_counter_sum(KSMBD_COUNTER_FIRST_REQ + i));
|
|
|
|
seq_puts(m, "\nSMB2 status\n");
|
|
seq_printf(m, "success:\t%lld\n",
|
|
ksmbd_counter_sum(KSMBD_COUNTER_STATUS_SUCCESS));
|
|
seq_printf(m, "informational:\t%lld\n",
|
|
ksmbd_counter_sum(KSMBD_COUNTER_STATUS_INFORMATIONAL));
|
|
seq_printf(m, "warning:\t%lld\n",
|
|
ksmbd_counter_sum(KSMBD_COUNTER_STATUS_WARNING));
|
|
seq_printf(m, "error:\t%lld\n",
|
|
ksmbd_counter_sum(KSMBD_COUNTER_STATUS_ERROR));
|
|
seq_printf(m, "access_denied:\t%lld\n",
|
|
ksmbd_counter_sum(KSMBD_COUNTER_ERROR_ACCESS_DENIED));
|
|
seq_printf(m, "not_found:\t%lld\n",
|
|
ksmbd_counter_sum(KSMBD_COUNTER_ERROR_NOT_FOUND));
|
|
seq_printf(m, "invalid_parameter:\t%lld\n",
|
|
ksmbd_counter_sum(KSMBD_COUNTER_ERROR_INVALID_PARAMETER));
|
|
seq_printf(m, "sharing_violation:\t%lld\n",
|
|
ksmbd_counter_sum(KSMBD_COUNTER_ERROR_SHARING_VIOLATION));
|
|
seq_printf(m, "not_supported:\t%lld\n",
|
|
ksmbd_counter_sum(KSMBD_COUNTER_ERROR_NOT_SUPPORTED));
|
|
seq_printf(m, "other:\t%lld\n",
|
|
ksmbd_counter_sum(KSMBD_COUNTER_ERROR_OTHER));
|
|
return 0;
|
|
}
|
|
|
|
void ksmbd_proc_cleanup(void)
|
|
{
|
|
int i;
|
|
|
|
if (!ksmbd_proc_fs)
|
|
return;
|
|
|
|
proc_remove(ksmbd_proc_fs);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ksmbd_counters.counters); i++)
|
|
percpu_counter_destroy(&ksmbd_counters.counters[i]);
|
|
|
|
ksmbd_proc_fs = NULL;
|
|
}
|
|
|
|
void ksmbd_proc_reset(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ksmbd_counters.counters); i++)
|
|
percpu_counter_set(&ksmbd_counters.counters[i], 0);
|
|
}
|
|
|
|
int ksmbd_proc_init(void)
|
|
{
|
|
int i;
|
|
int retval = -ENOMEM;
|
|
|
|
ksmbd_proc_fs = proc_mkdir("fs/ksmbd", NULL);
|
|
if (!ksmbd_proc_fs)
|
|
return retval;
|
|
|
|
if (!proc_mkdir_mode("sessions", 0400, ksmbd_proc_fs))
|
|
goto err_out;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ksmbd_counters.counters); i++) {
|
|
retval = percpu_counter_init(&ksmbd_counters.counters[i], 0, GFP_KERNEL);
|
|
if (retval)
|
|
goto err_out;
|
|
}
|
|
|
|
if (!ksmbd_proc_create("server", proc_show_ksmbd_stats, NULL)) {
|
|
retval = -ENOMEM;
|
|
goto err_out;
|
|
}
|
|
|
|
ksmbd_proc_reset();
|
|
return 0;
|
|
err_out:
|
|
ksmbd_proc_cleanup();
|
|
return retval;
|
|
}
|