ksmbd: add SMB3 request replay support

SMB3 clients can replay selected requests after a channel disconnect by
setting SMB2_FLAGS_REPLAY_OPERATION. The command sequence window rejects
duplicate MessageIds on one connection, but it does not validate requests
resent on another channel with a new MessageId.

Add the state and validation required to replay durable CREATE and
file-handle operations:

- track each open ChannelSequence, outstanding request counts, and lock
  sequence entries.
- retain a request-owned open reference until the common response path
  completes ChannelSequence accounting.
- replay DurableHandleReqV2 CREATE requests by CreateGuid, validating the
  durable state, SecurityContext, session, lease key, and persistent flag.
- publish CreateGuid and SecurityContext before an oplock or lease break
  can defer CREATE, rejecting replays of that pending CREATE with
  STATUS_FILE_NOT_AVAILABLE.
- retain the original CREATE action and replay completed CreateGuid
  requests, including requests that did not receive a durable-handle grant,
  without modifying the existing open.
- make replayed oplock and lease break acknowledgements idempotent. And
- preserve SMB2_FLAGS_REPLAY_OPERATION in responses.

Return STATUS_FILE_NOT_AVAILABLE when ChannelSequence validation rejects a
replayed WRITE, IOCTL, or SET_INFO request.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
This commit is contained in:
Namjae Jeon 2026-07-19 00:36:32 +09:00
parent f495154703
commit bb8bf7eb13
10 changed files with 587 additions and 39 deletions

View File

@ -11,6 +11,7 @@
#include "server.h"
#include "connection.h"
#include "ksmbd_work.h"
#include "vfs_cache.h"
#include "mgmt/ksmbd_ida.h"
static struct kmem_cache *work_cache;
@ -88,6 +89,7 @@ void ksmbd_free_work_struct(struct ksmbd_work *work)
ksmbd_release_id(&work->conn->async_ida, work->async_id);
if (work->owns_conn_ref)
ksmbd_conn_put(work->conn);
ksmbd_fd_put(work, work->request_open);
kmem_cache_free(work_cache, work);
}

View File

@ -12,6 +12,7 @@
struct ksmbd_conn;
struct ksmbd_session;
struct ksmbd_tree_connect;
struct ksmbd_file;
#define KSMBD_WORK_INLINE_IOVS 4
@ -93,6 +94,7 @@ struct ksmbd_work {
/* Work owns a reference to @conn. */
bool owns_conn_ref:1;
bool need_invalidate_rkey:1;
bool request_open_chseq_tracked:1;
unsigned int remote_key;
/* cancel works */
@ -100,6 +102,13 @@ struct ksmbd_work {
void **cancel_argv;
void (*cancel_fn)(void **argv);
/*
* Refcounted open associated with the SMB2 command currently being
* processed.
*/
struct ksmbd_file *request_open;
__le16 request_open_chseq;
struct work_struct work;
/* List head at conn->requests */
struct list_head request_entry;

View File

@ -478,14 +478,6 @@ void ksmbd_sessions_deregister(struct ksmbd_conn *conn)
down_write(&conn->session_lock);
xa_for_each(&conn->sessions, id, sess) {
unsigned long chann_id;
struct channel *chann;
xa_for_each(&sess->ksmbd_chann_list, chann_id, chann) {
if (chann->conn != conn)
ksmbd_conn_set_exiting(chann->conn);
}
ksmbd_chann_del(conn, sess);
if (xa_empty(&sess->ksmbd_chann_list)) {
xa_erase(&conn->sessions, sess->id);

View File

@ -1203,6 +1203,18 @@ static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level,
if (brk_opinfo->level == SMB2_OPLOCK_LEVEL_BATCH ||
brk_opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE)
brk_opinfo->op_state = OPLOCK_ACK_WAIT;
/*
* Keep a conflicting CREATE asynchronous while waiting for an
* oplock-break acknowledgement. Besides avoiding a blocked client
* request, this lets a replay arrive while the original CREATE is
* still pending and be rejected with FILE_NOT_AVAILABLE.
*/
if (in_work) {
setup_async_work(in_work, NULL, NULL);
smb2_send_interim_resp(in_work, STATUS_PENDING);
release_async_work(in_work);
}
}
err = smb2_oplock_break_noti(brk_opinfo);
@ -1445,12 +1457,13 @@ void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp)
* @tid: Tree id of connection
* @lctx: lease context information on file open
* @share_ret: share mode
* @replay: whether this is a replayed CREATE request
*
* Return: 0 on success, otherwise error
*/
int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
struct ksmbd_file *fp, __u16 tid,
struct lease_ctx_info *lctx, int share_ret)
struct lease_ctx_info *lctx, int share_ret, bool replay)
{
int err = 0;
int break_level = SMB2_OPLOCK_LEVEL_II;
@ -1535,6 +1548,21 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
prev_op_has_lease = prev_opinfo->is_lease;
if (prev_op_has_lease)
prev_op_state = prev_opinfo->o_lease->state;
/*
* A replay received while this open is waiting for an oplock or lease
* break must not observe an intermediate level and proceed as a new
* open. This check has to precede break_needed. an oplock may already
* have been downgraded from Batch to II while its acknowledgement is
* still pending.
*/
if (replay &&
(test_bit(0, &prev_opinfo->pending_break) ||
prev_opinfo->op_state == OPLOCK_ACK_WAIT)) {
err = -EINPROGRESS;
opinfo_put(prev_opinfo);
goto err_out;
}
if (share_ret < 0 &&
prev_opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE) {
err = share_ret;
@ -1569,7 +1597,14 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
goto set_lev;
}
if (err == -ENOENT) {
if (req_op_level != SMB2_OPLOCK_LEVEL_NONE)
/*
* A pending durable CREATE can lose the previous oplock when
* its holder closes the file. In that case grant the original
* request its full caching state. Other opens still need the
* normal shared-open downgrade below.
*/
if (!prev_durable_open &&
req_op_level != SMB2_OPLOCK_LEVEL_NONE)
req_op_level = SMB2_OPLOCK_LEVEL_II;
goto set_lev;
}

View File

@ -96,7 +96,7 @@ struct oplock_break_info {
int smb_grant_oplock(struct ksmbd_work *work, int req_op_level,
u64 pid, struct ksmbd_file *fp, __u16 tid,
struct lease_ctx_info *lctx, int share_ret);
struct lease_ctx_info *lctx, int share_ret, bool replay);
void smb_break_all_levII_oplock(struct ksmbd_work *work,
struct ksmbd_file *fp, int is_trunc);
void smb_break_all_levII_oplock_rename(struct ksmbd_work *work, struct ksmbd_file *fp);

View File

@ -15,6 +15,7 @@
#include "server.h"
#include "smb_common.h"
#include "smb2pdu.h"
#include "../common/smb2status.h"
#include "connection.h"
#include "transport_ipc.h"
@ -229,8 +230,10 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
}
rc = __process_request(work, conn, &command);
if (rc == SERVER_HANDLER_ABORT)
if (rc == SERVER_HANDLER_ABORT) {
smb2_complete_request_open(work);
break;
}
/*
* Call smb2_set_rsp_credits() function to set number of credits
@ -243,10 +246,13 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
if (rc < 0) {
conn->ops->set_rsp_status(work,
STATUS_INVALID_PARAMETER);
smb2_complete_request_open(work);
goto send;
}
}
smb2_complete_request_open(work);
is_chained = is_chained_smb2_message(work);
if (work->sess &&
@ -262,6 +268,7 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
} while (is_chained == true);
send:
smb2_complete_request_open(work);
/*
* Release any credit charge still outstanding for this request. On
* the normal path smb2_set_rsp_credits() already returned it, but the

View File

@ -554,6 +554,8 @@ static void init_chained_smb2_rsp(struct ksmbd_work *work)
*/
rsp_hdr->Flags = (SMB2_FLAGS_SERVER_TO_REDIR |
SMB2_FLAGS_RELATED_OPERATIONS);
if (rcv_hdr->Flags & SMB2_FLAGS_REPLAY_OPERATION)
rsp_hdr->Flags |= SMB2_FLAGS_REPLAY_OPERATION;
rsp_hdr->NextCommand = 0;
rsp_hdr->MessageId = rcv_hdr->MessageId;
rsp_hdr->Id.SyncId.ProcessId = rcv_hdr->Id.SyncId.ProcessId;
@ -646,6 +648,8 @@ int init_smb2_rsp_hdr(struct ksmbd_work *work)
* Message is response. We don't grant oplock yet.
*/
rsp_hdr->Flags = (SMB2_FLAGS_SERVER_TO_REDIR);
if (rcv_hdr->Flags & SMB2_FLAGS_REPLAY_OPERATION)
rsp_hdr->Flags |= SMB2_FLAGS_REPLAY_OPERATION;
rsp_hdr->NextCommand = 0;
rsp_hdr->MessageId = rcv_hdr->MessageId;
rsp_hdr->Id.SyncId.ProcessId = rcv_hdr->Id.SyncId.ProcessId;
@ -656,6 +660,185 @@ int init_smb2_rsp_hdr(struct ksmbd_work *work)
return 0;
}
static __le16 smb3_hdr_channel_sequence(struct smb2_hdr *hdr)
{
return ((struct smb3_hdr_req *)hdr)->ChannelSequence;
}
static bool smb3_hdr_replay(struct smb2_hdr *hdr)
{
return hdr->Flags & SMB2_FLAGS_REPLAY_OPERATION;
}
static int smb3_verify_channel_sequence(struct ksmbd_work *work,
struct ksmbd_file *fp,
struct smb2_hdr *hdr,
bool allow_stale)
{
__le16 chseq_le;
u16 chseq, old_chseq;
int ret = 0;
if (work->conn->dialect < SMB30_PROT_ID)
return 0;
chseq_le = smb3_hdr_channel_sequence(hdr);
chseq = le16_to_cpu(chseq_le);
spin_lock(&fp->f_lock);
old_chseq = le16_to_cpu(fp->channel_sequence);
if (smb3_hdr_replay(hdr)) {
if (chseq == old_chseq && fp->outstanding_pre_requests == 0) {
fp->outstanding_requests++;
} else if ((u16)(chseq - old_chseq) <= 0x7fff &&
fp->outstanding_pre_requests == 0) {
fp->outstanding_pre_requests += fp->outstanding_requests;
fp->outstanding_requests = 1;
fp->channel_sequence = chseq_le;
} else if (allow_stale) {
fp->outstanding_pre_requests++;
} else {
ret = -EAGAIN;
}
} else {
if (chseq == old_chseq) {
fp->outstanding_requests++;
} else if ((u16)(chseq - old_chseq) <= 0x7fff) {
fp->outstanding_pre_requests += fp->outstanding_requests;
fp->outstanding_requests = 1;
fp->channel_sequence = chseq_le;
} else if (allow_stale) {
fp->outstanding_pre_requests++;
} else {
ret = -EAGAIN;
}
}
spin_unlock(&fp->f_lock);
return ret;
}
static void smb3_complete_channel_sequence(struct ksmbd_work *work,
struct ksmbd_file *fp,
__le16 chseq_le)
{
u16 chseq;
if (work->conn->dialect < SMB30_PROT_ID)
return;
chseq = le16_to_cpu(chseq_le);
spin_lock(&fp->f_lock);
if (chseq == le16_to_cpu(fp->channel_sequence)) {
if (fp->outstanding_requests)
fp->outstanding_requests--;
} else {
if (fp->outstanding_pre_requests)
fp->outstanding_pre_requests--;
}
spin_unlock(&fp->f_lock);
}
static int smb2_set_request_open(struct ksmbd_work *work, struct ksmbd_file *fp,
struct smb2_hdr *hdr, bool verify_chseq,
bool allow_stale_chseq)
{
struct ksmbd_file *open;
int ret;
smb2_complete_request_open(work);
open = ksmbd_file_get(fp);
if (!open)
return -ESTALE;
if (verify_chseq) {
ret = smb3_verify_channel_sequence(work, fp, hdr,
allow_stale_chseq);
if (ret) {
ksmbd_fd_put(work, open);
return ret;
}
work->request_open_chseq_tracked = true;
}
work->request_open = open;
work->request_open_chseq = smb3_hdr_channel_sequence(hdr);
return 0;
}
void smb2_complete_request_open(struct ksmbd_work *work)
{
struct ksmbd_file *open = work->request_open;
if (!open)
return;
if (work->request_open_chseq_tracked)
smb3_complete_channel_sequence(work, open,
work->request_open_chseq);
work->request_open = NULL;
work->request_open_chseq_tracked = false;
ksmbd_fd_put(work, open);
}
static bool smb2_lock_sequence_applicable(struct ksmbd_work *work,
struct ksmbd_file *fp)
{
return fp->is_resilient || fp->is_durable || fp->is_persistent ||
(work->conn->dialect >= SMB30_PROT_ID &&
(work->conn->cli_cap & SMB2_GLOBAL_CAP_MULTI_CHANNEL));
}
static void smb2_verify_lock_sequence(struct ksmbd_work *work,
struct ksmbd_file *fp,
struct smb2_lock_req *req)
{
u32 val, index;
u8 sequence;
if (work->conn->dialect == SMB20_PROT_ID ||
!smb2_lock_sequence_applicable(work, fp))
return;
val = le32_to_cpu(req->LockSequenceNumber);
sequence = val & 0xf;
index = val >> 4;
if (!index || index > KSMBD_LOCK_SEQ_ARRAY_SIZE)
return;
spin_lock(&fp->f_lock);
if (fp->lock_seq[index - 1].valid &&
fp->lock_seq[index - 1].sequence != sequence)
fp->lock_seq[index - 1].valid = false;
spin_unlock(&fp->f_lock);
}
static void smb2_update_lock_sequence(struct ksmbd_work *work,
struct ksmbd_file *fp,
struct smb2_lock_req *req)
{
u32 val, index;
u8 sequence;
if (work->conn->dialect == SMB20_PROT_ID ||
!smb2_lock_sequence_applicable(work, fp))
return;
val = le32_to_cpu(req->LockSequenceNumber);
sequence = val & 0xf;
index = val >> 4;
if (!index || index > KSMBD_LOCK_SEQ_ARRAY_SIZE)
return;
spin_lock(&fp->f_lock);
fp->lock_seq[index - 1].valid = true;
fp->lock_seq[index - 1].sequence = sequence;
spin_unlock(&fp->f_lock);
}
/**
* smb2_allocate_rsp_buf() - allocate smb2 response buffer
* @work: smb work containing smb request buffer
@ -3142,12 +3325,86 @@ struct durable_info {
unsigned short int type;
bool persistent;
bool reconnected;
bool replay;
bool replay_consumed;
bool app_instance_id;
unsigned int timeout;
char *CreateGuid;
char AppInstanceId[SMB2_CREATE_GUID_SIZE];
};
static int smb2_check_durable_replay(struct ksmbd_work *work,
struct ksmbd_file *fp,
struct lease_ctx_info *lc,
bool persistent)
{
struct oplock_info *opinfo;
int ret = 0;
if (!fp->is_durable && !fp->is_persistent)
return -EACCES;
if (ksmbd_vfs_compare_durable_owner(fp, work->sess->user) == false)
return -EACCES;
if (fp->is_persistent && !persistent)
return -EINVAL;
opinfo = opinfo_get(fp);
if (!opinfo)
return 0;
if (opinfo->sess && opinfo->sess->id != work->sess->id) {
ret = -ENOEXEC;
goto out;
}
if (opinfo->is_lease) {
if (!lc ||
memcmp(opinfo->o_lease->lease_key, lc->lease_key,
SMB2_LEASE_KEY_SIZE)) {
ret = -EACCES;
goto out;
}
} else {
if (lc) {
ret = -EACCES;
goto out;
}
if (fp->is_durable && opinfo->level != SMB2_OPLOCK_LEVEL_BATCH)
ret = -EACCES;
}
out:
opinfo_put(opinfo);
return ret;
}
static bool smb2_durable_replay_consumed(struct ksmbd_file *fp)
{
bool consumed;
spin_lock(&fp->f_lock);
consumed = fp->durable_replay_consumed;
spin_unlock(&fp->f_lock);
return consumed;
}
static void smb2_mark_durable_replay_consumed(struct ksmbd_file *fp)
{
spin_lock(&fp->f_lock);
fp->durable_replay_consumed = true;
spin_unlock(&fp->f_lock);
}
static bool smb2_durable_replay_differs(struct ksmbd_file *fp,
struct smb2_create_req *req)
{
return fp->cdoption != req->CreateDisposition ||
fp->create_file_attributes != req->FileAttributes;
}
static int parse_durable_handle_context(struct ksmbd_work *work,
struct smb2_create_req *req,
struct lease_ctx_info *lc,
@ -3275,6 +3532,10 @@ static int parse_durable_handle_context(struct ksmbd_work *work,
durable_v2_blob =
(struct create_durable_req_v2 *)context;
ksmbd_debug(SMB, "Request for durable v2 open\n");
dh_info->CreateGuid = durable_v2_blob->dcontext.CreateGuid;
dh_info->persistent =
le32_to_cpu(durable_v2_blob->dcontext.Flags) &
SMB2_DHANDLE_FLAG_PERSISTENT;
dh_info->fp = ksmbd_lookup_fd_cguid(durable_v2_blob->dcontext.CreateGuid);
if (dh_info->fp) {
if (!memcmp(conn->ClientGUID, dh_info->fp->client_guid,
@ -3285,12 +3546,73 @@ static int parse_durable_handle_context(struct ksmbd_work *work,
goto out;
}
if (dh_info->fp->conn) {
if (dh_info->fp->f_state == FP_NEW) {
/* Original CREATE is still pending. */
ksmbd_put_durable_fd(dh_info->fp);
err = -EBADF;
err = -EAGAIN;
goto out;
}
dh_info->reconnected = true;
if (!dh_info->fp->is_durable &&
!dh_info->fp->is_persistent) {
/*
* A DurableHandleReqV2 CREATE can complete
* without granting durability (for example, if
* it requested no oplock). Its CreateGuid still
* identifies a completed CREATE for replay.
*/
if (dh_info->fp->conn &&
ksmbd_vfs_compare_durable_owner(
dh_info->fp, work->sess->user)) {
if (smb2_durable_replay_consumed(
dh_info->fp)) {
ksmbd_put_durable_fd(dh_info->fp);
dh_info->fp = NULL;
dh_info->type = dh_idx;
dh_info->replay_consumed = true;
break;
}
if (smb2_durable_replay_differs(
dh_info->fp, req))
smb2_mark_durable_replay_consumed(
dh_info->fp);
dh_info->replay = true;
dh_info->type = dh_idx;
goto out;
}
ksmbd_put_durable_fd(dh_info->fp);
err = -EACCES;
goto out;
}
if (dh_info->fp->conn &&
smb2_durable_replay_consumed(dh_info->fp)) {
ksmbd_put_durable_fd(dh_info->fp);
dh_info->fp = NULL;
dh_info->type = dh_idx;
dh_info->replay_consumed = true;
break;
}
err = smb2_check_durable_replay(work,
dh_info->fp,
lc,
dh_info->persistent);
if (err) {
ksmbd_put_durable_fd(dh_info->fp);
goto out;
}
if (dh_info->fp->conn) {
if (smb2_durable_replay_differs(dh_info->fp,
req))
smb2_mark_durable_replay_consumed(
dh_info->fp);
dh_info->replay = true;
} else {
dh_info->reconnected = true;
}
dh_info->type = dh_idx;
goto out;
}
ksmbd_put_durable_fd(dh_info->fp);
@ -3299,10 +3621,6 @@ static int parse_durable_handle_context(struct ksmbd_work *work,
if ((lc && (lc->req_state & SMB2_LEASE_HANDLE_CACHING_LE)) ||
req_op_level == SMB2_OPLOCK_LEVEL_BATCH) {
dh_info->CreateGuid =
durable_v2_blob->dcontext.CreateGuid;
dh_info->persistent =
le32_to_cpu(durable_v2_blob->dcontext.Flags);
dh_info->timeout =
le32_to_cpu(durable_v2_blob->dcontext.Timeout);
dh_info->type = dh_idx;
@ -3385,6 +3703,7 @@ int smb2_open(struct ksmbd_work *work)
int contxt_cnt = 0, query_disk_id = 0;
bool maximal_access_ctxt = false, posix_ctxt = false;
bool aapl_ctxt = false;
bool durable_rsp = true;
__u64 aapl_req_bitmap = 0, aapl_client_caps = 0;
int s_type = 0;
int next_off = 0;
@ -3504,6 +3823,21 @@ int smb2_open(struct ksmbd_work *work)
if (rc)
goto err_out2;
if (dh_info.replay == true) {
fp = dh_info.fp;
if (ksmbd_override_fsids(work)) {
rc = -ENOMEM;
goto err_out2;
}
file_info = FILE_OPENED;
rc = ksmbd_vfs_getattr(&fp->filp->f_path, &stat);
if (rc)
goto err_out2;
goto reconnected_fp;
}
if (dh_info.reconnected == true) {
rc = smb2_check_durable_oplock(conn, share, dh_info.fp,
lc, sess->user, name);
@ -3928,7 +4262,23 @@ int smb2_open(struct ksmbd_work *work)
goto err_out;
}
/*
* Publish the client and create GUID before an oplock/lease break can
* make this CREATE pending. A replay of that in-flight CREATE must find
* this FP_NEW handle and fail with STATUS_FILE_NOT_AVAILABLE instead of
* waiting on the same break again.
*/
memcpy(fp->client_guid, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE);
if (dh_info.CreateGuid) {
memcpy(fp->create_guid, dh_info.CreateGuid, SMB2_CREATE_GUID_SIZE);
fp->durable_replay_consumed = dh_info.replay_consumed;
rc = ksmbd_vfs_set_durable_owner(fp, sess->user);
if (rc)
goto err_out;
}
fp->cdoption = req->CreateDisposition;
fp->create_file_attributes = req->FileAttributes;
fp->daccess = daccess;
fp->saccess = req->ShareAccess;
fp->coption = req->CreateOptions;
@ -4096,7 +4446,8 @@ int smb2_open(struct ksmbd_work *work)
rc = smb_grant_oplock(work, req_op_level,
fp->persistent_id, fp,
le32_to_cpu(req->hdr.Id.SyncId.TreeId),
lc, share_ret);
lc, share_ret,
smb3_hdr_replay(&req->hdr));
if (rc < 0)
goto err_out1;
}
@ -4240,7 +4591,7 @@ int smb2_open(struct ksmbd_work *work)
if (created)
smb2_new_xattrs(tcon, &path, fp);
memcpy(fp->client_guid, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE);
fp->create_action = cpu_to_le32(file_info);
if (dh_info.type == DURABLE_REQ_V2 || dh_info.type == DURABLE_REQ) {
if (dh_info.type == DURABLE_REQ_V2 && dh_info.persistent &&
@ -4249,10 +4600,7 @@ int smb2_open(struct ksmbd_work *work)
fp->is_persistent = true;
else
fp->is_durable = true;
if (dh_info.type == DURABLE_REQ_V2) {
memcpy(fp->create_guid, dh_info.CreateGuid,
SMB2_CREATE_GUID_SIZE);
if (dh_info.app_instance_id)
memcpy(fp->app_instance_id,
dh_info.AppInstanceId,
@ -4273,10 +4621,22 @@ int smb2_open(struct ksmbd_work *work)
* cares, it sends its own AAPL context on this same CREATE, which
* this function's normal (non-reconnect) parsing already handles.
*/
reconnected_fp:
reconnected_fp:
if (dh_info.replay)
file_info = le32_to_cpu(fp->create_action);
rsp->StructureSize = cpu_to_le16(89);
opinfo = opinfo_get(fp);
rsp->OplockLevel = opinfo != NULL ? opinfo->level : 0;
/*
* A durable CREATE replay does not modify the existing open. When
* replayed without an oplock, however, its response reflects that
* request and cannot include a new durable-handle response context.
*/
if (dh_info.replay && !lc &&
req_op_level == SMB2_OPLOCK_LEVEL_NONE) {
rsp->OplockLevel = SMB2_OPLOCK_LEVEL_NONE;
durable_rsp = false;
}
rsp->Flags = 0;
rsp->CreateAction = cpu_to_le32(file_info);
rsp->CreationTime = cpu_to_le64(fp->create_time);
@ -4377,7 +4737,8 @@ int smb2_open(struct ksmbd_work *work)
next_off = conn->vals->create_disk_id_size;
}
if (dh_info.type == DURABLE_REQ || dh_info.type == DURABLE_REQ_V2) {
if (durable_rsp &&
(dh_info.type == DURABLE_REQ || dh_info.type == DURABLE_REQ_V2)) {
struct create_context *durable_ccontext;
durable_ccontext = (struct create_context *)(rsp->Buffer +
@ -4468,8 +4829,11 @@ int smb2_open(struct ksmbd_work *work)
err_out2:
if (!rc) {
rc = ksmbd_update_fstate(&work->sess->file_table, fp,
FP_INITED);
if (!dh_info.replay)
rc = ksmbd_update_fstate(&work->sess->file_table, fp,
FP_INITED);
if (!rc)
rc = smb2_set_request_open(work, fp, &req->hdr, false, false);
if (!rc)
rc = ksmbd_iov_pin_rsp(work, (void *)rsp, iov_len);
}
@ -4501,15 +4865,22 @@ int smb2_open(struct ksmbd_work *work)
rsp->hdr.Status = STATUS_OBJECT_NAME_COLLISION;
else if (rc == -EMFILE)
rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
else if (rc == -EINPROGRESS)
rsp->hdr.Status = STATUS_FILE_NOT_AVAILABLE;
else if (rc == -EAGAIN)
rsp->hdr.Status = STATUS_FILE_NOT_AVAILABLE;
if (!rsp->hdr.Status)
rsp->hdr.Status = STATUS_UNEXPECTED_IO_ERROR;
if (fp)
if (fp && !dh_info.replay)
ksmbd_fd_put(work, fp);
smb2_set_err_rsp(work);
ksmbd_debug(SMB, "Error response: %x\n", rsp->hdr.Status);
}
if (dh_info.replay)
ksmbd_put_durable_fd(dh_info.fp);
if (dh_info.reconnected) {
/*
* If reconnect succeeded, fp was republished in the
@ -7647,6 +8018,7 @@ int smb2_set_info(struct ksmbd_work *work)
struct smb2_set_info_rsp *rsp;
struct ksmbd_file *fp = NULL;
int rc = 0;
bool chseq_err = false;
unsigned int id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
ksmbd_debug(SMB, "Received smb2 set info request\n");
@ -7686,6 +8058,13 @@ int smb2_set_info(struct ksmbd_work *work)
goto err_out;
}
rc = smb2_set_request_open(work, fp, &req->hdr, true, false);
if (rc) {
rsp->hdr.Status = STATUS_FILE_NOT_AVAILABLE;
chseq_err = true;
goto err_out;
}
saved_cred = override_creds(fp->filp->f_cred);
switch (req->InfoType) {
case SMB2_O_INFO_FILE:
@ -7736,7 +8115,7 @@ int smb2_set_info(struct ksmbd_work *work)
rsp->hdr.Status = STATUS_OBJECT_NAME_INVALID;
else if (rc == -EBUSY || rc == -ENOTEMPTY)
rsp->hdr.Status = STATUS_DIRECTORY_NOT_EMPTY;
else if (rc == -EAGAIN)
else if (rc == -EAGAIN && !chseq_err)
rsp->hdr.Status = STATUS_FILE_LOCK_CONFLICT;
else if (rc == -EBADF || rc == -ESTALE)
rsp->hdr.Status = STATUS_INVALID_HANDLE;
@ -7945,6 +8324,10 @@ int smb2_read(struct ksmbd_work *work)
goto out;
}
err = smb2_set_request_open(work, fp, &req->hdr, true, true);
if (err)
goto out;
if (!(fp->daccess & (FILE_READ_DATA_LE | FILE_READ_ATTRIBUTES_LE))) {
pr_err("Not permitted to read : 0x%x\n", fp->daccess);
err = -EACCES;
@ -8192,6 +8575,7 @@ int smb2_write(struct ksmbd_work *work)
char *data_buf;
bool writethrough = false, is_rdma_channel = false;
bool async_interim = false;
bool chseq_err = false;
int err = 0;
unsigned int max_write_size = work->conn->vals->max_write_size;
unsigned int id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
@ -8281,6 +8665,13 @@ int smb2_write(struct ksmbd_work *work)
goto out;
}
err = smb2_set_request_open(work, fp, &req->hdr, true, false);
if (err) {
rsp->hdr.Status = STATUS_FILE_NOT_AVAILABLE;
chseq_err = true;
goto out;
}
if (!(fp->daccess & (FILE_WRITE_DATA_LE | FILE_READ_ATTRIBUTES_LE))) {
pr_err("Not permitted to write : 0x%x\n", fp->daccess);
err = -EACCES;
@ -8352,7 +8743,7 @@ int smb2_write(struct ksmbd_work *work)
if (async_interim)
release_async_work(work);
if (err == -EAGAIN)
if (err == -EAGAIN && !chseq_err)
rsp->hdr.Status = STATUS_FILE_LOCK_CONFLICT;
else if (err == -ENOSPC || err == -EFBIG)
rsp->hdr.Status = STATUS_DISK_FULL;
@ -8364,7 +8755,7 @@ int smb2_write(struct ksmbd_work *work)
rsp->hdr.Status = STATUS_SHARING_VIOLATION;
else if (err == -EINVAL)
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
else
else if (rsp->hdr.Status == 0)
rsp->hdr.Status = STATUS_INVALID_HANDLE;
smb2_set_err_rsp(work);
@ -8653,6 +9044,12 @@ int smb2_lock(struct ksmbd_work *work)
goto out2;
}
err = smb2_set_request_open(work, fp, &req->hdr, false, false);
if (err)
goto out2;
smb2_verify_lock_sequence(work, fp, req);
filp = fp->filp;
lock_count = le16_to_cpu(req->LockCount);
lock_ele = req->locks;
@ -8937,6 +9334,7 @@ int smb2_lock(struct ksmbd_work *work)
err = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_lock_rsp));
if (err)
goto out;
smb2_update_lock_sequence(work, fp, req);
ksmbd_fd_put(work, fp);
return 0;
@ -9487,10 +9885,12 @@ int smb2_ioctl(struct ksmbd_work *work)
struct smb2_ioctl_req *req;
struct smb2_ioctl_rsp *rsp;
unsigned int cnt_code, nbytes = 0, out_buf_len, in_buf_len;
u64 id = KSMBD_NO_FID;
u64 id = KSMBD_NO_FID, pid = KSMBD_NO_FID;
struct ksmbd_conn *conn = work->conn;
int ret = 0;
char *buffer;
bool no_fileid_ioctl = false;
bool chseq_err = false;
ksmbd_debug(SMB, "Received smb2 ioctl request\n");
@ -9503,14 +9903,17 @@ int smb2_ioctl(struct ksmbd_work *work)
ksmbd_debug(SMB, "Compound request set FID = %llu\n",
work->compound_fid);
id = work->compound_fid;
pid = work->compound_pfid;
}
} else {
req = smb_get_msg(work->request_buf);
rsp = smb_get_msg(work->response_buf);
}
if (!has_file_id(id))
if (!has_file_id(id)) {
id = req->VolatileFileId;
pid = req->PersistentFileId;
}
if (req->Flags != cpu_to_le32(SMB2_0_IOCTL_IS_FSCTL)) {
ret = -EOPNOTSUPP;
@ -9520,6 +9923,40 @@ int smb2_ioctl(struct ksmbd_work *work)
buffer = (char *)req + le32_to_cpu(req->InputOffset);
cnt_code = le32_to_cpu(req->CtlCode);
switch (cnt_code) {
case FSCTL_DFS_GET_REFERRALS:
case FSCTL_DFS_GET_REFERRALS_EX:
case FSCTL_QUERY_NETWORK_INTERFACE_INFO:
case FSCTL_VALIDATE_NEGOTIATE_INFO:
case FSCTL_PIPE_WAIT:
no_fileid_ioctl = true;
break;
default:
break;
}
if (!no_fileid_ioctl && has_file_id(id)) {
struct ksmbd_file *fp;
fp = ksmbd_lookup_fd_slow(work, id, pid);
if (!fp) {
if (cnt_code == FSCTL_DUPLICATE_EXTENTS_TO_FILE) {
rsp->hdr.Status = STATUS_FILE_CLOSED;
goto out2;
}
ret = -ENOENT;
goto out;
}
ret = smb2_set_request_open(work, fp, &req->hdr, true, false);
ksmbd_fd_put(work, fp);
if (ret) {
rsp->hdr.Status = STATUS_FILE_NOT_AVAILABLE;
chseq_err = true;
goto out;
}
}
ret = smb2_calc_max_out_buf_len(work,
offsetof(struct smb2_ioctl_rsp, Buffer),
le32_to_cpu(req->MaxOutputResponse));
@ -10055,7 +10492,7 @@ int smb2_ioctl(struct ksmbd_work *work)
rsp->hdr.Status = STATUS_NOT_SUPPORTED;
else if (ret == -ENOSPC)
rsp->hdr.Status = STATUS_BUFFER_TOO_SMALL;
else if (ret < 0 || rsp->hdr.Status == 0)
else if (!chseq_err && (ret < 0 || rsp->hdr.Status == 0))
rsp->hdr.Status = STATUS_INVALID_PARAMETER;
out2:
@ -10095,6 +10532,14 @@ static void smb20_oplock_break_ack(struct ksmbd_work *work)
return;
}
ret = smb2_set_request_open(work, fp, &req->hdr, false, false);
if (ret) {
rsp->hdr.Status = STATUS_FILE_CLOSED;
smb2_set_err_rsp(work);
ksmbd_fd_put(work, fp);
return;
}
opinfo = opinfo_get(fp);
if (!opinfo) {
pr_err("unexpected null oplock_info\n");
@ -10107,6 +10552,22 @@ static void smb20_oplock_break_ack(struct ksmbd_work *work)
if (opinfo->op_state != OPLOCK_ACK_WAIT) {
ksmbd_debug(SMB, "unexpected oplock state 0x%x\n",
opinfo->op_state);
if (smb3_hdr_replay(&req->hdr) &&
opinfo->op_state == OPLOCK_STATE_NONE) {
rsp->StructureSize = cpu_to_le16(24);
rsp->OplockLevel = opinfo->level;
rsp->Reserved = 0;
rsp->Reserved2 = 0;
rsp->VolatileFid = volatile_id;
rsp->PersistentFid = persistent_id;
ret = ksmbd_iov_pin_rsp(work, rsp,
sizeof(struct smb2_oplock_break));
if (ret)
ksmbd_debug(SMB,
"failed to pin replayed oplock break response: %d\n",
ret);
goto out_no_state_change;
}
if (opinfo->level == SMB2_OPLOCK_LEVEL_NONE)
status = STATUS_INVALID_OPLOCK_PROTOCOL;
else
@ -10175,6 +10636,7 @@ static void smb20_oplock_break_ack(struct ksmbd_work *work)
out:
opinfo->op_state = OPLOCK_STATE_NONE;
wake_up_interruptible_all(&opinfo->oplock_q);
out_no_state_change:
opinfo_put(opinfo);
ksmbd_fd_put(work, fp);
}
@ -10227,11 +10689,15 @@ static void smb21_lease_break_ack(struct ksmbd_work *work)
if (opinfo->op_state == OPLOCK_STATE_NONE) {
pr_err("unexpected lease break state 0x%x\n",
opinfo->op_state);
if (smb3_hdr_replay(&req->hdr))
goto replay_rsp;
rsp->hdr.Status = STATUS_UNSUCCESSFUL;
goto err_out;
}
if (!atomic_read(&opinfo->breaking_cnt)) {
if (smb3_hdr_replay(&req->hdr))
goto replay_rsp;
rsp->hdr.Status = STATUS_UNSUCCESSFUL;
goto err_out;
}
@ -10266,6 +10732,19 @@ static void smb21_lease_break_ack(struct ksmbd_work *work)
opinfo_put(opinfo);
return;
replay_rsp:
rsp->StructureSize = cpu_to_le16(36);
rsp->Reserved = 0;
rsp->Flags = 0;
memcpy(rsp->LeaseKey, req->LeaseKey, 16);
rsp->LeaseState = lease->state;
rsp->LeaseDuration = 0;
ret = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_lease_ack));
if (ret)
goto err_out;
opinfo_put(opinfo);
return;
err_out:
smb2_set_err_rsp(work);
opinfo_put(opinfo);

View File

@ -428,6 +428,7 @@ bool smb3_encryption_negotiated(struct ksmbd_conn *conn);
/* smb2 misc functions */
int ksmbd_smb2_check_message(struct ksmbd_work *work);
void smb2_complete_request_open(struct ksmbd_work *work);
/* smb2 command handlers */
int smb2_handle_negotiate(struct ksmbd_work *work);

View File

@ -729,6 +729,11 @@ static struct ksmbd_file *ksmbd_fp_get(struct ksmbd_file *fp)
return fp;
}
struct ksmbd_file *ksmbd_file_get(struct ksmbd_file *fp)
{
return ksmbd_fp_get(fp);
}
static struct ksmbd_file *__ksmbd_lookup_fd(struct ksmbd_file_table *ft,
u64 id)
{
@ -1572,7 +1577,7 @@ void ksmbd_stop_durable_scavenger(void)
}
/*
* ksmbd_vfs_copy_durable_owner - Copy owner info for durable reconnect
* ksmbd_vfs_set_durable_owner - Store owner info for durable replay/reconnect
* @fp: ksmbd file pointer to store owner info
* @user: user pointer to copy from
*
@ -1581,8 +1586,8 @@ void ksmbd_stop_durable_scavenger(void)
*
* Return: 0 on success, or negative error code on failure
*/
static int ksmbd_vfs_copy_durable_owner(struct ksmbd_file *fp,
struct ksmbd_user *user)
int ksmbd_vfs_set_durable_owner(struct ksmbd_file *fp,
struct ksmbd_user *user)
{
char *name;
@ -1653,7 +1658,7 @@ static bool session_fd_check(struct ksmbd_tree_connect *tcon,
if (WARN_ON_ONCE(!fp->conn))
return false;
if (ksmbd_vfs_copy_durable_owner(fp, user))
if (ksmbd_vfs_set_durable_owner(fp, user))
return false;
/*

View File

@ -82,6 +82,13 @@ struct durable_owner {
char *name;
};
#define KSMBD_LOCK_SEQ_ARRAY_SIZE 64
struct ksmbd_lock_sequence {
bool valid;
u8 sequence;
};
struct ksmbd_file {
struct file *filp;
u64 persistent_id;
@ -101,6 +108,7 @@ struct ksmbd_file {
__le32 saccess;
__le32 coption;
__le32 cdoption;
__le32 create_file_attributes;
__u64 create_time;
__u64 change_time;
__u64 allocation_size;
@ -128,6 +136,8 @@ struct ksmbd_file {
unsigned int durable_timeout;
unsigned int durable_scavenger_timeout;
/* CREATE action returned when this durable handle was established. */
__le32 create_action;
/* if ls is happening on directory, below is valid*/
struct ksmbd_readdir_data readdir_data;
@ -139,9 +149,14 @@ struct ksmbd_file {
bool is_persistent;
bool is_resilient;
bool durable_reconnect_disabled;
bool durable_replay_consumed;
bool is_posix_ctxt;
struct durable_owner owner;
__le16 channel_sequence;
unsigned int outstanding_requests;
unsigned int outstanding_pre_requests;
struct ksmbd_lock_sequence lock_seq[KSMBD_LOCK_SEQ_ARRAY_SIZE];
/*
* Pending CHANGE_NOTIFY completions for this handle, sent with
@ -180,6 +195,9 @@ struct ksmbd_file *ksmbd_lookup_fd_fast(struct ksmbd_work *work, u64 id);
struct ksmbd_file *ksmbd_lookup_foreign_fd(struct ksmbd_work *work, u64 id);
struct ksmbd_file *ksmbd_lookup_fd_slow(struct ksmbd_work *work, u64 id,
u64 pid);
int ksmbd_vfs_set_durable_owner(struct ksmbd_file *fp,
struct ksmbd_user *user);
struct ksmbd_file *ksmbd_file_get(struct ksmbd_file *fp);
void ksmbd_fd_put(struct ksmbd_work *work, struct ksmbd_file *fp);
struct ksmbd_inode *ksmbd_inode_lookup_lock(struct dentry *d);
void ksmbd_inode_put(struct ksmbd_inode *ci);