blob: 005aa93a49d692d2ffe173970b9ba4ff8b728c7f [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2016 Namjae Jeon <linkinjeon@kernel.org>
* Copyright (C) 2018 Samsung Electronics Co., Ltd.
*/
#include <linux/inetdevice.h>
#include <net/addrconf.h>
#include <linux/syscalls.h>
#include <linux/namei.h>
#include <linux/statfs.h>
#include <linux/ethtool.h>
#include <linux/falloc.h>
#include "glob.h"
#include "smb2pdu.h"
#include "smbfsctl.h"
#include "oplock.h"
#include "smbacl.h"
#include "auth.h"
#include "asn1.h"
#include "connection.h"
#include "transport_ipc.h"
#include "transport_rdma.h"
#include "vfs.h"
#include "vfs_cache.h"
#include "misc.h"
#include "server.h"
#include "smb_common.h"
#include "smbstatus.h"
#include "ksmbd_work.h"
#include "mgmt/user_config.h"
#include "mgmt/share_config.h"
#include "mgmt/tree_connect.h"
#include "mgmt/user_session.h"
#include "mgmt/ksmbd_ida.h"
#include "ndr.h"
static void __wbuf(struct ksmbd_work *work, void **req, void **rsp)
{
if (work->next_smb2_rcv_hdr_off) {
*req = ksmbd_req_buf_next(work);
*rsp = ksmbd_resp_buf_next(work);
} else {
*req = work->request_buf;
*rsp = work->response_buf;
}
}
#define WORK_BUFFERS(w, rq, rs) __wbuf((w), (void **)&(rq), (void **)&(rs))
/**
* check_session_id() - check for valid session id in smb header
* @conn: connection instance
* @id: session id from smb header
*
* Return: 1 if valid session id, otherwise 0
*/
static inline bool check_session_id(struct ksmbd_conn *conn, u64 id)
{
struct ksmbd_session *sess;
if (id == 0 || id == -1)
return false;
sess = ksmbd_session_lookup_all(conn, id);
if (sess)
return true;
pr_err("Invalid user session id: %llu\n", id);
return false;
}
struct channel *lookup_chann_list(struct ksmbd_session *sess, struct ksmbd_conn *conn)
{
struct channel *chann;
list_for_each_entry(chann, &sess->ksmbd_chann_list, chann_list) {
if (chann->conn == conn)
return chann;
}
return NULL;
}
/**
* smb2_get_ksmbd_tcon() - get tree connection information using a tree id.
* @work: smb work
*
* Return: 0 if there is a tree connection matched or these are
* skipable commands, otherwise error
*/
int smb2_get_ksmbd_tcon(struct ksmbd_work *work)
{
struct smb2_hdr *req_hdr = work->request_buf;
int tree_id;
work->tcon = NULL;
if (work->conn->ops->get_cmd_val(work) == SMB2_TREE_CONNECT_HE ||
work->conn->ops->get_cmd_val(work) == SMB2_CANCEL_HE ||
work->conn->ops->get_cmd_val(work) == SMB2_LOGOFF_HE) {
ksmbd_debug(SMB, "skip to check tree connect request\n");
return 0;
}
if (xa_empty(&work->sess->tree_conns)) {
ksmbd_debug(SMB, "NO tree connected\n");
return -ENOENT;
}
tree_id = le32_to_cpu(req_hdr->Id.SyncId.TreeId);
work->tcon = ksmbd_tree_conn_lookup(work->sess, tree_id);
if (!work->tcon) {
pr_err("Invalid tid %d\n", tree_id);
return -EINVAL;
}
return 1;
}
/**
* smb2_set_err_rsp() - set error response code on smb response
* @work: smb work containing response buffer
*/
void smb2_set_err_rsp(struct ksmbd_work *work)
{
struct smb2_err_rsp *err_rsp;
if (work->next_smb2_rcv_hdr_off)
err_rsp = ksmbd_resp_buf_next(work);
else
err_rsp = work->response_buf;
if (err_rsp->hdr.Status != STATUS_STOPPED_ON_SYMLINK) {
err_rsp->StructureSize = SMB2_ERROR_STRUCTURE_SIZE2_LE;
err_rsp->ErrorContextCount = 0;
err_rsp->Reserved = 0;
err_rsp->ByteCount = 0;
err_rsp->ErrorData[0] = 0;
inc_rfc1001_len(work->response_buf, SMB2_ERROR_STRUCTURE_SIZE2);
}
}
/**
* is_smb2_neg_cmd() - is it smb2 negotiation command
* @work: smb work containing smb header
*
* Return: true if smb2 negotiation command, otherwise false
*/
bool is_smb2_neg_cmd(struct ksmbd_work *work)
{
struct smb2_hdr *hdr = work->request_buf;
/* is it SMB2 header ? */
if (hdr->ProtocolId != SMB2_PROTO_NUMBER)
return false;
/* make sure it is request not response message */
if (hdr->Flags & SMB2_FLAGS_SERVER_TO_REDIR)
return false;
if (hdr->Command != SMB2_NEGOTIATE)
return false;
return true;
}
/**
* is_smb2_rsp() - is it smb2 response
* @work: smb work containing smb response buffer
*
* Return: true if smb2 response, otherwise false
*/
bool is_smb2_rsp(struct ksmbd_work *work)
{
struct smb2_hdr *hdr = work->response_buf;
/* is it SMB2 header ? */
if (hdr->ProtocolId != SMB2_PROTO_NUMBER)
return false;
/* make sure it is response not request message */
if (!(hdr->Flags & SMB2_FLAGS_SERVER_TO_REDIR))
return false;
return true;
}
/**
* get_smb2_cmd_val() - get smb command code from smb header
* @work: smb work containing smb request buffer
*
* Return: smb2 request command value
*/
u16 get_smb2_cmd_val(struct ksmbd_work *work)
{
struct smb2_hdr *rcv_hdr;
if (work->next_smb2_rcv_hdr_off)
rcv_hdr = ksmbd_req_buf_next(work);
else
rcv_hdr = work->request_buf;
return le16_to_cpu(rcv_hdr->Command);
}
/**
* set_smb2_rsp_status() - set error response code on smb2 header
* @work: smb work containing response buffer
* @err: error response code
*/
void set_smb2_rsp_status(struct ksmbd_work *work, __le32 err)
{
struct smb2_hdr *rsp_hdr;
if (work->next_smb2_rcv_hdr_off)
rsp_hdr = ksmbd_resp_buf_next(work);
else
rsp_hdr = work->response_buf;
rsp_hdr->Status = err;
smb2_set_err_rsp(work);
}
/**
* init_smb2_neg_rsp() - initialize smb2 response for negotiate command
* @work: smb work containing smb request buffer
*
* smb2 negotiate response is sent in reply of smb1 negotiate command for
* dialect auto-negotiation.
*/
int init_smb2_neg_rsp(struct ksmbd_work *work)
{
struct smb2_hdr *rsp_hdr;
struct smb2_negotiate_rsp *rsp;
struct ksmbd_conn *conn = work->conn;
if (conn->need_neg == false)
return -EINVAL;
rsp_hdr = work->response_buf;
memset(rsp_hdr, 0, sizeof(struct smb2_hdr) + 2);
rsp_hdr->smb2_buf_length =
cpu_to_be32(smb2_hdr_size_no_buflen(conn->vals));
rsp_hdr->ProtocolId = SMB2_PROTO_NUMBER;
rsp_hdr->StructureSize = SMB2_HEADER_STRUCTURE_SIZE;
rsp_hdr->CreditRequest = cpu_to_le16(2);
rsp_hdr->Command = SMB2_NEGOTIATE;
rsp_hdr->Flags = (SMB2_FLAGS_SERVER_TO_REDIR);
rsp_hdr->NextCommand = 0;
rsp_hdr->MessageId = 0;
rsp_hdr->Id.SyncId.ProcessId = 0;
rsp_hdr->Id.SyncId.TreeId = 0;
rsp_hdr->SessionId = 0;
memset(rsp_hdr->Signature, 0, 16);
rsp = work->response_buf;
WARN_ON(ksmbd_conn_good(work));
rsp->StructureSize = cpu_to_le16(65);
ksmbd_debug(SMB, "conn->dialect 0x%x\n", conn->dialect);
rsp->DialectRevision = cpu_to_le16(conn->dialect);
/* Not setting conn guid rsp->ServerGUID, as it
* not used by client for identifying connection
*/
rsp->Capabilities = cpu_to_le32(conn->vals->capabilities);
/* Default Max Message Size till SMB2.0, 64K*/
rsp->MaxTransactSize = cpu_to_le32(conn->vals->max_trans_size);
rsp->MaxReadSize = cpu_to_le32(conn->vals->max_read_size);
rsp->MaxWriteSize = cpu_to_le32(conn->vals->max_write_size);
rsp->SystemTime = cpu_to_le64(ksmbd_systime());
rsp->ServerStartTime = 0;
rsp->SecurityBufferOffset = cpu_to_le16(128);
rsp->SecurityBufferLength = cpu_to_le16(AUTH_GSS_LENGTH);
ksmbd_copy_gss_neg_header(((char *)(&rsp->hdr) +
sizeof(rsp->hdr.smb2_buf_length)) +
le16_to_cpu(rsp->SecurityBufferOffset));
inc_rfc1001_len(rsp, sizeof(struct smb2_negotiate_rsp) -
sizeof(struct smb2_hdr) - sizeof(rsp->Buffer) +
AUTH_GSS_LENGTH);
rsp->SecurityMode = SMB2_NEGOTIATE_SIGNING_ENABLED_LE;
if (server_conf.signing == KSMBD_CONFIG_OPT_MANDATORY)
rsp->SecurityMode |= SMB2_NEGOTIATE_SIGNING_REQUIRED_LE;
conn->use_spnego = true;
ksmbd_conn_set_need_negotiate(work);
return 0;
}
static int smb2_consume_credit_charge(struct ksmbd_work *work,
unsigned short credit_charge)
{
struct ksmbd_conn *conn = work->conn;
unsigned int rsp_credits = 1;
if (!conn->total_credits)
return 0;
if (credit_charge > 0)
rsp_credits = credit_charge;
conn->total_credits -= rsp_credits;
return rsp_credits;
}
/**
* smb2_set_rsp_credits() - set number of credits in response buffer
* @work: smb work containing smb response buffer
*/
int smb2_set_rsp_credits(struct ksmbd_work *work)
{
struct smb2_hdr *req_hdr = ksmbd_req_buf_next(work);
struct smb2_hdr *hdr = ksmbd_resp_buf_next(work);
struct ksmbd_conn *conn = work->conn;
unsigned short credits_requested = le16_to_cpu(req_hdr->CreditRequest);
unsigned short credit_charge = 1, credits_granted = 0;
unsigned short aux_max, aux_credits, min_credits;
int rsp_credit_charge;
if (hdr->Command == SMB2_CANCEL)
goto out;
/* get default minimum credits by shifting maximum credits by 4 */
min_credits = conn->max_credits >> 4;
if (conn->total_credits >= conn->max_credits) {
pr_err("Total credits overflow: %d\n", conn->total_credits);
conn->total_credits = min_credits;
}
rsp_credit_charge =
smb2_consume_credit_charge(work, le16_to_cpu(req_hdr->CreditCharge));
if (rsp_credit_charge < 0)
return -EINVAL;
hdr->CreditCharge = cpu_to_le16(rsp_credit_charge);
if (credits_requested > 0) {
aux_credits = credits_requested - 1;
aux_max = 32;
if (hdr->Command == SMB2_NEGOTIATE)
aux_max = 0;
aux_credits = (aux_credits < aux_max) ? aux_credits : aux_max;
credits_granted = aux_credits + credit_charge;
/* if credits granted per client is getting bigger than default
* minimum credits then we should wrap it up within the limits.
*/
if ((conn->total_credits + credits_granted) > min_credits)
credits_granted = min_credits - conn->total_credits;
/*
* TODO: Need to adjuct CreditRequest value according to
* current cpu load
*/
} else if (conn->total_credits == 0) {
credits_granted = 1;
}
conn->total_credits += credits_granted;
work->credits_granted += credits_granted;
if (!req_hdr->NextCommand) {
/* Update CreditRequest in last request */
hdr->CreditRequest = cpu_to_le16(work->credits_granted);
}
out:
ksmbd_debug(SMB,
"credits: requested[%d] granted[%d] total_granted[%d]\n",
credits_requested, credits_granted,
conn->total_credits);
return 0;
}
/**
* init_chained_smb2_rsp() - initialize smb2 chained response
* @work: smb work containing smb response buffer
*/
static void init_chained_smb2_rsp(struct ksmbd_work *work)
{
struct smb2_hdr *req = ksmbd_req_buf_next(work);
struct smb2_hdr *rsp = ksmbd_resp_buf_next(work);
struct smb2_hdr *rsp_hdr;
struct smb2_hdr *rcv_hdr;
int next_hdr_offset = 0;
int len, new_len;
/* Len of this response = updated RFC len - offset of previous cmd
* in the compound rsp
*/
/* Storing the current local FID which may be needed by subsequent
* command in the compound request
*/
if (req->Command == SMB2_CREATE && rsp->Status == STATUS_SUCCESS) {
work->compound_fid =
le64_to_cpu(((struct smb2_create_rsp *)rsp)->
VolatileFileId);
work->compound_pfid =
le64_to_cpu(((struct smb2_create_rsp *)rsp)->
PersistentFileId);
work->compound_sid = le64_to_cpu(rsp->SessionId);
}
len = get_rfc1002_len(work->response_buf) - work->next_smb2_rsp_hdr_off;
next_hdr_offset = le32_to_cpu(req->NextCommand);
new_len = ALIGN(len, 8);
inc_rfc1001_len(work->response_buf, ((sizeof(struct smb2_hdr) - 4)
+ new_len - len));
rsp->NextCommand = cpu_to_le32(new_len);
work->next_smb2_rcv_hdr_off += next_hdr_offset;
work->next_smb2_rsp_hdr_off += new_len;
ksmbd_debug(SMB,
"Compound req new_len = %d rcv off = %d rsp off = %d\n",
new_len, work->next_smb2_rcv_hdr_off,
work->next_smb2_rsp_hdr_off);
rsp_hdr = ksmbd_resp_buf_next(work);
rcv_hdr = ksmbd_req_buf_next(work);
if (!(rcv_hdr->Flags & SMB2_FLAGS_RELATED_OPERATIONS)) {
ksmbd_debug(SMB, "related flag should be set\n");
work->compound_fid = KSMBD_NO_FID;
work->compound_pfid = KSMBD_NO_FID;
}
memset((char *)rsp_hdr + 4, 0, sizeof(struct smb2_hdr) + 2);
rsp_hdr->ProtocolId = SMB2_PROTO_NUMBER;
rsp_hdr->StructureSize = SMB2_HEADER_STRUCTURE_SIZE;
rsp_hdr->Command = rcv_hdr->Command;
/*
* Message is response. We don't grant oplock yet.
*/
rsp_hdr->Flags = (SMB2_FLAGS_SERVER_TO_REDIR |
SMB2_FLAGS_RELATED_OPERATIONS);
rsp_hdr->NextCommand = 0;
rsp_hdr->MessageId = rcv_hdr->MessageId;
rsp_hdr->Id.SyncId.ProcessId = rcv_hdr->Id.SyncId.ProcessId;
rsp_hdr->Id.SyncId.TreeId = rcv_hdr->Id.SyncId.TreeId;
rsp_hdr->SessionId = rcv_hdr->SessionId;
memcpy(rsp_hdr->Signature, rcv_hdr->Signature, 16);
}
/**
* is_chained_smb2_message() - check for chained command
* @work: smb work containing smb request buffer
*
* Return: true if chained request, otherwise false
*/
bool is_chained_smb2_message(struct ksmbd_work *work)
{
struct smb2_hdr *hdr = work->request_buf;
unsigned int len, next_cmd;
if (hdr->ProtocolId != SMB2_PROTO_NUMBER)
return false;
hdr = ksmbd_req_buf_next(work);
next_cmd = le32_to_cpu(hdr->NextCommand);
if (next_cmd > 0) {
if ((u64)work->next_smb2_rcv_hdr_off + next_cmd +
__SMB2_HEADER_STRUCTURE_SIZE >
get_rfc1002_len(work->request_buf)) {
pr_err("next command(%u) offset exceeds smb msg size\n",
next_cmd);
return false;
}
ksmbd_debug(SMB, "got SMB2 chained command\n");
init_chained_smb2_rsp(work);
return true;
} else if (work->next_smb2_rcv_hdr_off) {
/*
* This is last request in chained command,
* align response to 8 byte
*/
len = ALIGN(get_rfc1002_len(work->response_buf), 8);
len = len - get_rfc1002_len(work->response_buf);
if (len) {
ksmbd_debug(SMB, "padding len %u\n", len);
inc_rfc1001_len(work->response_buf, len);
if (work->aux_payload_sz)
work->aux_payload_sz += len;
}
}
return false;
}
/**
* init_smb2_rsp_hdr() - initialize smb2 response
* @work: smb work containing smb request buffer
*
* Return: 0
*/
int init_smb2_rsp_hdr(struct ksmbd_work *work)
{
struct smb2_hdr *rsp_hdr = work->response_buf;
struct smb2_hdr *rcv_hdr = work->request_buf;
struct ksmbd_conn *conn = work->conn;
memset(rsp_hdr, 0, sizeof(struct smb2_hdr) + 2);
rsp_hdr->smb2_buf_length =
cpu_to_be32(smb2_hdr_size_no_buflen(conn->vals));
rsp_hdr->ProtocolId = rcv_hdr->ProtocolId;
rsp_hdr->StructureSize = SMB2_HEADER_STRUCTURE_SIZE;
rsp_hdr->Command = rcv_hdr->Command;
/*
* Message is response. We don't grant oplock yet.
*/
rsp_hdr->Flags = (SMB2_FLAGS_SERVER_TO_REDIR);
rsp_hdr->NextCommand = 0;
rsp_hdr->MessageId = rcv_hdr->MessageId;
rsp_hdr->Id.SyncId.ProcessId = rcv_hdr->Id.SyncId.ProcessId;
rsp_hdr->Id.SyncId.TreeId = rcv_hdr->Id.SyncId.TreeId;
rsp_hdr->SessionId = rcv_hdr->SessionId;
memcpy(rsp_hdr->Signature, rcv_hdr->Signature, 16);
work->syncronous = true;
if (work->async_id) {
ksmbd_release_id(&conn->async_ida, work->async_id);
work->async_id = 0;
}
return 0;
}
/**
* smb2_allocate_rsp_buf() - allocate smb2 response buffer
* @work: smb work containing smb request buffer
*
* Return: 0 on success, otherwise -ENOMEM
*/
int smb2_allocate_rsp_buf(struct ksmbd_work *work)
{
struct smb2_hdr *hdr = work->request_buf;
size_t small_sz = MAX_CIFS_SMALL_BUFFER_SIZE;
size_t large_sz = work->conn->vals->max_trans_size + MAX_SMB2_HDR_SIZE;
size_t sz = small_sz;
int cmd = le16_to_cpu(hdr->Command);
if (cmd == SMB2_IOCTL_HE || cmd == SMB2_QUERY_DIRECTORY_HE)
sz = large_sz;
if (cmd == SMB2_QUERY_INFO_HE) {
struct smb2_query_info_req *req;
req = work->request_buf;
if (req->InfoType == SMB2_O_INFO_FILE &&
(req->FileInfoClass == FILE_FULL_EA_INFORMATION ||
req->FileInfoClass == FILE_ALL_INFORMATION))
sz = large_sz;
}
/* allocate large response buf for chained commands */
if (le32_to_cpu(hdr->NextCommand) > 0)
sz = large_sz;
work->response_buf = kvmalloc(sz, GFP_KERNEL | __GFP_ZERO);
if (!work->response_buf)
return -ENOMEM;
work->response_sz = sz;
return 0;
}
/**
* smb2_check_user_session() - check for valid session for a user
* @work: smb work containing smb request buffer
*
* Return: 0 on success, otherwise error
*/
int smb2_check_user_session(struct ksmbd_work *work)
{
struct smb2_hdr *req_hdr = work->request_buf;
struct ksmbd_conn *conn = work->conn;
unsigned int cmd = conn->ops->get_cmd_val(work);
unsigned long long sess_id;
work->sess = NULL;
/*
* SMB2_ECHO, SMB2_NEGOTIATE, SMB2_SESSION_SETUP command do not
* require a session id, so no need to validate user session's for
* these commands.
*/
if (cmd == SMB2_ECHO_HE || cmd == SMB2_NEGOTIATE_HE ||
cmd == SMB2_SESSION_SETUP_HE)
return 0;
if (!ksmbd_conn_good(work))
return -EINVAL;
sess_id = le64_to_cpu(req_hdr->SessionId);
/* Check for validity of user session */
work->sess = ksmbd_session_lookup_all(conn, sess_id);
if (work->sess)
return 1;
ksmbd_debug(SMB, "Invalid user session, Uid %llu\n", sess_id);
return -EINVAL;
}
static void destroy_previous_session(struct ksmbd_user *user, u64 id)
{
struct ksmbd_session *prev_sess = ksmbd_session_lookup_slowpath(id);
struct ksmbd_user *prev_user;
if (!prev_sess)
return;
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)) {
put_session(prev_sess);
return;
}
put_session(prev_sess);
ksmbd_session_destroy(prev_sess);
}
/**
* smb2_get_name() - get filename string from on the wire smb format
* @share: ksmbd_share_config pointer
* @src: source buffer
* @maxlen: maxlen of source string
* @nls_table: nls_table pointer
*
* Return: matching converted filename on success, otherwise error ptr
*/
static char *
smb2_get_name(struct ksmbd_share_config *share, const char *src,
const int maxlen, struct nls_table *local_nls)
{
char *name;
name = smb_strndup_from_utf16(src, maxlen, 1, local_nls);
if (IS_ERR(name)) {
pr_err("failed to get name %ld\n", PTR_ERR(name));
return name;
}
ksmbd_conv_path_to_unix(name);
ksmbd_strip_last_slash(name);
return name;
}
int setup_async_work(struct ksmbd_work *work, void (*fn)(void **), void **arg)
{
struct smb2_hdr *rsp_hdr;
struct ksmbd_conn *conn = work->conn;
int id;
rsp_hdr = work->response_buf;
rsp_hdr->Flags |= SMB2_FLAGS_ASYNC_COMMAND;
id = ksmbd_acquire_async_msg_id(&conn->async_ida);
if (id < 0) {
pr_err("Failed to alloc async message id\n");
return id;
}
work->syncronous = false;
work->async_id = id;
rsp_hdr->Id.AsyncId = cpu_to_le64(id);
ksmbd_debug(SMB,
"Send interim Response to inform async request id : %d\n",
work->async_id);
work->cancel_fn = fn;
work->cancel_argv = arg;
if (list_empty(&work->async_request_entry)) {
spin_lock(&conn->request_lock);
list_add_tail(&work->async_request_entry, &conn->async_requests);
spin_unlock(&conn->request_lock);
}
return 0;
}
void smb2_send_interim_resp(struct ksmbd_work *work, __le32 status)
{
struct smb2_hdr *rsp_hdr;
rsp_hdr = work->response_buf;
smb2_set_err_rsp(work);
rsp_hdr->Status = status;
work->multiRsp = 1;
ksmbd_conn_write(work);
rsp_hdr->Status = 0;
work->multiRsp = 0;
}
static __le32 smb2_get_reparse_tag_special_file(umode_t mode)
{
if (S_ISDIR(mode) || S_ISREG(mode))
return 0;
if (S_ISLNK(mode))
return IO_REPARSE_TAG_LX_SYMLINK_LE;
else if (S_ISFIFO(mode))
return IO_REPARSE_TAG_LX_FIFO_LE;
else if (S_ISSOCK(mode))
return IO_REPARSE_TAG_AF_UNIX_LE;
else if (S_ISCHR(mode))
return IO_REPARSE_TAG_LX_CHR_LE;
else if (S_ISBLK(mode))
return IO_REPARSE_TAG_LX_BLK_LE;
return 0;
}
/**
* smb2_get_dos_mode() - get file mode in dos format from unix mode
* @stat: kstat containing file mode
* @attribute: attribute flags
*
* Return: converted dos mode
*/
static int smb2_get_dos_mode(struct kstat *stat, int attribute)
{
int attr = 0;
if (S_ISDIR(stat->mode)) {
attr = ATTR_DIRECTORY |
(attribute & (ATTR_HIDDEN | ATTR_SYSTEM));
} else {
attr = (attribute & 0x00005137) | ATTR_ARCHIVE;
attr &= ~(ATTR_DIRECTORY);
if (S_ISREG(stat->mode) && (server_conf.share_fake_fscaps &
FILE_SUPPORTS_SPARSE_FILES))
attr |= ATTR_SPARSE;
if (smb2_get_reparse_tag_special_file(stat->mode))
attr |= ATTR_REPARSE;
}
return attr;
}
static void build_preauth_ctxt(struct smb2_preauth_neg_context *pneg_ctxt,
__le16 hash_id)
{
pneg_ctxt->ContextType = SMB2_PREAUTH_INTEGRITY_CAPABILITIES;
pneg_ctxt->DataLength = cpu_to_le16(38);
pneg_ctxt->HashAlgorithmCount = cpu_to_le16(1);
pneg_ctxt->Reserved = cpu_to_le32(0);
pneg_ctxt->SaltLength = cpu_to_le16(SMB311_SALT_SIZE);
get_random_bytes(pneg_ctxt->Salt, SMB311_SALT_SIZE);
pneg_ctxt->HashAlgorithms = hash_id;
}
static void build_encrypt_ctxt(struct smb2_encryption_neg_context *pneg_ctxt,
__le16 cipher_type)
{
pneg_ctxt->ContextType = SMB2_ENCRYPTION_CAPABILITIES;
pneg_ctxt->DataLength = cpu_to_le16(4);
pneg_ctxt->Reserved = cpu_to_le32(0);
pneg_ctxt->CipherCount = cpu_to_le16(1);
pneg_ctxt->Ciphers[0] = cipher_type;
}
static void build_compression_ctxt(struct smb2_compression_ctx *pneg_ctxt,
__le16 comp_algo)
{
pneg_ctxt->ContextType = SMB2_COMPRESSION_CAPABILITIES;
pneg_ctxt->DataLength =
cpu_to_le16(sizeof(struct smb2_compression_ctx)
- sizeof(struct smb2_neg_context));
pneg_ctxt->Reserved = cpu_to_le32(0);
pneg_ctxt->CompressionAlgorithmCount = cpu_to_le16(1);
pneg_ctxt->Reserved1 = cpu_to_le32(0);
pneg_ctxt->CompressionAlgorithms[0] = comp_algo;
}
static void build_sign_cap_ctxt(struct smb2_signing_capabilities *pneg_ctxt,
__le16 sign_algo)
{
pneg_ctxt->ContextType = SMB2_SIGNING_CAPABILITIES;
pneg_ctxt->DataLength =
cpu_to_le16((sizeof(struct smb2_signing_capabilities) + 2)
- sizeof(struct smb2_neg_context));
pneg_ctxt->Reserved = cpu_to_le32(0);
pneg_ctxt->SigningAlgorithmCount = cpu_to_le16(1);
pneg_ctxt->SigningAlgorithms[0] = sign_algo;
}
static void build_posix_ctxt(struct smb2_posix_neg_context *pneg_ctxt)
{
pneg_ctxt->ContextType = SMB2_POSIX_EXTENSIONS_AVAILABLE;
pneg_ctxt->DataLength = cpu_to_le16(POSIX_CTXT_DATA_LEN);
/* SMB2_CREATE_TAG_POSIX is "0x93AD25509CB411E7B42383DE968BCD7C" */
pneg_ctxt->Name[0] = 0x93;
pneg_ctxt->Name[1] = 0xAD;
pneg_ctxt->Name[2] = 0x25;
pneg_ctxt->Name[3] = 0x50;
pneg_ctxt->Name[4] = 0x9C;
pneg_ctxt->Name[5] = 0xB4;
pneg_ctxt->Name[6] = 0x11;
pneg_ctxt->Name[7] = 0xE7;
pneg_ctxt->Name[8] = 0xB4;
pneg_ctxt->Name[9] = 0x23;
pneg_ctxt->Name[10] = 0x83;
pneg_ctxt->Name[11] = 0xDE;
pneg_ctxt->Name[12] = 0x96;
pneg_ctxt->Name[13] = 0x8B;
pneg_ctxt->Name[14] = 0xCD;
pneg_ctxt->Name[15] = 0x7C;
}
static void assemble_neg_contexts(struct ksmbd_conn *conn,
struct smb2_negotiate_rsp *rsp)
{
/* +4 is to account for the RFC1001 len field */
char *pneg_ctxt = (char *)rsp +
le32_to_cpu(rsp->NegotiateContextOffset) + 4;
int neg_ctxt_cnt = 1;
int ctxt_size;
ksmbd_debug(SMB,
"assemble SMB2_PREAUTH_INTEGRITY_CAPABILITIES context\n");
build_preauth_ctxt((struct smb2_preauth_neg_context *)pneg_ctxt,
conn->preauth_info->Preauth_HashId);
rsp->NegotiateContextCount = cpu_to_le16(neg_ctxt_cnt);
inc_rfc1001_len(rsp, AUTH_GSS_PADDING);
ctxt_size = sizeof(struct smb2_preauth_neg_context);
/* Round to 8 byte boundary */
pneg_ctxt += round_up(sizeof(struct smb2_preauth_neg_context), 8);
if (conn->cipher_type) {
ctxt_size = round_up(ctxt_size, 8);
ksmbd_debug(SMB,
"assemble SMB2_ENCRYPTION_CAPABILITIES context\n");
build_encrypt_ctxt((struct smb2_encryption_neg_context *)pneg_ctxt,
conn->cipher_type);
rsp->NegotiateContextCount = cpu_to_le16(++neg_ctxt_cnt);
ctxt_size += sizeof(struct smb2_encryption_neg_context) + 2;
/* Round to 8 byte boundary */
pneg_ctxt +=
round_up(sizeof(struct smb2_encryption_neg_context) + 2,
8);
}
if (conn->compress_algorithm) {
ctxt_size = round_up(ctxt_size, 8);
ksmbd_debug(SMB,
"assemble SMB2_COMPRESSION_CAPABILITIES context\n");
/* Temporarily set to SMB3_COMPRESS_NONE */
build_compression_ctxt((struct smb2_compression_ctx *)pneg_ctxt,
conn->compress_algorithm);
rsp->NegotiateContextCount = cpu_to_le16(++neg_ctxt_cnt);
ctxt_size += sizeof(struct smb2_compression_ctx) + 2;
/* Round to 8 byte boundary */
pneg_ctxt += round_up(sizeof(struct smb2_compression_ctx) + 2,
8);
}
if (conn->posix_ext_supported) {
ctxt_size = round_up(ctxt_size, 8);
ksmbd_debug(SMB,
"assemble SMB2_POSIX_EXTENSIONS_AVAILABLE context\n");
build_posix_ctxt((struct smb2_posix_neg_context *)pneg_ctxt);
rsp->NegotiateContextCount = cpu_to_le16(++neg_ctxt_cnt);
ctxt_size += sizeof(struct smb2_posix_neg_context);
/* Round to 8 byte boundary */
pneg_ctxt += round_up(sizeof(struct smb2_posix_neg_context), 8);
}
if (conn->signing_negotiated) {
ctxt_size = round_up(ctxt_size, 8);
ksmbd_debug(SMB,
"assemble SMB2_SIGNING_CAPABILITIES context\n");
build_sign_cap_ctxt((struct smb2_signing_capabilities *)pneg_ctxt,
conn->signing_algorithm);
rsp->NegotiateContextCount = cpu_to_le16(++neg_ctxt_cnt);
ctxt_size += sizeof(struct smb2_signing_capabilities) + 2;
}
inc_rfc1001_len(rsp, ctxt_size);
}
static __le32 decode_preauth_ctxt(struct ksmbd_conn *conn,
struct smb2_preauth_neg_context *pneg_ctxt)
{
__le32 err = STATUS_NO_PREAUTH_INTEGRITY_HASH_OVERLAP;
if (pneg_ctxt->HashAlgorithms == SMB2_PREAUTH_INTEGRITY_SHA512) {
conn->preauth_info->Preauth_HashId =
SMB2_PREAUTH_INTEGRITY_SHA512;
err = STATUS_SUCCESS;
}
return err;
}
static void decode_encrypt_ctxt(struct ksmbd_conn *conn,
struct smb2_encryption_neg_context *pneg_ctxt,
int len_of_ctxts)
{
int cph_cnt = le16_to_cpu(pneg_ctxt->CipherCount);
int i, cphs_size = cph_cnt * sizeof(__le16);
conn->cipher_type = 0;
if (sizeof(struct smb2_encryption_neg_context) + cphs_size >
len_of_ctxts) {
pr_err("Invalid cipher count(%d)\n", cph_cnt);
return;
}
if (!(server_conf.flags & KSMBD_GLOBAL_FLAG_SMB2_ENCRYPTION))
return;
for (i = 0; i < cph_cnt; i++) {
if (pneg_ctxt->Ciphers[i] == SMB2_ENCRYPTION_AES128_GCM ||
pneg_ctxt->Ciphers[i] == SMB2_ENCRYPTION_AES128_CCM ||
pneg_ctxt->Ciphers[i] == SMB2_ENCRYPTION_AES256_CCM ||
pneg_ctxt->Ciphers[i] == SMB2_ENCRYPTION_AES256_GCM) {
ksmbd_debug(SMB, "Cipher ID = 0x%x\n",
pneg_ctxt->Ciphers[i]);
conn->cipher_type = pneg_ctxt->Ciphers[i];
break;
}
}
}
static void decode_compress_ctxt(struct ksmbd_conn *conn,
struct smb2_compression_ctx *pneg_ctxt)
{
conn->compress_algorithm = SMB3_COMPRESS_NONE;
}
static void decode_sign_cap_ctxt(struct ksmbd_conn *conn,
struct smb2_signing_capabilities *pneg_ctxt,
int len_of_ctxts)
{
int sign_algo_cnt = le16_to_cpu(pneg_ctxt->SigningAlgorithmCount);
int i, sign_alos_size = sign_algo_cnt * sizeof(__le16);
conn->signing_negotiated = false;
if (sizeof(struct smb2_signing_capabilities) + sign_alos_size >
len_of_ctxts) {
pr_err("Invalid signing algorithm count(%d)\n", sign_algo_cnt);
return;
}
for (i = 0; i < sign_algo_cnt; i++) {
if (pneg_ctxt->SigningAlgorithms[i] == SIGNING_ALG_HMAC_SHA256 ||
pneg_ctxt->SigningAlgorithms[i] == SIGNING_ALG_AES_CMAC) {
ksmbd_debug(SMB, "Signing Algorithm ID = 0x%x\n",
pneg_ctxt->SigningAlgorithms[i]);
conn->signing_negotiated = true;
conn->signing_algorithm =
pneg_ctxt->SigningAlgorithms[i];
break;
}
}
}
static __le32 deassemble_neg_contexts(struct ksmbd_conn *conn,
struct smb2_negotiate_req *req)
{
/* +4 is to account for the RFC1001 len field */
struct smb2_neg_context *pctx = (struct smb2_neg_context *)((char *)req + 4);
int i = 0, len_of_ctxts;
int offset = le32_to_cpu(req->NegotiateContextOffset);
int neg_ctxt_cnt = le16_to_cpu(req->NegotiateContextCount);
int len_of_smb = be32_to_cpu(req->hdr.smb2_buf_length);
__le32 status = STATUS_INVALID_PARAMETER;
ksmbd_debug(SMB, "decoding %d negotiate contexts\n", neg_ctxt_cnt);
if (len_of_smb <= offset) {
ksmbd_debug(SMB, "Invalid response: negotiate context offset\n");
return status;
}
len_of_ctxts = len_of_smb - offset;
while (i++ < neg_ctxt_cnt) {
int clen;
/* check that offset is not beyond end of SMB */
if (len_of_ctxts == 0)
break;
if (len_of_ctxts < sizeof(struct smb2_neg_context))
break;
pctx = (struct smb2_neg_context *)((char *)pctx + offset);
clen = le16_to_cpu(pctx->DataLength);
if (clen + sizeof(struct smb2_neg_context) > len_of_ctxts)
break;
if (pctx->ContextType == SMB2_PREAUTH_INTEGRITY_CAPABILITIES) {
ksmbd_debug(SMB,
"deassemble SMB2_PREAUTH_INTEGRITY_CAPABILITIES context\n");
if (conn->preauth_info->Preauth_HashId)
break;
status = decode_preauth_ctxt(conn,
(struct smb2_preauth_neg_context *)pctx);
if (status != STATUS_SUCCESS)
break;
} else if (pctx->ContextType == SMB2_ENCRYPTION_CAPABILITIES) {
ksmbd_debug(SMB,
"deassemble SMB2_ENCRYPTION_CAPABILITIES context\n");
if (conn->cipher_type)
break;
decode_encrypt_ctxt(conn,
(struct smb2_encryption_neg_context *)pctx,
len_of_ctxts);
} else if (pctx->ContextType == SMB2_COMPRESSION_CAPABILITIES) {
ksmbd_debug(SMB,
"deassemble SMB2_COMPRESSION_CAPABILITIES context\n");
if (conn->compress_algorithm)
break;
decode_compress_ctxt(conn,
(struct smb2_compression_ctx *)pctx);
} else if (pctx->ContextType == SMB2_NETNAME_NEGOTIATE_CONTEXT_ID) {
ksmbd_debug(SMB,
"deassemble SMB2_NETNAME_NEGOTIATE_CONTEXT_ID context\n");
} else if (pctx->ContextType == SMB2_POSIX_EXTENSIONS_AVAILABLE) {
ksmbd_debug(SMB,
"deassemble SMB2_POSIX_EXTENSIONS_AVAILABLE context\n");
conn->posix_ext_supported = true;
} else if (pctx->ContextType == SMB2_SIGNING_CAPABILITIES) {
ksmbd_debug(SMB,
"deassemble SMB2_SIGNING_CAPABILITIES context\n");
decode_sign_cap_ctxt(conn,
(struct smb2_signing_capabilities *)pctx,
len_of_ctxts);
}
/* offsets must be 8 byte aligned */
clen = (clen + 7) & ~0x7;
offset = clen + sizeof(struct smb2_neg_context);
len_of_ctxts -= clen + sizeof(struct smb2_neg_context);
}
return status;
}
/**
* smb2_handle_negotiate() - handler for smb2 negotiate command
* @work: smb work containing smb request buffer
*
* Return: 0
*/
int smb2_handle_negotiate(struct ksmbd_work *work)
{
struct ksmbd_conn *conn = work->conn;
struct smb2_negotiate_req *req = work->request_buf;
struct smb2_negotiate_rsp *rsp = work->response_buf;
int rc = 0;
unsigned int smb2_buf_len, smb2_neg_size;
__le32 status;
ksmbd_debug(SMB, "Received negotiate request\n");
conn->need_neg = false;
if (ksmbd_conn_good(work)) {
pr_err("conn->tcp_status is already in CifsGood State\n");
work->send_no_response = 1;
return rc;
}
if (req->DialectCount == 0) {
pr_err("malformed packet\n");
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
rc = -EINVAL;
goto err_out;
}
smb2_buf_len = get_rfc1002_len(work->request_buf);
smb2_neg_size = offsetof(struct smb2_negotiate_req, Dialects) - 4;
if (smb2_neg_size > smb2_buf_len) {
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
rc = -EINVAL;
goto err_out;
}
if (conn->dialect == SMB311_PROT_ID) {
unsigned int nego_ctxt_off = le32_to_cpu(req->NegotiateContextOffset);
if (smb2_buf_len < nego_ctxt_off) {
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
rc = -EINVAL;
goto err_out;
}
if (smb2_neg_size > nego_ctxt_off) {
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
rc = -EINVAL;
goto err_out;
}
if (smb2_neg_size + le16_to_cpu(req->DialectCount) * sizeof(__le16) >
nego_ctxt_off) {
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
rc = -EINVAL;
goto err_out;
}
} else {
if (smb2_neg_size + le16_to_cpu(req->DialectCount) * sizeof(__le16) >
smb2_buf_len) {
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
rc = -EINVAL;
goto err_out;
}
}
conn->cli_cap = le32_to_cpu(req->Capabilities);
switch (conn->dialect) {
case SMB311_PROT_ID:
conn->preauth_info =
kzalloc(sizeof(struct preauth_integrity_info),
GFP_KERNEL);
if (!conn->preauth_info) {
rc = -ENOMEM;
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
goto err_out;
}
status = deassemble_neg_contexts(conn, req);
if (status != STATUS_SUCCESS) {
pr_err("deassemble_neg_contexts error(0x%x)\n",
status);
rsp->hdr.Status = status;
rc = -EINVAL;
goto err_out;
}
rc = init_smb3_11_server(conn);
if (rc < 0) {
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
goto err_out;
}
ksmbd_gen_preauth_integrity_hash(conn,
work->request_buf,
conn->preauth_info->Preauth_HashValue);
rsp->NegotiateContextOffset =
cpu_to_le32(OFFSET_OF_NEG_CONTEXT);
assemble_neg_contexts(conn, rsp);
break;
case SMB302_PROT_ID:
init_smb3_02_server(conn);
break;
case SMB30_PROT_ID:
init_smb3_0_server(conn);
break;
case SMB21_PROT_ID:
init_smb2_1_server(conn);
break;
case SMB2X_PROT_ID:
case BAD_PROT_ID:
default:
ksmbd_debug(SMB, "Server dialect :0x%x not supported\n",
conn->dialect);
rsp->hdr.Status = STATUS_NOT_SUPPORTED;
rc = -EINVAL;
goto err_out;
}
rsp->Capabilities = cpu_to_le32(conn->vals->capabilities);
/* For stats */
conn->connection_type = conn->dialect;
rsp->MaxTransactSize = cpu_to_le32(conn->vals->max_trans_size);
rsp->MaxReadSize = cpu_to_le32(conn->vals->max_read_size);
rsp->MaxWriteSize = cpu_to_le32(conn->vals->max_write_size);
memcpy(conn->ClientGUID, req->ClientGUID,
SMB2_CLIENT_GUID_SIZE);
conn->cli_sec_mode = le16_to_cpu(req->SecurityMode);
rsp->StructureSize = cpu_to_le16(65);
rsp->DialectRevision = cpu_to_le16(conn->dialect);
/* Not setting conn guid rsp->ServerGUID, as it
* not used by client for identifying server
*/
memset(rsp->ServerGUID, 0, SMB2_CLIENT_GUID_SIZE);
rsp->SystemTime = cpu_to_le64(ksmbd_systime());
rsp->ServerStartTime = 0;
ksmbd_debug(SMB, "negotiate context offset %d, count %d\n",
le32_to_cpu(rsp->NegotiateContextOffset),
le16_to_cpu(rsp->NegotiateContextCount));
rsp->SecurityBufferOffset = cpu_to_le16(128);
rsp->SecurityBufferLength = cpu_to_le16(AUTH_GSS_LENGTH);
ksmbd_copy_gss_neg_header(((char *)(&rsp->hdr) +
sizeof(rsp->hdr.smb2_buf_length)) +
le16_to_cpu(rsp->SecurityBufferOffset));
inc_rfc1001_len(rsp, sizeof(struct smb2_negotiate_rsp) -
sizeof(struct smb2_hdr) - sizeof(rsp->Buffer) +
AUTH_GSS_LENGTH);
rsp->SecurityMode = SMB2_NEGOTIATE_SIGNING_ENABLED_LE;
conn->use_spnego = true;
if ((server_conf.signing == KSMBD_CONFIG_OPT_AUTO ||
server_conf.signing == KSMBD_CONFIG_OPT_DISABLED) &&
req->SecurityMode & SMB2_NEGOTIATE_SIGNING_REQUIRED_LE)
conn->sign = true;
else if (server_conf.signing == KSMBD_CONFIG_OPT_MANDATORY) {
server_conf.enforced_signing = true;
rsp->SecurityMode |= SMB2_NEGOTIATE_SIGNING_REQUIRED_LE;
conn->sign = true;
}
conn->srv_sec_mode = le16_to_cpu(rsp->SecurityMode);
ksmbd_conn_set_need_negotiate(work);
err_out:
if (rc < 0)
smb2_set_err_rsp(work);
return rc;
}
static int alloc_preauth_hash(struct ksmbd_session *sess,
struct ksmbd_conn *conn)
{
if (sess->Preauth_HashValue)
return 0;
sess->Preauth_HashValue = kmemdup(conn->preauth_info->Preauth_HashValue,
PREAUTH_HASHVALUE_SIZE, GFP_KERNEL);
if (!sess->Preauth_HashValue)
return -ENOMEM;
return 0;
}
static int generate_preauth_hash(struct ksmbd_work *work)
{
struct ksmbd_conn *conn = work->conn;
struct ksmbd_session *sess = work->sess;
u8 *preauth_hash;
if (conn->dialect != SMB311_PROT_ID)
return 0;
if (conn->binding) {
struct preauth_session *preauth_sess;
preauth_sess = ksmbd_preauth_session_lookup(conn, sess->id);
if (!preauth_sess) {
preauth_sess = ksmbd_preauth_session_alloc(conn, sess->id);
if (!preauth_sess)
return -ENOMEM;
}
preauth_hash = preauth_sess->Preauth_HashValue;
} else {
if (!sess->Preauth_HashValue)
if (alloc_preauth_hash(sess, conn))
return -ENOMEM;
preauth_hash = sess->Preauth_HashValue;
}
ksmbd_gen_preauth_integrity_hash(conn, work->request_buf, preauth_hash);
return 0;
}
static int decode_negotiation_token(struct ksmbd_work *work,
struct negotiate_message *negblob)
{
struct ksmbd_conn *conn = work->conn;
struct smb2_sess_setup_req *req;
int sz;
if (!conn->use_spnego)
return -EINVAL;
req = work->request_buf;
sz = le16_to_cpu(req->SecurityBufferLength);
if (ksmbd_decode_negTokenInit((char *)negblob, sz, conn)) {
if (ksmbd_decode_negTokenTarg((char *)negblob, sz, conn)) {
conn->auth_mechs |= KSMBD_AUTH_NTLMSSP;
conn->preferred_auth_mech = KSMBD_AUTH_NTLMSSP;
conn->use_spnego = false;
}
}
return 0;
}
static int ntlm_negotiate(struct ksmbd_work *work,
struct negotiate_message *negblob)
{
struct smb2_sess_setup_req *req = work->request_buf;
struct smb2_sess_setup_rsp *rsp = work->response_buf;
struct challenge_message *chgblob;
unsigned char *spnego_blob = NULL;
u16 spnego_blob_len;
char *neg_blob;
int sz, rc;
ksmbd_debug(SMB, "negotiate phase\n");
sz = le16_to_cpu(req->SecurityBufferLength);
rc = ksmbd_decode_ntlmssp_neg_blob(negblob, sz, work->sess);
if (rc)
return rc;
sz = le16_to_cpu(rsp->SecurityBufferOffset);
chgblob =
(struct challenge_message *)((char *)&rsp->hdr.ProtocolId + sz);
memset(chgblob, 0, sizeof(struct challenge_message));
if (!work->conn->use_spnego) {
sz = ksmbd_build_ntlmssp_challenge_blob(chgblob, work->sess);
if (sz < 0)
return -ENOMEM;
rsp->SecurityBufferLength = cpu_to_le16(sz);
return 0;
}
sz = sizeof(struct challenge_message);
sz += (strlen(ksmbd_netbios_name()) * 2 + 1 + 4) * 6;
neg_blob = kzalloc(sz, GFP_KERNEL);
if (!neg_blob)
return -ENOMEM;
chgblob = (struct challenge_message *)neg_blob;
sz = ksmbd_build_ntlmssp_challenge_blob(chgblob, work->sess);
if (sz < 0) {
rc = -ENOMEM;
goto out;
}
rc = build_spnego_ntlmssp_neg_blob(&spnego_blob, &spnego_blob_len,
neg_blob, sz);
if (rc) {
rc = -ENOMEM;
goto out;
}
sz = le16_to_cpu(rsp->SecurityBufferOffset);
memcpy((char *)&rsp->hdr.ProtocolId + sz, spnego_blob, spnego_blob_len);
rsp->SecurityBufferLength = cpu_to_le16(spnego_blob_len);
out:
kfree(spnego_blob);
kfree(neg_blob);
return rc;
}
static struct authenticate_message *user_authblob(struct ksmbd_conn *conn,
struct smb2_sess_setup_req *req)
{
int sz;
if (conn->use_spnego && conn->mechToken)
return (struct authenticate_message *)conn->mechToken;
sz = le16_to_cpu(req->SecurityBufferOffset);
return (struct authenticate_message *)((char *)&req->hdr.ProtocolId
+ sz);
}
static struct ksmbd_user *session_user(struct ksmbd_conn *conn,
struct smb2_sess_setup_req *req)
{
struct authenticate_message *authblob;
struct ksmbd_user *user;
char *name;
int sz;
authblob = user_authblob(conn, req);
sz = le32_to_cpu(authblob->UserName.BufferOffset);
name = smb_strndup_from_utf16((const char *)authblob + sz,
le16_to_cpu(authblob->UserName.Length),
true,
conn->local_nls);
if (IS_ERR(name)) {
pr_err("cannot allocate memory\n");
return NULL;
}
ksmbd_debug(SMB, "session setup request for user %s\n", name);
user = ksmbd_login_user(name);
kfree(name);
return user;
}
static int ntlm_authenticate(struct ksmbd_work *work)
{
struct smb2_sess_setup_req *req = work->request_buf;
struct smb2_sess_setup_rsp *rsp = work->response_buf;
struct ksmbd_conn *conn = work->conn;
struct ksmbd_session *sess = work->sess;
struct channel *chann = NULL;
struct ksmbd_user *user;
u64 prev_id;
int sz, rc;
ksmbd_debug(SMB, "authenticate phase\n");
if (conn->use_spnego) {
unsigned char *spnego_blob;
u16 spnego_blob_len;
rc = build_spnego_ntlmssp_auth_blob(&spnego_blob,
&spnego_blob_len,
0);
if (rc)
return -ENOMEM;
sz = le16_to_cpu(rsp->SecurityBufferOffset);
memcpy((char *)&rsp->hdr.ProtocolId + sz, spnego_blob, spnego_blob_len);
rsp->SecurityBufferLength = cpu_to_le16(spnego_blob_len);
kfree(spnego_blob);
inc_rfc1001_len(rsp, spnego_blob_len - 1);
}
user = session_user(conn, req);
if (!user) {
ksmbd_debug(SMB, "Unknown user name or an error\n");
return -EPERM;
}
/* Check for previous session */
prev_id = le64_to_cpu(req->PreviousSessionId);
if (prev_id && prev_id != sess->id)
destroy_previous_session(user, prev_id);
if (sess->state == SMB2_SESSION_VALID) {
/*
* Reuse session if anonymous try to connect
* on reauthetication.
*/
if (ksmbd_anonymous_user(user)) {
ksmbd_free_user(user);
return 0;
}
ksmbd_free_user(sess->user);
}
sess->user = user;
if (user_guest(sess->user)) {
if (conn->sign) {
ksmbd_debug(SMB, "Guest login not allowed when signing enabled\n");
return -EPERM;
}
rsp->SessionFlags = SMB2_SESSION_FLAG_IS_GUEST_LE;
} else {
struct authenticate_message *authblob;
authblob = user_authblob(conn, req);
sz = le16_to_cpu(req->SecurityBufferLength);
rc = ksmbd_decode_ntlmssp_auth_blob(authblob, sz, sess);
if (rc) {
set_user_flag(sess->user, KSMBD_USER_FLAG_BAD_PASSWORD);
ksmbd_debug(SMB, "authentication failed\n");
return -EPERM;
}
/*
* If session state is SMB2_SESSION_VALID, We can assume
* that it is reauthentication. And the user/password
* has been verified, so return it here.
*/
if (sess->state == SMB2_SESSION_VALID) {
if (conn->binding)
goto binding_session;
return 0;
}
if ((conn->sign || server_conf.enforced_signing) ||
(req->SecurityMode & SMB2_NEGOTIATE_SIGNING_REQUIRED))
sess->sign = true;
if (conn->vals->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION &&
conn->ops->generate_encryptionkey &&
!(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING)) {
rc = conn->ops->generate_encryptionkey(sess);
if (rc) {
ksmbd_debug(SMB,
"SMB3 encryption key generation failed\n");
return -EINVAL;
}
sess->enc = true;
rsp->SessionFlags = SMB2_SESSION_FLAG_ENCRYPT_DATA_LE;
/*
* signing is disable if encryption is enable
* on this session
*/
sess->sign = false;
}
}
binding_session:
if (conn->dialect >= SMB30_PROT_ID) {
chann = lookup_chann_list(sess, conn);
if (!chann) {
chann = kmalloc(sizeof(struct channel), GFP_KERNEL);
if (!chann)
return -ENOMEM;
chann->conn = conn;
INIT_LIST_HEAD(&chann->chann_list);
list_add(&chann->chann_list, &sess->ksmbd_chann_list);
}
}
if (conn->ops->generate_signingkey) {
rc = conn->ops->generate_signingkey(sess, conn);
if (rc) {
ksmbd_debug(SMB, "SMB3 signing key generation failed\n");
return -EINVAL;
}
}
if (!ksmbd_conn_lookup_dialect(conn)) {
pr_err("fail to verify the dialect\n");
return -ENOENT;
}
return 0;
}
#ifdef CONFIG_SMB_SERVER_KERBEROS5
static int krb5_authenticate(struct ksmbd_work *work)
{
struct smb2_sess_setup_req *req = work->request_buf;
struct smb2_sess_setup_rsp *rsp = work->response_buf;
struct ksmbd_conn *conn = work->conn;
struct ksmbd_session *sess = work->sess;
char *in_blob, *out_blob;
struct channel *chann = NULL;
u64 prev_sess_id;
int in_len, out_len;
int retval;
in_blob = (char *)&req->hdr.ProtocolId +
le16_to_cpu(req->SecurityBufferOffset);
in_len = le16_to_cpu(req->SecurityBufferLength);
out_blob = (char *)&rsp->hdr.ProtocolId +
le16_to_cpu(rsp->SecurityBufferOffset);
out_len = work->response_sz -
offsetof(struct smb2_hdr, smb2_buf_length) -
le16_to_cpu(rsp->SecurityBufferOffset);
/* Check previous session */
prev_sess_id = le64_to_cpu(req->PreviousSessionId);
if (prev_sess_id && prev_sess_id != sess->id)
destroy_previous_session(sess->user, prev_sess_id);
if (sess->state == SMB2_SESSION_VALID)
ksmbd_free_user(sess->user);
retval = ksmbd_krb5_authenticate(sess, in_blob, in_len,
out_blob, &out_len);
if (retval) {
ksmbd_debug(SMB, "krb5 authentication failed\n");
return -EINVAL;
}
rsp->SecurityBufferLength = cpu_to_le16(out_len);
inc_rfc1001_len(rsp, out_len - 1);
if ((conn->sign || server_conf.enforced_signing) ||
(req->SecurityMode & SMB2_NEGOTIATE_SIGNING_REQUIRED))
sess->sign = true;
if ((conn->vals->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION) &&
conn->ops->generate_encryptionkey) {
retval = conn->ops->generate_encryptionkey(sess);
if (retval) {
ksmbd_debug(SMB,
"SMB3 encryption key generation failed\n");
return -EINVAL;
}
sess->enc = true;
rsp->SessionFlags = SMB2_SESSION_FLAG_ENCRYPT_DATA_LE;
sess->sign = false;
}
if (conn->dialect >= SMB30_PROT_ID) {
chann = lookup_chann_list(sess, conn);
if (!chann) {
chann = kmalloc(sizeof(struct channel), GFP_KERNEL);
if (!chann)
return -ENOMEM;
chann->conn = conn;
INIT_LIST_HEAD(&chann->chann_list);
list_add(&chann->chann_list, &sess->ksmbd_chann_list);
}
}
if (conn->ops->generate_signingkey) {
retval = conn->ops->generate_signingkey(sess, conn);
if (retval) {
ksmbd_debug(SMB, "SMB3 signing key generation failed\n");
return -EINVAL;
}
}
if (!ksmbd_conn_lookup_dialect(conn)) {
pr_err("fail to verify the dialect\n");
return -ENOENT;
}
return 0;
}
#else
static int krb5_authenticate(struct ksmbd_work *work)
{
return -EOPNOTSUPP;
}
#endif
int smb2_sess_setup(struct ksmbd_work *work)
{
struct ksmbd_conn *conn = work->conn;
struct smb2_sess_setup_req *req = work->request_buf;
struct smb2_sess_setup_rsp *rsp = work->response_buf;
struct ksmbd_session *sess;
struct negotiate_message *negblob;
int rc = 0;
ksmbd_debug(SMB, "Received request for session setup\n");
rsp->StructureSize = cpu_to_le16(9);
rsp->SessionFlags = 0;
rsp->SecurityBufferOffset = cpu_to_le16(72);
rsp->SecurityBufferLength = 0;
inc_rfc1001_len(rsp, 9);
if (!req->hdr.SessionId) {
sess = ksmbd_smb2_session_create();
if (!sess) {
rc = -ENOMEM;
goto out_err;
}
rsp->hdr.SessionId = cpu_to_le64(sess->id);
ksmbd_session_register(conn, sess);
} else if (conn->dialect >= SMB30_PROT_ID &&
(server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL) &&
req->Flags & SMB2_SESSION_REQ_FLAG_BINDING) {
u64 sess_id = le64_to_cpu(req->hdr.SessionId);
sess = ksmbd_session_lookup_slowpath(sess_id);
if (!sess) {
rc = -ENOENT;
goto out_err;
}
if (conn->dialect != sess->conn->dialect) {
rc = -EINVAL;
goto out_err;
}
if (!(req->hdr.Flags & SMB2_FLAGS_SIGNED)) {
rc = -EINVAL;
goto out_err;
}
if (strncmp(conn->ClientGUID, sess->conn->ClientGUID,
SMB2_CLIENT_GUID_SIZE)) {
rc = -ENOENT;
goto out_err;
}
if (sess->state == SMB2_SESSION_IN_PROGRESS) {
rc = -EACCES;
goto out_err;
}
if (sess->state == SMB2_SESSION_EXPIRED) {
rc = -EFAULT;
goto out_err;
}
if (ksmbd_session_lookup(conn, sess_id)) {
rc = -EACCES;
goto out_err;
}
conn->binding = true;
} else if ((conn->dialect < SMB30_PROT_ID ||
server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL) &&
(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING)) {
sess = NULL;
rc = -EACCES;
goto out_err;
} else {
sess = ksmbd_session_lookup(conn,
le64_to_cpu(req->hdr.SessionId));
if (!sess) {
rc = -ENOENT;
goto out_err;
}
}
work->sess = sess;
if (sess->state == SMB2_SESSION_EXPIRED)
sess->state = SMB2_SESSION_IN_PROGRESS;
negblob = (struct negotiate_message *)((char *)&req->hdr.ProtocolId +
le16_to_cpu(req->SecurityBufferOffset));
if (decode_negotiation_token(work, negblob) == 0) {
if (conn->mechToken)
negblob = (struct negotiate_message *)conn->mechToken;
}
if (server_conf.auth_mechs & conn->auth_mechs) {
rc = generate_preauth_hash(work);
if (rc)
goto out_err;
if (conn->preferred_auth_mech &
(KSMBD_AUTH_KRB5 | KSMBD_AUTH_MSKRB5)) {
rc = krb5_authenticate(work);
if (rc) {
rc = -EINVAL;
goto out_err;
}
ksmbd_conn_set_good(work);
sess->state = SMB2_SESSION_VALID;
kfree(sess->Preauth_HashValue);
sess->Preauth_HashValue = NULL;
} else if (conn->preferred_auth_mech == KSMBD_AUTH_NTLMSSP) {
if (negblob->MessageType == NtLmNegotiate) {
rc = ntlm_negotiate(work, negblob);
if (rc)
goto out_err;
rsp->hdr.Status =
STATUS_MORE_PROCESSING_REQUIRED;
/*
* Note: here total size -1 is done as an
* adjustment for 0 size blob
*/
inc_rfc1001_len(rsp, le16_to_cpu(rsp->SecurityBufferLength) - 1);
} else if (negblob->MessageType == NtLmAuthenticate) {
rc = ntlm_authenticate(work);
if (rc)
goto out_err;
ksmbd_conn_set_good(work);
sess->state = SMB2_SESSION_VALID;
if (conn->binding) {
struct preauth_session *preauth_sess;
preauth_sess =
ksmbd_preauth_session_lookup(conn, sess->id);
if (preauth_sess) {
list_del(&preauth_sess->preauth_entry);
kfree(preauth_sess);
}
}
kfree(sess->Preauth_HashValue);
sess->Preauth_HashValue = NULL;
}
} else {
/* TODO: need one more negotiation */
pr_err("Not support the preferred authentication\n");
rc = -EINVAL;
}
} else {
pr_err("Not support authentication\n");
rc = -EINVAL;
}
out_err:
if (rc == -EINVAL)
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
else if (rc == -ENOENT)
rsp->hdr.Status = STATUS_USER_SESSION_DELETED;
else if (rc == -EACCES)
rsp->hdr.Status = STATUS_REQUEST_NOT_ACCEPTED;
else if (rc == -EFAULT)
rsp->hdr.Status = STATUS_NETWORK_SESSION_EXPIRED;
else if (rc == -ENOMEM)
rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
else if (rc)
rsp->hdr.Status = STATUS_LOGON_FAILURE;
if (conn->use_spnego && conn->mechToken) {
kfree(conn->mechToken);
conn->mechToken = NULL;
}
if (rc < 0 && sess) {
ksmbd_session_destroy(sess);
work->sess = NULL;
}
return rc;
}
/**
* smb2_tree_connect() - handler for smb2 tree connect command
* @work: smb work containing smb request buffer
*
* Return: 0 on success, otherwise error
*/
int smb2_tree_connect(struct ksmbd_work *work)
{
struct ksmbd_conn *conn = work->conn;
struct smb2_tree_connect_req *req = work->request_buf;
struct smb2_tree_connect_rsp *rsp = work->response_buf;
struct ksmbd_session *sess = work->sess;
char *treename = NULL, *name = NULL;
struct ksmbd_tree_conn_status status;
struct ksmbd_share_config *share;
int rc = -EINVAL;
treename = smb_strndup_from_utf16(req->Buffer,
le16_to_cpu(req->PathLength), true,
conn->local_nls);
if (IS_ERR(treename)) {
pr_err("treename is NULL\n");
status.ret = KSMBD_TREE_CONN_STATUS_ERROR;
goto out_err1;
}
name = ksmbd_extract_sharename(treename);
if (IS_ERR(name)) {
status.ret = KSMBD_TREE_CONN_STATUS_ERROR;
goto out_err1;
}
ksmbd_debug(SMB, "tree connect request for tree %s treename %s\n",
name, treename);
status = ksmbd_tree_conn_connect(sess, name);
if (status.ret == KSMBD_TREE_CONN_STATUS_OK)
rsp->hdr.Id.SyncId.TreeId = cpu_to_le32(status.tree_conn->id);
else
goto out_err1;
share = status.tree_conn->share_conf;
if (test_share_config_flag(share, KSMBD_SHARE_FLAG_PIPE)) {
ksmbd_debug(SMB, "IPC share path request\n");
rsp->ShareType = SMB2_SHARE_TYPE_PIPE;
rsp->MaximalAccess = FILE_READ_DATA_LE | FILE_READ_EA_LE |
FILE_EXECUTE_LE | FILE_READ_ATTRIBUTES_LE |
FILE_DELETE_LE | FILE_READ_CONTROL_LE |
FILE_WRITE_DAC_LE | FILE_WRITE_OWNER_LE |
FILE_SYNCHRONIZE_LE;
} else {
rsp->ShareType = SMB2_SHARE_TYPE_DISK;
rsp->MaximalAccess = FILE_READ_DATA_LE | FILE_READ_EA_LE |
FILE_EXECUTE_LE | FILE_READ_ATTRIBUTES_LE;
if (test_tree_conn_flag(status.tree_conn,
KSMBD_TREE_CONN_FLAG_WRITABLE)) {
rsp->MaximalAccess |= FILE_WRITE_DATA_LE |
FILE_APPEND_DATA_LE | FILE_WRITE_EA_LE |
FILE_DELETE_LE | FILE_WRITE_ATTRIBUTES_LE |
FILE_DELETE_CHILD_LE | FILE_READ_CONTROL_LE |
FILE_WRITE_DAC_LE | FILE_WRITE_OWNER_LE |
FILE_SYNCHRONIZE_LE;
}
}
status.tree_conn->maximal_access = le32_to_cpu(rsp->MaximalAccess);
if (conn->posix_ext_supported)
status.tree_conn->posix_extensions = true;
out_err1:
rsp->StructureSize = cpu_to_le16(16);
rsp->Capabilities = 0;
rsp->Reserved = 0;
/* default manual caching */
rsp->ShareFlags = SMB2_SHAREFLAG_MANUAL_CACHING;
inc_rfc1001_len(rsp, 16);
if (!IS_ERR(treename))
kfree(treename);
if (!IS_ERR(name))
kfree(name);
switch (status.ret) {
case KSMBD_TREE_CONN_STATUS_OK:
rsp->hdr.Status = STATUS_SUCCESS;
rc = 0;
break;
case KSMBD_TREE_CONN_STATUS_NO_SHARE:
rsp->hdr.Status = STATUS_BAD_NETWORK_PATH;
break;
case -ENOMEM:
case KSMBD_TREE_CONN_STATUS_NOMEM:
rsp->hdr.Status = STATUS_NO_MEMORY;
break;
case KSMBD_TREE_CONN_STATUS_ERROR:
case KSMBD_TREE_CONN_STATUS_TOO_MANY_CONNS:
case KSMBD_TREE_CONN_STATUS_TOO_MANY_SESSIONS:
rsp->hdr.Status = STATUS_ACCESS_DENIED;
break;
case -EINVAL:
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
break;
default:
rsp->hdr.Status = STATUS_ACCESS_DENIED;
}
return rc;
}
/**
* smb2_create_open_flags() - convert smb open flags to unix open flags
* @file_present: is file already present
* @access: file access flags
* @disposition: file disposition flags
* @may_flags: set with MAY_ flags
*
* Return: file open flags
*/
static int smb2_create_open_flags(bool file_present, __le32 access,
__le32 disposition,
int *may_flags)
{
int oflags = O_NONBLOCK | O_LARGEFILE;
if (access & FILE_READ_DESIRED_ACCESS_LE &&
access & FILE_WRITE_DESIRE_ACCESS_LE) {
oflags |= O_RDWR;
*may_flags = MAY_OPEN | MAY_READ | MAY_WRITE;
} else if (access & FILE_WRITE_DESIRE_ACCESS_LE) {
oflags |= O_WRONLY;
*may_flags = MAY_OPEN | MAY_WRITE;
} else {
oflags |= O_RDONLY;
*may_flags = MAY_OPEN | MAY_READ;
}
if (access == FILE_READ_ATTRIBUTES_LE)
oflags |= O_PATH;
if (file_present) {
switch (disposition & FILE_CREATE_MASK_LE) {
case FILE_OPEN_LE:
case FILE_CREATE_LE:
break;
case FILE_SUPERSEDE_LE:
case FILE_OVERWRITE_LE:
case FILE_OVERWRITE_IF_LE:
oflags |= O_TRUNC;
break;
default:
break;
}
} else {
switch (disposition & FILE_CREATE_MASK_LE) {
case FILE_SUPERSEDE_LE:
case FILE_CREATE_LE:
case FILE_OPEN_IF_LE:
case FILE_OVERWRITE_IF_LE:
oflags |= O_CREAT;
break;
case FILE_OPEN_LE:
case FILE_OVERWRITE_LE:
oflags &= ~O_CREAT;
break;
default:
break;
}
}
return oflags;
}
/**
* smb2_tree_disconnect() - handler for smb tree connect request
* @work: smb work containing request buffer
*
* Return: 0
*/
int smb2_tree_disconnect(struct ksmbd_work *work)
{
struct smb2_tree_disconnect_rsp *rsp = work->response_buf;
struct ksmbd_session *sess = work->sess;
struct ksmbd_tree_connect *tcon = work->tcon;
rsp->StructureSize = cpu_to_le16(4);
inc_rfc1001_len(rsp, 4);
ksmbd_debug(SMB, "request\n");
if (!tcon) {
struct smb2_tree_disconnect_req *req = work->request_buf;
ksmbd_debug(SMB, "Invalid tid %d\n", req->hdr.Id.SyncId.TreeId);
rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED;
smb2_set_err_rsp(work);
return 0;
}
ksmbd_close_tree_conn_fds(work);
ksmbd_tree_conn_disconnect(sess, tcon);
return 0;
}
/**
* smb2_session_logoff() - handler for session log off request
* @work: smb work containing request buffer
*
* Return: 0
*/
int smb2_session_logoff(struct ksmbd_work *work)
{
struct ksmbd_conn *conn = work->conn;
struct smb2_logoff_rsp *rsp = work->response_buf;
struct ksmbd_session *sess = work->sess;
rsp->StructureSize = cpu_to_le16(4);
inc_rfc1001_len(rsp, 4);
ksmbd_debug(SMB, "request\n");
/* Got a valid session, set connection state */
WARN_ON(sess->conn != conn);
/* setting CifsExiting here may race with start_tcp_sess */
ksmbd_conn_set_need_reconnect(work);
ksmbd_close_session_fds(work);
ksmbd_conn_wait_idle(conn);
if (ksmbd_tree_conn_session_logoff(sess)) {
struct smb2_logoff_req *req = work->request_buf;
ksmbd_debug(SMB, "Invalid tid %d\n", req->hdr.Id.SyncId.TreeId);
rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED;
smb2_set_err_rsp(work);
return 0;
}
ksmbd_destroy_file_table(&sess->file_table);
sess->state = SMB2_SESSION_EXPIRED;
ksmbd_free_user(sess->user);
sess->user = NULL;
/* let start_tcp_sess free connection info now */
ksmbd_conn_set_need_negotiate(work);
return 0;
}
/**
* create_smb2_pipe() - create IPC pipe
* @work: smb work containing request buffer
*
* Return: 0 on success, otherwise error
*/
static noinline int create_smb2_pipe(struct ksmbd_work *work)
{
struct smb2_create_rsp *rsp = work->response_buf;
struct smb2_create_req *req = work->request_buf;
int id;
int err;
char *name;
name = smb_strndup_from_utf16(req->Buffer, le16_to_cpu(req->NameLength),
1, work->conn->local_nls);
if (IS_ERR(name)) {
rsp->hdr.Status = STATUS_NO_MEMORY;
err = PTR_ERR(name);
goto out;
}
id = ksmbd_session_rpc_open(work->sess, name);
if (id < 0) {
pr_err("Unable to open RPC pipe: %d\n", id);
err = id;
goto out;
}
rsp->hdr.Status = STATUS_SUCCESS;
rsp->StructureSize = cpu_to_le16(89);
rsp->OplockLevel = SMB2_OPLOCK_LEVEL_NONE;
rsp->Reserved = 0;
rsp->CreateAction = cpu_to_le32(FILE_OPENED);
rsp->CreationTime = cpu_to_le64(0);
rsp->LastAccessTime = cpu_to_le64(0);
rsp->ChangeTime = cpu_to_le64(0);
rsp->AllocationSize = cpu_to_le64(0);
rsp->EndofFile = cpu_to_le64(0);
rsp->FileAttributes = ATTR_NORMAL_LE;
rsp->Reserved2 = 0;
rsp->VolatileFileId = cpu_to_le64(id);
rsp->PersistentFileId = 0;
rsp->CreateContextsOffset = 0;
rsp->CreateContextsLength = 0;
inc_rfc1001_len(rsp, 88); /* StructureSize - 1*/
kfree(name);
return 0;
out:
switch (err) {
case -EINVAL:
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
break;
case -ENOSPC:
case -ENOMEM:
rsp->hdr.Status = STATUS_NO_MEMORY;
break;
}
if (!IS_ERR(name))
kfree(name);
smb2_set_err_rsp(work);
return err;
}
/**
* smb2_set_ea() - handler for setting extended attributes using set
* info command
* @eabuf: set info command buffer
* @buf_len: set info command buffer length
* @path: dentry path for get ea
*
* Return: 0 on success, otherwise error
*/
static int smb2_set_ea(struct smb2_ea_info *eabuf, unsigned int buf_len,
struct path *path)
{
struct user_namespace *user_ns = mnt_user_ns(path->mnt);
char *attr_name = NULL, *value;
int rc = 0;
unsigned int next = 0;
if (buf_len < sizeof(struct smb2_ea_info) + eabuf->EaNameLength +
le16_to_cpu(eabuf->EaValueLength))
return -EINVAL;
attr_name = kmalloc(XATTR_NAME_MAX + 1, GFP_KERNEL);
if (!attr_name)
return -ENOMEM;
do {
if (!eabuf->EaNameLength)
goto next;
ksmbd_debug(SMB,
"name : <%s>, name_len : %u, value_len : %u, next : %u\n",
eabuf->name, eabuf->EaNameLength,
le16_to_cpu(eabuf->EaValueLength),
le32_to_cpu(eabuf->NextEntryOffset));
if (eabuf->EaNameLength >
(XATTR_NAME_MAX - XATTR_USER_PREFIX_LEN)) {
rc = -EINVAL;
break;
}
memcpy(attr_name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN);
memcpy(&attr_name[XATTR_USER_PREFIX_LEN], eabuf->name,
eabuf->EaNameLength);
attr_name[XATTR_USER_PREFIX_LEN + eabuf->EaNameLength] = '\0';
value = (char *)&eabuf->name + eabuf->EaNameLength + 1;
if (!eabuf->EaValueLength) {
rc = ksmbd_vfs_casexattr_len(user_ns,
path->dentry,
attr_name,
XATTR_USER_PREFIX_LEN +
eabuf->EaNameLength);
/* delete the EA only when it exits */
if (rc > 0) {
rc = ksmbd_vfs_remove_xattr(user_ns,
path->dentry,
attr_name);
if (rc < 0) {
ksmbd_debug(SMB,
"remove xattr failed(%d)\n",
rc);
break;
}
}
/* if the EA doesn't exist, just do nothing. */
rc = 0;
} else {
rc = ksmbd_vfs_setxattr(user_ns,
path->dentry, attr_name, value,
le16_to_cpu(eabuf->EaValueLength), 0);
if (rc < 0) {
ksmbd_debug(SMB,
"ksmbd_vfs_setxattr is failed(%d)\n",
rc);
break;
}
}
next:
next = le32_to_cpu(eabuf->NextEntryOffset);
if (next == 0 || buf_len < next)
break;
buf_len -= next;
eabuf = (struct smb2_ea_info *)((char *)eabuf + next);
if (next < (u32)eabuf->EaNameLength + le16_to_cpu(eabuf->EaValueLength))
break;
} while (next != 0);
kfree(attr_name);
return rc;
}
static noinline int smb2_set_stream_name_xattr(struct path *path,
struct ksmbd_file *fp,
char *stream_name, int s_type)
{
struct user_namespace *user_ns = mnt_user_ns(path->mnt);
size_t xattr_stream_size;
char *xattr_stream_name;
int rc;
rc = ksmbd_vfs_xattr_stream_name(stream_name,
&xattr_stream_name,
&xattr_stream_size,
s_type);
if (rc)
return rc;
fp->stream.name = xattr_stream_name;
fp->stream.size = xattr_stream_size;
/* Check if there is stream prefix in xattr space */
rc = ksmbd_vfs_casexattr_len(user_ns,
path->dentry,
xattr_stream_name,
xattr_stream_size);
if (rc >= 0)
return 0;
if (fp->cdoption == FILE_OPEN_LE) {
ksmbd_debug(SMB, "XATTR stream name lookup failed: %d\n", rc);
return -EBADF;
}
rc = ksmbd_vfs_setxattr(user_ns, path->dentry,
xattr_stream_name, NULL, 0, 0);
if (rc < 0)
pr_err("Failed to store XATTR stream name :%d\n", rc);
return 0;
}
static int smb2_remove_smb_xattrs(struct path *path)
{
struct user_namespace *user_ns = mnt_user_ns(path->mnt);
char *name, *xattr_list = NULL;
ssize_t xattr_list_len;
int err = 0;
xattr_list_len = ksmbd_vfs_listxattr(path->dentry, &xattr_list);
if (xattr_list_len < 0) {
goto out;
} else if (!xattr_list_len) {
ksmbd_debug(SMB, "empty xattr in the file\n");
goto out;
}
for (name = xattr_list; name - xattr_list < xattr_list_len;
name += strlen(name) + 1) {
ksmbd_debug(SMB, "%s, len %zd\n", name, strlen(name));
if (strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN) &&
strncmp(&name[XATTR_USER_PREFIX_LEN], DOS_ATTRIBUTE_PREFIX,
DOS_ATTRIBUTE_PREFIX_LEN) &&
strncmp(&name[XATTR_USER_PREFIX_LEN], STREAM_PREFIX, STREAM_PREFIX_LEN))
continue;
err = ksmbd_vfs_remove_xattr(user_ns, path->dentry, name);
if (err)
ksmbd_debug(SMB, "remove xattr failed : %s\n", name);
}
out:
kvfree(xattr_list);
return err;
}
static int smb2_create_truncate(struct path *path)
{
int rc = vfs_truncate(path, 0);
if (rc) {
pr_err("vfs_truncate failed, rc %d\n", rc);
return rc;
}
rc = smb2_remove_smb_xattrs(path);
if (rc == -EOPNOTSUPP)
rc = 0;
if (rc)
ksmbd_debug(SMB,
"ksmbd_truncate_stream_name_xattr failed, rc %d\n",
rc);
return rc;
}
static void smb2_new_xattrs(struct ksmbd_tree_connect *tcon, struct path *path,
struct ksmbd_file *fp)
{
struct xattr_dos_attrib da = {0};
int rc;
if (!test_share_config_flag(tcon->share_conf,
KSMBD_SHARE_FLAG_STORE_DOS_ATTRS))
return;
da.version = 4;
da.attr = le32_to_cpu(fp->f_ci->m_fattr);
da.itime = da.create_time = fp->create_time;
da.flags = XATTR_DOSINFO_ATTRIB | XATTR_DOSINFO_CREATE_TIME |
XATTR_DOSINFO_ITIME;
rc = ksmbd_vfs_set_dos_attrib_xattr(mnt_user_ns(path->mnt),
path->dentry, &da);
if (rc)
ksmbd_debug(SMB, "failed to store file attribute into xattr\n");
}
static void smb2_update_xattrs(struct ksmbd_tree_connect *tcon,
struct path *path, struct ksmbd_file *fp)
{
struct xattr_dos_attrib da;
int rc;
fp->f_ci->m_fattr &= ~(ATTR_HIDDEN_LE | ATTR_SYSTEM_LE);
/* get FileAttributes from XATTR_NAME_DOS_ATTRIBUTE */
if (!test_share_config_flag(tcon->share_conf,
KSMBD_SHARE_FLAG_STORE_DOS_ATTRS))
return;
rc = ksmbd_vfs_get_dos_attrib_xattr(mnt_user_ns(path->mnt),
path->dentry, &da);
if (rc > 0) {
fp->f_ci->m_fattr = cpu_to_le32(da.attr);
fp->create_time = da.create_time;
fp->itime = da.itime;
}
}
static int smb2_creat(struct ksmbd_work *work, struct path *path, char *name,
int open_flags, umode_t posix_mode, bool is_dir)
{
struct ksmbd_tree_connect *tcon = work->tcon;
struct ksmbd_share_config *share = tcon->share_conf;
umode_t mode;
int rc;
if (!(open_flags & O_CREAT))
return -EBADF;
ksmbd_debug(SMB, "file does not exist, so creating\n");
if (is_dir == true) {
ksmbd_debug(SMB, "creating directory\n");
mode = share_config_directory_mode(share, posix_mode);
rc = ksmbd_vfs_mkdir(work, name, mode);
if (rc)
return rc;
} else {
ksmbd_debug(SMB, "creating regular file\n");
mode = share_config_create_mode(share, posix_mode);
rc = ksmbd_vfs_create(work, name, mode);
if (rc)
return rc;
}
rc = ksmbd_vfs_kern_path(work, name, 0, path, 0);
if (rc) {
pr_err("cannot get linux path (%s), err = %d\n",
name, rc);
return rc;
}
return 0;
}
static int smb2_create_sd_buffer(struct ksmbd_work *work,
struct smb2_create_req *req,
struct path *path)
{
struct create_context *context;
struct create_sd_buf_req *sd_buf;
if (!req->CreateContextsOffset)
return -ENOENT;
/* Parse SD BUFFER create contexts */
context = smb2_find_context_vals(req, SMB2_CREATE_SD_BUFFER);
if (!context)
return -ENOENT;
else if (IS_ERR(context))
return PTR_ERR(context);
ksmbd_debug(SMB,
"Set ACLs using SMB2_CREATE_SD_BUFFER context\n");
sd_buf = (struct create_sd_buf_req *)context;
if (le16_to_cpu(context->DataOffset) +
le32_to_cpu(context->DataLength) <
sizeof(struct create_sd_buf_req))
return -EINVAL;
return set_info_sec(work->conn, work->tcon, path, &sd_buf->ntsd,
le32_to_cpu(sd_buf->ccontext.DataLength), true);
}
static void ksmbd_acls_fattr(struct smb_fattr *fattr,
struct user_namespace *mnt_userns,
struct inode *inode)
{
fattr->cf_uid = i_uid_into_mnt(mnt_userns, inode);
fattr->cf_gid = i_gid_into_mnt(mnt_userns, inode);
fattr->cf_mode = inode->i_mode;
fattr->cf_acls = NULL;
fattr->cf_dacls = NULL;
if (IS_ENABLED(CONFIG_FS_POSIX_ACL)) {
fattr->cf_acls = get_acl(inode, ACL_TYPE_ACCESS);
if (S_ISDIR(inode->i_mode))
fattr->cf_dacls = get_acl(inode, ACL_TYPE_DEFAULT);
}
}
/**
* smb2_open() - handler for smb file open request
* @work: smb work containing request buffer
*
* Return: 0 on success, otherwise error
*/
int smb2_open(struct ksmbd_work *work)
{
struct ksmbd_conn *conn = work->conn;
struct ksmbd_session *sess = work->sess;
struct ksmbd_tree_connect *tcon = work->tcon;
struct smb2_create_req *req;
struct smb2_create_rsp *rsp, *rsp_org;
struct path path;
struct ksmbd_share_config *share = tcon->share_conf;
struct ksmbd_file *fp = NULL;
struct file *filp = NULL;
struct user_namespace *user_ns = NULL;
struct kstat stat;
struct create_context *context;
struct lease_ctx_info *lc = NULL;
struct create_ea_buf_req *ea_buf = NULL;
struct oplock_info *opinfo;
__le32 *next_ptr = NULL;
int req_op_level = 0, open_flags = 0, may_flags = 0, file_info = 0;
int rc = 0;
int contxt_cnt = 0, query_disk_id = 0;
int maximal_access_ctxt = 0, posix_ctxt = 0;
int s_type = 0;
int next_off = 0;
char *name = NULL;
char *stream_name = NULL;
bool file_present = false, created = false, already_permitted = false;
int share_ret, need_truncate = 0;
u64 time;
umode_t posix_mode = 0;
__le32 daccess, maximal_access = 0;
rsp_org = work->response_buf;
WORK_BUFFERS(work, req, rsp);
if (req->hdr.NextCommand && !work->next_smb2_rcv_hdr_off &&
(req->hdr.Flags & SMB2_FLAGS_RELATED_OPERATIONS)) {
ksmbd_debug(SMB, "invalid flag in chained command\n");
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
smb2_set_err_rsp(work);
return -EINVAL;
}
if (test_share_config_flag(share, KSMBD_SHARE_FLAG_PIPE)) {
ksmbd_debug(SMB, "IPC pipe create request\n");
return create_smb2_pipe(work);
}
if (req->NameLength) {
if ((req->CreateOptions & FILE_DIRECTORY_FILE_LE) &&
*(char *)req->Buffer == '\\') {
pr_err("not allow directory name included leading slash\n");
rc = -EINVAL;
goto err_out1;
}
name = smb2_get_name(share,
req->Buffer,
le16_to_cpu(req->NameLength),
work->conn->local_nls);
if (IS_ERR(name)) {
rc = PTR_ERR(name);
if (rc != -ENOMEM)
rc = -ENOENT;
name = NULL;
goto err_out1;
}
ksmbd_debug(SMB, "converted name = %s\n", name);
if (strchr(name, ':')) {
if (!test_share_config_flag(work->tcon->share_conf,
KSMBD_SHARE_FLAG_STREAMS)) {
rc = -EBADF;
goto err_out1;
}
rc = parse_stream_name(name, &stream_name, &s_type);
if (rc < 0)
goto err_out1;
}
rc = ksmbd_validate_filename(name);
if (rc < 0)
goto err_out1;
if (ksmbd_share_veto_filename(share, name)) {
rc = -ENOENT;
ksmbd_debug(SMB, "Reject open(), vetoed file: %s\n",
name);
goto err_out1;
}
} else {
name = kstrdup("", GFP_KERNEL);
if (!name) {
rc = -ENOMEM;
goto err_out1;
}
}
req_op_level = req->RequestedOplockLevel;
if (req_op_level == SMB2_OPLOCK_LEVEL_LEASE)
lc = parse_lease_state(req);
if (le32_to_cpu(req->ImpersonationLevel) > le32_to_cpu(IL_DELEGATE_LE)) {
pr_err("Invalid impersonationlevel : 0x%x\n",
le32_to_cpu(req->ImpersonationLevel));
rc = -EIO;
rsp->hdr.Status = STATUS_BAD_IMPERSONATION_LEVEL;
goto err_out1;
}
if (req->CreateOptions && !(req->CreateOptions & CREATE_OPTIONS_MASK)) {
pr_err("Invalid create options : 0x%x\n",
le32_to_cpu(req->CreateOptions));
rc = -EINVAL;
goto err_out1;
} else {
if (req->CreateOptions & FILE_SEQUENTIAL_ONLY_LE &&
req->CreateOptions & FILE_RANDOM_ACCESS_LE)
req->CreateOptions = ~(FILE_SEQUENTIAL_ONLY_LE);
if (req->CreateOptions &
(FILE_OPEN_BY_FILE_ID_LE | CREATE_TREE_CONNECTION |
FILE_RESERVE_OPFILTER_LE)) {
rc = -EOPNOTSUPP;
goto err_out1;
}
if (req->CreateOptions & FILE_DIRECTORY_FILE_LE) {
if (req->CreateOptions & FILE_NON_DIRECTORY_FILE_LE) {
rc = -EINVAL;
goto err_out1;
} else if (req->CreateOptions & FILE_NO_COMPRESSION_LE) {
req->CreateOptions = ~(FILE_NO_COMPRESSION_LE);
}
}
}
if (le32_to_cpu(req->CreateDisposition) >
le32_to_cpu(FILE_OVERWRITE_IF_LE)) {
pr_err("Invalid create disposition : 0x%x\n",
le32_to_cpu(req->CreateDisposition));
rc = -EINVAL;
goto err_out1;
}
if (!(req->DesiredAccess & DESIRED_ACCESS_MASK)) {
pr_err("Invalid desired access : 0x%x\n",
le32_to_cpu(req->DesiredAccess));
rc = -EACCES;
goto err_out1;
}
if (req->FileAttributes && !(req->FileAttributes & ATTR_MASK_LE)) {
pr_err("Invalid file attribute : 0x%x\n",
le32_to_cpu(req->FileAttributes));
rc = -EINVAL;
goto err_out1;
}
if (req->CreateContextsOffset) {
/* Parse non-durable handle create contexts */
context = smb2_find_context_vals(req, SMB2_CREATE_EA_BUFFER);
if (IS_ERR(context)) {
rc = PTR_ERR(context);
goto err_out1;
} else if (context) {
ea_buf = (struct create_ea_buf_req *)context;
if (le16_to_cpu(context->DataOffset) +
le32_to_cpu(context->DataLength) <
sizeof(struct create_ea_buf_req)) {
rc = -EINVAL;
goto err_out1;
}
if (req->CreateOptions & FILE_NO_EA_KNOWLEDGE_LE) {
rsp->hdr.Status = STATUS_ACCESS_DENIED;
rc = -EACCES;
goto err_out1;
}
}
context = smb2_find_context_vals(req,
SMB2_CREATE_QUERY_MAXIMAL_ACCESS_REQUEST);
if (IS_ERR(context)) {
rc = PTR_ERR(context);
goto err_out1;
} else if (context) {
ksmbd_debug(SMB,
"get query maximal access context\n");
maximal_access_ctxt = 1;
}
context = smb2_find_context_vals(req,
SMB2_CREATE_TIMEWARP_REQUEST);
if (IS_ERR(context)) {
rc = PTR_ERR(context);
goto err_out1;
} else if (context) {
ksmbd_debug(SMB, "get timewarp context\n");
rc = -EBADF;
goto err_out1;
}
if (tcon->posix_extensions) {
context = smb2_find_context_vals(req,
SMB2_CREATE_TAG_POSIX);
if (IS_ERR(context)) {
rc = PTR_ERR(context);
goto err_out1;
} else if (context) {
struct create_posix *posix =
(struct create_posix *)context;
if (le16_to_cpu(context->DataOffset) +
le32_to_cpu(context->DataLength) <
sizeof(struct create_posix)) {
rc = -EINVAL;
goto err_out1;
}
ksmbd_debug(SMB, "get posix context\n");
posix_mode = le32_to_cpu(posix->Mode);
posix_ctxt = 1;
}
}
}
if (ksmbd_override_fsids(work)) {
rc = -ENOMEM;
goto err_out1;
}
rc = ksmbd_vfs_kern_path(work, name, LOOKUP_NO_SYMLINKS, &path, 1);
if (!rc) {
if (req->CreateOptions & FILE_DELETE_ON_CLOSE_LE) {
/*
* If file exists with under flags, return access
* denied error.
*/
if (req->CreateDisposition == FILE_OVERWRITE_IF_LE ||
req->CreateDisposition == FILE_OPEN_IF_LE) {
rc = -EACCES;
path_put(&path);
goto err_out;
}
if (!test_tree_conn_flag(tcon, KSMBD_TREE_CONN_FLAG_WRITABLE)) {
ksmbd_debug(SMB,
"User does not have write permission\n");
rc = -EACCES;
path_put(&path);
goto err_out;
}
} else if (d_is_symlink(path.dentry)) {
rc = -EACCES;
path_put(&path