mirror of
https://github.com/torvalds/linux.git
synced 2026-05-30 01:53:29 +02:00
This patch introduces a /proc filesystem interface to ksmbd, providing visibility into the internal state of the SMB server. This allows administrators and developers to monitor active connections, user sessions, and opened files in real-time without relying on external tools or heavy debugging. Key changes include: - Connection Monitoring (/proc/fs/ksmbd/clients): Displays a list of active network connections, including client IP addresses, SMB dialects, credits, and last active timestamps. - Session Management (/proc/fs/ksmbd/sessions/): Adds a global sessions file to list all authenticated users and their session IDs. - Creates individual session entries (e.g., /proc/fs/ksmbd/sessions/<id>) detailing capabilities (DFS, Multi-channel, etc.), signing/encryption algorithms, and connected tree shares. - File Tracking (/proc/fs/ksmbd/files): Shows all currently opened files across the server, including tree IDs, process IDs (PID), access modes (daccess/saccess), and oplock/lease states. - Statistics & Counters: Implements internal counters for global server metrics, such as the number of tree connections, total sessions, and processed read/write bytes. Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Bahubali B Gumaji <bahubali.bg@samsung.com> Signed-off-by: Sang-Soo Lee <constant.lee@samsung.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org> Signed-off-by: Steve French <stfrench@microsoft.com>
850 lines
19 KiB
C
850 lines
19 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (C) 2018 Samsung Electronics Co., Ltd.
|
|
* Copyright (C) 2018 Namjae Jeon <linkinjeon@kernel.org>
|
|
*/
|
|
|
|
#include <linux/user_namespace.h>
|
|
|
|
#include "smb_common.h"
|
|
#include "server.h"
|
|
#include "misc.h"
|
|
#include "../common/smb2status.h"
|
|
#include "connection.h"
|
|
#include "ksmbd_work.h"
|
|
#include "mgmt/user_session.h"
|
|
#include "mgmt/user_config.h"
|
|
#include "mgmt/tree_connect.h"
|
|
#include "mgmt/share_config.h"
|
|
|
|
/*for shortname implementation */
|
|
static const char *basechars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_-!@#$%";
|
|
#define MANGLE_BASE (strlen(basechars) - 1)
|
|
#define MAGIC_CHAR '~'
|
|
#define PERIOD '.'
|
|
#define mangle(V) ((char)(basechars[(V) % MANGLE_BASE]))
|
|
|
|
struct smb_protocol {
|
|
int index;
|
|
char *name;
|
|
char *prot;
|
|
__u16 prot_id;
|
|
};
|
|
|
|
static struct smb_protocol smb1_protos[] = {
|
|
{
|
|
SMB21_PROT,
|
|
"\2SMB 2.1",
|
|
"SMB2_10",
|
|
SMB21_PROT_ID
|
|
},
|
|
{
|
|
SMB2X_PROT,
|
|
"\2SMB 2.???",
|
|
"SMB2_22",
|
|
SMB2X_PROT_ID
|
|
},
|
|
};
|
|
|
|
static struct smb_protocol smb2_protos[] = {
|
|
{
|
|
SMB21_PROT,
|
|
"\2SMB 2.1",
|
|
"SMB2_10",
|
|
SMB21_PROT_ID
|
|
},
|
|
{
|
|
SMB30_PROT,
|
|
"\2SMB 3.0",
|
|
"SMB3_00",
|
|
SMB30_PROT_ID
|
|
},
|
|
{
|
|
SMB302_PROT,
|
|
"\2SMB 3.02",
|
|
"SMB3_02",
|
|
SMB302_PROT_ID
|
|
},
|
|
{
|
|
SMB311_PROT,
|
|
"\2SMB 3.1.1",
|
|
"SMB3_11",
|
|
SMB311_PROT_ID
|
|
},
|
|
};
|
|
|
|
unsigned int ksmbd_server_side_copy_max_chunk_count(void)
|
|
{
|
|
return 256;
|
|
}
|
|
|
|
unsigned int ksmbd_server_side_copy_max_chunk_size(void)
|
|
{
|
|
return (2U << 30) - 1;
|
|
}
|
|
|
|
unsigned int ksmbd_server_side_copy_max_total_size(void)
|
|
{
|
|
return (2U << 30) - 1;
|
|
}
|
|
|
|
inline int ksmbd_min_protocol(void)
|
|
{
|
|
return SMB21_PROT;
|
|
}
|
|
|
|
inline int ksmbd_max_protocol(void)
|
|
{
|
|
return SMB311_PROT;
|
|
}
|
|
|
|
static const struct {
|
|
int version;
|
|
const char *string;
|
|
} version_strings[] = {
|
|
#ifdef CONFIG_SMB_INSECURE_SERVER
|
|
{SMB1_PROT, SMB1_VERSION_STRING},
|
|
#endif
|
|
{SMB2_PROT, SMB20_VERSION_STRING},
|
|
{SMB21_PROT, SMB21_VERSION_STRING},
|
|
{SMB30_PROT, SMB30_VERSION_STRING},
|
|
{SMB302_PROT, SMB302_VERSION_STRING},
|
|
{SMB311_PROT, SMB311_VERSION_STRING},
|
|
};
|
|
|
|
const char *ksmbd_get_protocol_string(int version)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(version_strings); i++) {
|
|
if (version_strings[i].version == version)
|
|
return version_strings[i].string;
|
|
}
|
|
return "";
|
|
}
|
|
int ksmbd_lookup_protocol_idx(char *str)
|
|
{
|
|
int offt = ARRAY_SIZE(smb1_protos) - 1;
|
|
int len = strlen(str);
|
|
|
|
while (offt >= 0) {
|
|
if (!strncmp(str, smb1_protos[offt].prot, len)) {
|
|
ksmbd_debug(SMB, "selected %s dialect idx = %d\n",
|
|
smb1_protos[offt].prot, offt);
|
|
return smb1_protos[offt].index;
|
|
}
|
|
offt--;
|
|
}
|
|
|
|
offt = ARRAY_SIZE(smb2_protos) - 1;
|
|
while (offt >= 0) {
|
|
if (!strncmp(str, smb2_protos[offt].prot, len)) {
|
|
ksmbd_debug(SMB, "selected %s dialect idx = %d\n",
|
|
smb2_protos[offt].prot, offt);
|
|
return smb2_protos[offt].index;
|
|
}
|
|
offt--;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* ksmbd_verify_smb_message() - check for valid smb2 request header
|
|
* @work: smb work
|
|
*
|
|
* check for valid smb signature and packet direction(request/response)
|
|
*
|
|
* Return: 0 on success, otherwise -EINVAL
|
|
*/
|
|
int ksmbd_verify_smb_message(struct ksmbd_work *work)
|
|
{
|
|
struct smb2_hdr *smb2_hdr = ksmbd_req_buf_next(work);
|
|
struct smb_hdr *hdr;
|
|
|
|
if (smb2_hdr->ProtocolId == SMB2_PROTO_NUMBER)
|
|
return ksmbd_smb2_check_message(work);
|
|
|
|
hdr = smb_get_msg(work->request_buf);
|
|
if (*(__le32 *)hdr->Protocol == SMB1_PROTO_NUMBER &&
|
|
hdr->Command == SMB_COM_NEGOTIATE) {
|
|
work->conn->outstanding_credits++;
|
|
return 0;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
/**
|
|
* ksmbd_smb_request() - check for valid smb request type
|
|
* @conn: connection instance
|
|
*
|
|
* Return: true on success, otherwise false
|
|
*/
|
|
bool ksmbd_smb_request(struct ksmbd_conn *conn)
|
|
{
|
|
__le32 *proto;
|
|
|
|
if (conn->request_buf[0] != 0)
|
|
return false;
|
|
|
|
proto = (__le32 *)smb_get_msg(conn->request_buf);
|
|
if (*proto == SMB2_COMPRESSION_TRANSFORM_ID) {
|
|
pr_err_ratelimited("smb2 compression not support yet");
|
|
return false;
|
|
}
|
|
|
|
if (*proto != SMB1_PROTO_NUMBER &&
|
|
*proto != SMB2_PROTO_NUMBER &&
|
|
*proto != SMB2_TRANSFORM_PROTO_NUM)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool supported_protocol(int idx)
|
|
{
|
|
if (idx == SMB2X_PROT &&
|
|
(server_conf.min_protocol >= SMB21_PROT ||
|
|
server_conf.max_protocol <= SMB311_PROT))
|
|
return true;
|
|
|
|
return (server_conf.min_protocol <= idx &&
|
|
idx <= server_conf.max_protocol);
|
|
}
|
|
|
|
static char *next_dialect(char *dialect, int *next_off, int bcount)
|
|
{
|
|
dialect = dialect + *next_off;
|
|
*next_off = strnlen(dialect, bcount);
|
|
if (dialect[*next_off] != '\0')
|
|
return NULL;
|
|
return dialect;
|
|
}
|
|
|
|
static int ksmbd_lookup_dialect_by_name(char *cli_dialects, __le16 byte_count)
|
|
{
|
|
int i, seq_num, bcount, next;
|
|
char *dialect;
|
|
|
|
for (i = ARRAY_SIZE(smb1_protos) - 1; i >= 0; i--) {
|
|
seq_num = 0;
|
|
next = 0;
|
|
dialect = cli_dialects;
|
|
bcount = le16_to_cpu(byte_count);
|
|
do {
|
|
dialect = next_dialect(dialect, &next, bcount);
|
|
if (!dialect)
|
|
break;
|
|
ksmbd_debug(SMB, "client requested dialect %s\n",
|
|
dialect);
|
|
if (!strcmp(dialect, smb1_protos[i].name)) {
|
|
if (supported_protocol(smb1_protos[i].index)) {
|
|
ksmbd_debug(SMB,
|
|
"selected %s dialect\n",
|
|
smb1_protos[i].name);
|
|
if (smb1_protos[i].index == SMB1_PROT)
|
|
return seq_num;
|
|
return smb1_protos[i].prot_id;
|
|
}
|
|
}
|
|
seq_num++;
|
|
bcount -= (++next);
|
|
} while (bcount > 0);
|
|
}
|
|
|
|
return BAD_PROT_ID;
|
|
}
|
|
|
|
int ksmbd_lookup_dialect_by_id(__le16 *cli_dialects, __le16 dialects_count)
|
|
{
|
|
int i;
|
|
int count;
|
|
|
|
for (i = ARRAY_SIZE(smb2_protos) - 1; i >= 0; i--) {
|
|
count = le16_to_cpu(dialects_count);
|
|
while (--count >= 0) {
|
|
ksmbd_debug(SMB, "client requested dialect 0x%x\n",
|
|
le16_to_cpu(cli_dialects[count]));
|
|
if (le16_to_cpu(cli_dialects[count]) !=
|
|
smb2_protos[i].prot_id)
|
|
continue;
|
|
|
|
if (supported_protocol(smb2_protos[i].index)) {
|
|
ksmbd_debug(SMB, "selected %s dialect\n",
|
|
smb2_protos[i].name);
|
|
return smb2_protos[i].prot_id;
|
|
}
|
|
}
|
|
}
|
|
|
|
return BAD_PROT_ID;
|
|
}
|
|
|
|
static int ksmbd_negotiate_smb_dialect(void *buf)
|
|
{
|
|
int smb_buf_length = get_rfc1002_len(buf);
|
|
__le32 proto = ((struct smb2_hdr *)smb_get_msg(buf))->ProtocolId;
|
|
|
|
if (proto == SMB2_PROTO_NUMBER) {
|
|
struct smb2_negotiate_req *req;
|
|
int smb2_neg_size =
|
|
offsetof(struct smb2_negotiate_req, Dialects);
|
|
|
|
req = (struct smb2_negotiate_req *)smb_get_msg(buf);
|
|
if (smb2_neg_size > smb_buf_length)
|
|
goto err_out;
|
|
|
|
if (struct_size(req, Dialects, le16_to_cpu(req->DialectCount)) >
|
|
smb_buf_length)
|
|
goto err_out;
|
|
|
|
return ksmbd_lookup_dialect_by_id(req->Dialects,
|
|
req->DialectCount);
|
|
}
|
|
|
|
if (proto == SMB1_PROTO_NUMBER) {
|
|
struct smb_negotiate_req *req;
|
|
|
|
req = (struct smb_negotiate_req *)smb_get_msg(buf);
|
|
if (le16_to_cpu(req->ByteCount) < 2)
|
|
goto err_out;
|
|
|
|
if (offsetof(struct smb_negotiate_req, DialectsArray) +
|
|
le16_to_cpu(req->ByteCount) > smb_buf_length) {
|
|
goto err_out;
|
|
}
|
|
|
|
return ksmbd_lookup_dialect_by_name(req->DialectsArray,
|
|
req->ByteCount);
|
|
}
|
|
|
|
err_out:
|
|
return BAD_PROT_ID;
|
|
}
|
|
|
|
#define SMB_COM_NEGOTIATE_EX 0x0
|
|
|
|
/**
|
|
* get_smb1_cmd_val() - get smb command value from smb header
|
|
* @work: smb work containing smb header
|
|
*
|
|
* Return: smb command value
|
|
*/
|
|
static u16 get_smb1_cmd_val(struct ksmbd_work *work)
|
|
{
|
|
return SMB_COM_NEGOTIATE_EX;
|
|
}
|
|
|
|
/**
|
|
* init_smb1_rsp_hdr() - initialize smb negotiate response header
|
|
* @work: smb work containing smb request
|
|
*
|
|
* Return: 0 on success, otherwise -EINVAL
|
|
*/
|
|
static int init_smb1_rsp_hdr(struct ksmbd_work *work)
|
|
{
|
|
struct smb_hdr *rsp_hdr = (struct smb_hdr *)smb_get_msg(work->response_buf);
|
|
struct smb_hdr *rcv_hdr = (struct smb_hdr *)smb_get_msg(work->request_buf);
|
|
|
|
rsp_hdr->Command = SMB_COM_NEGOTIATE;
|
|
*(__le32 *)rsp_hdr->Protocol = SMB1_PROTO_NUMBER;
|
|
rsp_hdr->Flags = SMBFLG_RESPONSE;
|
|
rsp_hdr->Flags2 = SMBFLG2_UNICODE | SMBFLG2_ERR_STATUS |
|
|
SMBFLG2_EXT_SEC | SMBFLG2_IS_LONG_NAME;
|
|
rsp_hdr->Pid = rcv_hdr->Pid;
|
|
rsp_hdr->Mid = rcv_hdr->Mid;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* smb1_check_user_session() - check for valid session for a user
|
|
* @work: smb work containing smb request buffer
|
|
*
|
|
* Return: 0 on success, otherwise error
|
|
*/
|
|
static int smb1_check_user_session(struct ksmbd_work *work)
|
|
{
|
|
unsigned int cmd = work->conn->ops->get_cmd_val(work);
|
|
|
|
if (cmd == SMB_COM_NEGOTIATE_EX)
|
|
return 0;
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
/**
|
|
* smb1_allocate_rsp_buf() - allocate response buffer for a command
|
|
* @work: smb work containing smb request
|
|
*
|
|
* Return: 0 on success, otherwise -ENOMEM
|
|
*/
|
|
static int smb1_allocate_rsp_buf(struct ksmbd_work *work)
|
|
{
|
|
work->response_buf = kzalloc(MAX_CIFS_SMALL_BUFFER_SIZE,
|
|
KSMBD_DEFAULT_GFP);
|
|
work->response_sz = MAX_CIFS_SMALL_BUFFER_SIZE;
|
|
|
|
if (!work->response_buf) {
|
|
pr_err("Failed to allocate %u bytes buffer\n",
|
|
MAX_CIFS_SMALL_BUFFER_SIZE);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* set_smb1_rsp_status() - set error type in smb response header
|
|
* @work: smb work containing smb response header
|
|
* @err: error code to set in response
|
|
*/
|
|
static void set_smb1_rsp_status(struct ksmbd_work *work, __le32 err)
|
|
{
|
|
work->send_no_response = 1;
|
|
}
|
|
|
|
static struct smb_version_ops smb1_server_ops = {
|
|
.get_cmd_val = get_smb1_cmd_val,
|
|
.init_rsp_hdr = init_smb1_rsp_hdr,
|
|
.allocate_rsp_buf = smb1_allocate_rsp_buf,
|
|
.check_user_session = smb1_check_user_session,
|
|
.set_rsp_status = set_smb1_rsp_status,
|
|
};
|
|
|
|
static struct smb_version_values smb1_server_values = {
|
|
.max_credits = SMB2_MAX_CREDITS,
|
|
};
|
|
|
|
static int smb1_negotiate(struct ksmbd_work *work)
|
|
{
|
|
return ksmbd_smb_negotiate_common(work, SMB_COM_NEGOTIATE);
|
|
}
|
|
|
|
static struct smb_version_cmds smb1_server_cmds[1] = {
|
|
[SMB_COM_NEGOTIATE_EX] = { .proc = smb1_negotiate, },
|
|
};
|
|
|
|
static int init_smb1_server(struct ksmbd_conn *conn)
|
|
{
|
|
conn->vals = &smb1_server_values;
|
|
conn->ops = &smb1_server_ops;
|
|
conn->cmds = smb1_server_cmds;
|
|
conn->max_cmds = ARRAY_SIZE(smb1_server_cmds);
|
|
return 0;
|
|
}
|
|
|
|
int ksmbd_init_smb_server(struct ksmbd_conn *conn)
|
|
{
|
|
struct smb_hdr *rcv_hdr = (struct smb_hdr *)smb_get_msg(conn->request_buf);
|
|
__le32 proto;
|
|
|
|
proto = *(__le32 *)rcv_hdr->Protocol;
|
|
if (conn->need_neg == false) {
|
|
if (proto == SMB1_PROTO_NUMBER)
|
|
return -EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
if (proto == SMB1_PROTO_NUMBER)
|
|
return init_smb1_server(conn);
|
|
return init_smb3_11_server(conn);
|
|
}
|
|
|
|
int ksmbd_populate_dot_dotdot_entries(struct ksmbd_work *work, int info_level,
|
|
struct ksmbd_file *dir,
|
|
struct ksmbd_dir_info *d_info,
|
|
char *search_pattern,
|
|
int (*fn)(struct ksmbd_conn *, int,
|
|
struct ksmbd_dir_info *,
|
|
struct ksmbd_kstat *))
|
|
{
|
|
int i, rc = 0;
|
|
struct ksmbd_conn *conn = work->conn;
|
|
struct mnt_idmap *idmap = file_mnt_idmap(dir->filp);
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
struct kstat kstat;
|
|
struct ksmbd_kstat ksmbd_kstat;
|
|
struct dentry *dentry;
|
|
|
|
if (!dir->dot_dotdot[i]) { /* fill dot entry info */
|
|
if (i == 0) {
|
|
d_info->name = ".";
|
|
d_info->name_len = 1;
|
|
dentry = dir->filp->f_path.dentry;
|
|
} else {
|
|
d_info->name = "..";
|
|
d_info->name_len = 2;
|
|
dentry = dir->filp->f_path.dentry->d_parent;
|
|
}
|
|
|
|
if (!match_pattern(d_info->name, d_info->name_len,
|
|
search_pattern)) {
|
|
dir->dot_dotdot[i] = 1;
|
|
continue;
|
|
}
|
|
|
|
ksmbd_kstat.kstat = &kstat;
|
|
rc = ksmbd_vfs_fill_dentry_attrs(work,
|
|
idmap,
|
|
dentry,
|
|
&ksmbd_kstat);
|
|
if (rc)
|
|
break;
|
|
|
|
rc = fn(conn, info_level, d_info, &ksmbd_kstat);
|
|
if (rc)
|
|
break;
|
|
if (d_info->out_buf_len <= 0)
|
|
break;
|
|
|
|
dir->dot_dotdot[i] = 1;
|
|
if (d_info->flags & SMB2_RETURN_SINGLE_ENTRY) {
|
|
d_info->out_buf_len = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* ksmbd_extract_shortname() - get shortname from long filename
|
|
* @conn: connection instance
|
|
* @longname: source long filename
|
|
* @shortname: destination short filename
|
|
*
|
|
* Return: shortname length or 0 when source long name is '.' or '..'
|
|
* TODO: Though this function conforms the restriction of 8.3 Filename spec,
|
|
* but the result is different with Windows 7's one. need to check.
|
|
*/
|
|
int ksmbd_extract_shortname(struct ksmbd_conn *conn, const char *longname,
|
|
char *shortname)
|
|
{
|
|
const char *p;
|
|
char base[9], extension[4];
|
|
char out[13] = {0};
|
|
int baselen = 0;
|
|
int extlen = 0, len = 0;
|
|
unsigned int csum = 0;
|
|
const unsigned char *ptr;
|
|
bool dot_present = true;
|
|
|
|
p = longname;
|
|
if ((*p == '.') || (!(strcmp(p, "..")))) {
|
|
/*no mangling required */
|
|
return 0;
|
|
}
|
|
|
|
p = strrchr(longname, '.');
|
|
if (p == longname) { /*name starts with a dot*/
|
|
strscpy(extension, "___", sizeof(extension));
|
|
} else {
|
|
if (p) {
|
|
p++;
|
|
while (*p && extlen < 3) {
|
|
if (*p != '.')
|
|
extension[extlen++] = toupper(*p);
|
|
p++;
|
|
}
|
|
extension[extlen] = '\0';
|
|
} else {
|
|
dot_present = false;
|
|
}
|
|
}
|
|
|
|
p = longname;
|
|
if (*p == '.') {
|
|
p++;
|
|
longname++;
|
|
}
|
|
while (*p && (baselen < 5)) {
|
|
if (*p != '.')
|
|
base[baselen++] = toupper(*p);
|
|
p++;
|
|
}
|
|
|
|
base[baselen] = MAGIC_CHAR;
|
|
memcpy(out, base, baselen + 1);
|
|
|
|
ptr = longname;
|
|
len = strlen(longname);
|
|
for (; len > 0; len--, ptr++)
|
|
csum += *ptr;
|
|
|
|
csum = csum % (MANGLE_BASE * MANGLE_BASE);
|
|
out[baselen + 1] = mangle(csum / MANGLE_BASE);
|
|
out[baselen + 2] = mangle(csum);
|
|
out[baselen + 3] = PERIOD;
|
|
|
|
if (dot_present)
|
|
memcpy(out + baselen + 4, extension, 4);
|
|
else
|
|
out[baselen + 4] = '\0';
|
|
smbConvertToUTF16((__le16 *)shortname, out, PATH_MAX,
|
|
conn->local_nls, 0);
|
|
len = strlen(out) * 2;
|
|
return len;
|
|
}
|
|
|
|
static int __smb2_negotiate(struct ksmbd_conn *conn)
|
|
{
|
|
return (conn->dialect >= SMB20_PROT_ID &&
|
|
conn->dialect <= SMB311_PROT_ID);
|
|
}
|
|
|
|
static int smb_handle_negotiate(struct ksmbd_work *work)
|
|
{
|
|
struct smb_negotiate_rsp *neg_rsp = smb_get_msg(work->response_buf);
|
|
|
|
ksmbd_debug(SMB, "Unsupported SMB1 protocol\n");
|
|
|
|
if (ksmbd_iov_pin_rsp(work, (void *)neg_rsp,
|
|
sizeof(struct smb_negotiate_rsp)))
|
|
return -ENOMEM;
|
|
|
|
neg_rsp->hdr.Status.CifsError = STATUS_SUCCESS;
|
|
neg_rsp->hdr.WordCount = 1;
|
|
neg_rsp->DialectIndex = cpu_to_le16(work->conn->dialect);
|
|
neg_rsp->ByteCount = 0;
|
|
return 0;
|
|
}
|
|
|
|
int ksmbd_smb_negotiate_common(struct ksmbd_work *work, unsigned int command)
|
|
{
|
|
struct ksmbd_conn *conn = work->conn;
|
|
int ret;
|
|
|
|
conn->dialect =
|
|
ksmbd_negotiate_smb_dialect(work->request_buf);
|
|
ksmbd_debug(SMB, "conn->dialect 0x%x\n", conn->dialect);
|
|
|
|
if (command == SMB2_NEGOTIATE_HE) {
|
|
ret = smb2_handle_negotiate(work);
|
|
return ret;
|
|
}
|
|
|
|
if (command == SMB_COM_NEGOTIATE) {
|
|
if (__smb2_negotiate(conn)) {
|
|
init_smb3_11_server(conn);
|
|
init_smb2_neg_rsp(work);
|
|
ksmbd_debug(SMB, "Upgrade to SMB2 negotiation\n");
|
|
return 0;
|
|
}
|
|
return smb_handle_negotiate(work);
|
|
}
|
|
|
|
pr_err("Unknown SMB negotiation command: %u\n", command);
|
|
return -EINVAL;
|
|
}
|
|
|
|
enum SHARED_MODE_ERRORS {
|
|
SHARE_DELETE_ERROR,
|
|
SHARE_READ_ERROR,
|
|
SHARE_WRITE_ERROR,
|
|
FILE_READ_ERROR,
|
|
FILE_WRITE_ERROR,
|
|
FILE_DELETE_ERROR,
|
|
};
|
|
|
|
static const char * const shared_mode_errors[] = {
|
|
"Current access mode does not permit SHARE_DELETE",
|
|
"Current access mode does not permit SHARE_READ",
|
|
"Current access mode does not permit SHARE_WRITE",
|
|
"Desired access mode does not permit FILE_READ",
|
|
"Desired access mode does not permit FILE_WRITE",
|
|
"Desired access mode does not permit FILE_DELETE",
|
|
};
|
|
|
|
static void smb_shared_mode_error(int error, struct ksmbd_file *prev_fp,
|
|
struct ksmbd_file *curr_fp)
|
|
{
|
|
ksmbd_debug(SMB, "%s\n", shared_mode_errors[error]);
|
|
ksmbd_debug(SMB, "Current mode: 0x%x Desired mode: 0x%x\n",
|
|
prev_fp->saccess, curr_fp->daccess);
|
|
}
|
|
|
|
int ksmbd_smb_check_shared_mode(struct file *filp, struct ksmbd_file *curr_fp)
|
|
{
|
|
int rc = 0;
|
|
struct ksmbd_file *prev_fp;
|
|
|
|
/*
|
|
* Lookup fp in master fp list, and check desired access and
|
|
* shared mode between previous open and current open.
|
|
*/
|
|
down_read(&curr_fp->f_ci->m_lock);
|
|
list_for_each_entry(prev_fp, &curr_fp->f_ci->m_fp_list, node) {
|
|
if (file_inode(filp) != file_inode(prev_fp->filp))
|
|
continue;
|
|
|
|
if (filp == prev_fp->filp)
|
|
continue;
|
|
|
|
if (ksmbd_stream_fd(prev_fp) && ksmbd_stream_fd(curr_fp))
|
|
if (strcmp(prev_fp->stream.name, curr_fp->stream.name))
|
|
continue;
|
|
|
|
if (prev_fp->attrib_only != curr_fp->attrib_only)
|
|
continue;
|
|
|
|
if (!(prev_fp->saccess & FILE_SHARE_DELETE_LE) &&
|
|
curr_fp->daccess & FILE_DELETE_LE) {
|
|
smb_shared_mode_error(SHARE_DELETE_ERROR,
|
|
prev_fp,
|
|
curr_fp);
|
|
rc = -EPERM;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Only check FILE_SHARE_DELETE if stream opened and
|
|
* normal file opened.
|
|
*/
|
|
if (ksmbd_stream_fd(prev_fp) && !ksmbd_stream_fd(curr_fp))
|
|
continue;
|
|
|
|
if (!(prev_fp->saccess & FILE_SHARE_READ_LE) &&
|
|
curr_fp->daccess & (FILE_EXECUTE_LE | FILE_READ_DATA_LE)) {
|
|
smb_shared_mode_error(SHARE_READ_ERROR,
|
|
prev_fp,
|
|
curr_fp);
|
|
rc = -EPERM;
|
|
break;
|
|
}
|
|
|
|
if (!(prev_fp->saccess & FILE_SHARE_WRITE_LE) &&
|
|
curr_fp->daccess & (FILE_WRITE_DATA_LE | FILE_APPEND_DATA_LE)) {
|
|
smb_shared_mode_error(SHARE_WRITE_ERROR,
|
|
prev_fp,
|
|
curr_fp);
|
|
rc = -EPERM;
|
|
break;
|
|
}
|
|
|
|
if (prev_fp->daccess & (FILE_EXECUTE_LE | FILE_READ_DATA_LE) &&
|
|
!(curr_fp->saccess & FILE_SHARE_READ_LE)) {
|
|
smb_shared_mode_error(FILE_READ_ERROR,
|
|
prev_fp,
|
|
curr_fp);
|
|
rc = -EPERM;
|
|
break;
|
|
}
|
|
|
|
if (prev_fp->daccess & (FILE_WRITE_DATA_LE | FILE_APPEND_DATA_LE) &&
|
|
!(curr_fp->saccess & FILE_SHARE_WRITE_LE)) {
|
|
smb_shared_mode_error(FILE_WRITE_ERROR,
|
|
prev_fp,
|
|
curr_fp);
|
|
rc = -EPERM;
|
|
break;
|
|
}
|
|
|
|
if (prev_fp->daccess & FILE_DELETE_LE &&
|
|
!(curr_fp->saccess & FILE_SHARE_DELETE_LE)) {
|
|
smb_shared_mode_error(FILE_DELETE_ERROR,
|
|
prev_fp,
|
|
curr_fp);
|
|
rc = -EPERM;
|
|
break;
|
|
}
|
|
}
|
|
up_read(&curr_fp->f_ci->m_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool is_asterisk(char *p)
|
|
{
|
|
return p && p[0] == '*';
|
|
}
|
|
|
|
int __ksmbd_override_fsids(struct ksmbd_work *work,
|
|
struct ksmbd_share_config *share)
|
|
{
|
|
struct ksmbd_session *sess = work->sess;
|
|
struct ksmbd_user *user = sess->user;
|
|
struct cred *cred;
|
|
struct group_info *gi;
|
|
unsigned int uid;
|
|
unsigned int gid;
|
|
int i;
|
|
|
|
uid = user_uid(user);
|
|
gid = user_gid(user);
|
|
if (share->force_uid != KSMBD_SHARE_INVALID_UID)
|
|
uid = share->force_uid;
|
|
if (share->force_gid != KSMBD_SHARE_INVALID_GID)
|
|
gid = share->force_gid;
|
|
|
|
cred = prepare_kernel_cred(&init_task);
|
|
if (!cred)
|
|
return -ENOMEM;
|
|
|
|
cred->fsuid = make_kuid(&init_user_ns, uid);
|
|
cred->fsgid = make_kgid(&init_user_ns, gid);
|
|
|
|
gi = groups_alloc(user->ngroups);
|
|
if (!gi) {
|
|
abort_creds(cred);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
for (i = 0; i < user->ngroups; i++)
|
|
gi->gid[i] = make_kgid(&init_user_ns, user->sgid[i]);
|
|
|
|
if (user->ngroups)
|
|
groups_sort(gi);
|
|
|
|
set_groups(cred, gi);
|
|
put_group_info(gi);
|
|
|
|
if (!uid_eq(cred->fsuid, GLOBAL_ROOT_UID))
|
|
cred->cap_effective = cap_drop_fs_set(cred->cap_effective);
|
|
|
|
WARN_ON(work->saved_cred);
|
|
work->saved_cred = override_creds(cred);
|
|
return 0;
|
|
}
|
|
|
|
int ksmbd_override_fsids(struct ksmbd_work *work)
|
|
{
|
|
return __ksmbd_override_fsids(work, work->tcon->share_conf);
|
|
}
|
|
|
|
void ksmbd_revert_fsids(struct ksmbd_work *work)
|
|
{
|
|
const struct cred *cred;
|
|
WARN_ON(!work->saved_cred);
|
|
|
|
cred = revert_creds(work->saved_cred);
|
|
work->saved_cred = NULL;
|
|
put_cred(cred);
|
|
}
|
|
|
|
__le32 smb_map_generic_desired_access(__le32 daccess)
|
|
{
|
|
if (daccess & FILE_GENERIC_READ_LE) {
|
|
daccess |= cpu_to_le32(GENERIC_READ_FLAGS);
|
|
daccess &= ~FILE_GENERIC_READ_LE;
|
|
}
|
|
|
|
if (daccess & FILE_GENERIC_WRITE_LE) {
|
|
daccess |= cpu_to_le32(GENERIC_WRITE_FLAGS);
|
|
daccess &= ~FILE_GENERIC_WRITE_LE;
|
|
}
|
|
|
|
if (daccess & FILE_GENERIC_EXECUTE_LE) {
|
|
daccess |= cpu_to_le32(GENERIC_EXECUTE_FLAGS);
|
|
daccess &= ~FILE_GENERIC_EXECUTE_LE;
|
|
}
|
|
|
|
if (daccess & FILE_GENERIC_ALL_LE) {
|
|
daccess |= cpu_to_le32(GENERIC_ALL_FLAGS);
|
|
daccess &= ~FILE_GENERIC_ALL_LE;
|
|
}
|
|
|
|
return daccess;
|
|
}
|