linux-next: manual merge of the ksmbd tree with the origin tree
From: Mark Brown <broonie@kernel.org>
Date: 2026-09-07 12:42:15
Also in:
lkml
Hi all,
Today's linux-next merge of the ksmbd tree got a conflict in:
fs/smb/server/smb2pdu.c
between commit:
3a2c4d55e32ad ("treewide: refresh kmalloc_obj() conversions")
from the origin tree and commits:
596450ed188db ("smb/server: support compound fid in notify requests")
ccdae1bdbd85f ("ksmbd: refactor smb2_notify() to a blocking wait")
from the ksmbd tree.
I fixed it up (see below) and can carry the fix as necessary. This
is now fixed as far as linux-next is concerned, but any non trivial
conflicts should be mentioned to your upstream maintainer when your tree
is submitted for merging. You may also want to consider cooperating
with the maintainer of the conflicting tree to minimise any particularly
complex conflicts.
diff --combined fs/smb/server/smb2pdu.c
index b7ce670946260,e7c965e7b0684..0000000000000--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c@@@ -58,10 -58,6 +58,6 @@@ static void __wbuf(struct ksmbd_work *w
}
}
- static struct ksmbd_work *smb2_notify_cancel_claim(void **argv);
- static void smb2_notify_cancel_fn(void **argv);
- static void smb2_complete_notify_cancel(struct ksmbd_work *in_work);
-
#define WORK_BUFFERS(w, rq, rs) __wbuf((w), (void **)&(rq), (void **)&(rs))
#define SMB2_CREATE_FILE_ATTRIBUTE_MASK \
@@@ -1245,6 -1241,7 +1241,7 @@@ void smb2_send_interim_resp(struct ksmb
{
struct smb2_hdr *rsp_hdr;
struct ksmbd_work *in_work = ksmbd_alloc_work_struct();
+ u16 command;
if (!in_work)
return;
@@@ -1268,6 -1265,23 +1265,23 @@@
smb2_set_err_rsp(in_work);
rsp_hdr->Status = status;
+ /*
+ * Async interim responses are unsigned, but final responses must
+ * follow the normal signing rules. The synthetic work has no
+ * request buffer, so use the original work for request signing
+ * checks and the response header for SMB3 command selection.
+ */
+ command = work->conn->ops->get_cmd_val(work);
+ if (status != STATUS_PENDING && !work->encrypted && work->sess &&
+ work->conn->ops->set_sign_rsp &&
+ (work->sess->sign ||
+ (work->conn->ops->is_sign_req &&
+ work->conn->ops->is_sign_req(work, command)))) {
+ in_work->sess = work->sess;
+ work->conn->ops->set_sign_rsp(in_work);
+ in_work->sess = NULL;
+ }
+
if (smb2_send_interim_work(in_work, work, true))
ksmbd_debug(SMB, "failed to send interim response\n");
ksmbd_free_work_struct(in_work);
@@@ -9699,7 -9713,6 +9713,6 @@@ int smb2_cancel(struct ksmbd_work *work
struct smb2_hdr *hdr = smb_get_msg(work->request_buf);
struct smb2_hdr *chdr;
struct ksmbd_work *iter;
- struct ksmbd_work *cancelled_notify = NULL;
struct list_head *command_list;
if (work->next_smb2_rcv_hdr_off)
@@@ -9737,23 -9750,11 +9750,11 @@@
"smb2 with AsyncId %llu cancelled command = 0x%x\n",
le64_to_cpu(hdr->Id.AsyncId),
le16_to_cpu(chdr->Command));
- if (iter->cancel_fn == smb2_notify_cancel_fn)
- cancelled_notify =
- smb2_notify_cancel_claim(iter->cancel_argv);
- else if (iter->cancel_fn)
+ if (iter->cancel_fn)
iter->cancel_fn(iter->cancel_argv);
break;
}
spin_unlock(&conn->request_lock);
-
- /*
- * Complete a cancelled notify before this CANCEL handler returns.
- * Deferring it to the system workqueue lets a following request and
- * its response overtake STATUS_CANCELLED, leaving clients waiting
- * for the original notify even though the cancellation was accepted.
- */
- if (cancelled_notify)
- smb2_complete_notify_cancel(cancelled_notify);
} else {
command_list = &conn->requests;
@@@ -11710,137 -11711,26 +11711,26 @@@ int smb2_oplock_break(struct ksmbd_wor
return 0;
}
- /*
- * Cancel handler for a deferred CHANGE_NOTIFY. Races against
- * __ksmbd_close_fd()'s notify_pendings drain (vfs_cache.c), which can run
- * concurrently on a different connection closing the same handle -- only
- * one of the two may claim and free in_work, so both sides check
- * list_empty() under fp->f_lock before touching it (list_del_init()
- * leaves a node empty, so whichever side removes it first is the owner;
- * the loser must not touch in_work again, since the winner may already be
- * freeing it).
- *
- * smb2_cancel() holds conn->request_lock (a spinlock) for the entire
- * time it walks conn->async_requests and calls this function -- so this
- * runs with preemption disabled and must not sleep or re-acquire that
- * same lock. release_async_work() does both (it takes conn->request_lock
- * itself, and frees things that can involve sleeping paths), so calling
- * it from here would self-deadlock the very thread processing the
- * client's CANCEL command. ksmbd_conn_write() can also sleep (it takes
- * conn's write mutex). So: do only the non-sleeping, no-relock cleanup
- * inline here. smb2_cancel() sends and frees the claimed notify after it
- * drops request_lock, preserving response order for a client CANCEL. The
- * connection teardown caller has no such post-unlock path, so its wrapper
- * defers the send and free to a workqueue.
- */
- struct notify_cancel_ctx {
- struct work_struct work;
- struct ksmbd_work *in_work;
+ struct ksmbd_notify_req {
+ wait_queue_head_t wait;
};
- static void smb2_send_notify_cancelled(struct ksmbd_work *work)
+ /*
+ * Cancel handler for a pending CHANGE_NOTIFY. Called either by
+ * smb2_cancel() (conn->request_lock held, work->state already set to
+ * KSMBD_WORK_CANCELLED by the caller) or by
+ * set_close_state_blocked_works() (vfs_cache.c, fp->f_lock held,
+ * work->state already set to KSMBD_WORK_CLOSED by the caller) -- both
+ * callers hold a spinlock across this call, so it must not sleep.
+ * wake_up() only wakes the waiter in smb2_notify(); it does not touch
+ * fp->blocked_works itself, matching smb2_remove_blocked_lock()'s same
+ * non-mutating style for the equivalent byte-range-lock wait.
+ */
+ static void smb2_notify_cancel(void **argv)
{
- struct smb2_hdr *hdr = smb_get_msg(work->response_buf);
- struct ksmbd_conn *conn = work->conn;
- struct ksmbd_session *sess;
+ struct ksmbd_notify_req *notify_req = argv[0];
- sess = ksmbd_session_lookup(conn, le64_to_cpu(hdr->SessionId));
- if (sess) {
- work->sess = sess;
- if (work->encrypted && sess->enc && conn->ops->encrypt_resp) {
- conn->ops->encrypt_resp(work);
- } else if (conn->ops->is_sign_req && conn->ops->set_sign_rsp &&
- conn->ops->is_sign_req(work,
- conn->ops->get_cmd_val(work))) {
- conn->ops->set_sign_rsp(work);
- }
- }
-
- ksmbd_conn_write(work);
- if (sess) {
- ksmbd_user_session_put(sess);
- work->sess = NULL;
- }
- }
-
- static void smb2_notify_cancel_deferred(struct work_struct *w)
- {
- struct notify_cancel_ctx *ctx =
- container_of(w, struct notify_cancel_ctx, work);
- struct ksmbd_conn *conn = ctx->in_work->conn;
-
- smb2_complete_notify_cancel(ctx->in_work);
- kfree(ctx);
- /*
- * The connection teardown waits for r_count before destroying
- * connection sessions and their proc entries.
- */
- ksmbd_conn_r_count_dec(conn);
- }
-
- static struct ksmbd_work *smb2_notify_cancel_claim(void **argv)
- {
- struct ksmbd_work *in_work = (struct ksmbd_work *)argv[0];
- struct ksmbd_file *fp = (struct ksmbd_file *)argv[1];
- bool claimed;
-
- spin_lock(&fp->f_lock);
- claimed = !list_empty(&in_work->notify_entry);
- if (claimed)
- list_del_init(&in_work->notify_entry);
- spin_unlock(&fp->f_lock);
-
- if (!claimed)
- return NULL;
-
- /* conn->request_lock is held by smb2_cancel() or connection teardown. */
- in_work->cancel_fn = NULL;
- kfree(in_work->cancel_argv);
- in_work->cancel_argv = NULL;
- return in_work;
- }
-
- static void smb2_complete_notify_cancel(struct ksmbd_work *in_work)
- {
- struct smb2_hdr *in_hdr = smb_get_msg(in_work->response_buf);
-
- in_hdr->Status = STATUS_CANCELLED;
- smb2_send_notify_cancelled(in_work);
- release_async_work(in_work);
- ksmbd_free_work_struct(in_work);
- }
-
- static void smb2_notify_cancel_fn(void **argv)
- {
- struct ksmbd_work *in_work = smb2_notify_cancel_claim(argv);
- struct ksmbd_conn *conn;
- struct notify_cancel_ctx *ctx;
-
- if (!in_work)
- return;
- conn = in_work->conn;
-
- ctx = kmalloc_obj(*ctx, GFP_ATOMIC);
- if (!ctx) {
- /* Can't defer the response -- free without sending one. */
- list_del_init(&in_work->async_request_entry);
- in_work->asynchronous = false;
- if (in_work->async_id) {
- ksmbd_release_id(&conn->async_ida, in_work->async_id);
- in_work->async_id = 0;
- }
- ksmbd_free_work_struct(in_work);
- return;
- }
- ctx->in_work = in_work;
- INIT_WORK(&ctx->work, smb2_notify_cancel_deferred);
- /*
- * This deferred work can outlive the connection handler's receive loop.
- * Keep teardown from destroying the connection's sessions until the
- * deferred response has finished using them.
- */
- ksmbd_conn_r_count_inc(conn);
- schedule_work(&ctx->work);
+ wake_up(¬ify_req->wait);
}
/**
@@@ -11853,9 -11743,11 +11743,11 @@@ int smb2_notify(struct ksmbd_work *work
{
struct smb2_change_notify_req *req;
struct smb2_change_notify_rsp *rsp;
- struct ksmbd_work *in_work;
- struct smb2_hdr *in_hdr;
- struct ksmbd_file *fp;
+ struct ksmbd_notify_req notify_req;
+ struct ksmbd_file *fp = NULL;
+ void **argv = NULL;
+ bool async_work = false;
+ int err = 0;
ksmbd_debug(SMB, "Received smb2 notify\n");
@@@ -11866,164 -11758,83 +11758,83 @@@
if (work->next_smb2_rcv_hdr_off && req->hdr.NextCommand) {
rsp->hdr.Status = STATUS_INTERNAL_ERROR;
- smb2_set_err_rsp(work);
- return -EIO;
+ err = -EIO;
+ goto out;
}
- /*
- * macOS backupd sends CHANGE_NOTIFY with FileId=FFFF...FFFF (share-root
- * sentinel) to watch for changes on the share root without holding an
- * open handle. Respond STATUS_PENDING + STATUS_NOTIFY_CLEANUP immediately;
- * without this, backupd aborts Time Machine setup on STATUS_FILE_CLOSED.
- */
- if (req->VolatileFileId == SMB2_NO_FID &&
- req->PersistentFileId == SMB2_NO_FID) {
- in_work = ksmbd_alloc_work_struct();
- if (!in_work || allocate_interim_rsp_buf(in_work)) {
- if (in_work)
- ksmbd_free_work_struct(in_work);
- rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
- smb2_set_err_rsp(work);
- return 0;
- }
- if (setup_async_work(work, NULL, NULL)) {
- ksmbd_free_work_struct(in_work);
- rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
- smb2_set_err_rsp(work);
- return 0;
- }
- smb2_send_interim_resp(work, STATUS_PENDING);
- in_work->conn = work->conn;
- in_hdr = smb_get_msg(in_work->response_buf);
- memcpy(in_hdr, ksmbd_resp_buf_next(work),
- __SMB2_HEADER_STRUCTURE_SIZE);
- in_hdr->Flags |= SMB2_FLAGS_ASYNC_COMMAND;
- in_hdr->Id.AsyncId = cpu_to_le64(work->async_id);
- smb2_set_err_rsp(in_work);
- in_hdr->Status = STATUS_NOTIFY_CLEANUP;
- in_work->async_id = work->async_id;
- work->async_id = 0;
- release_async_work(work);
- if (smb2_send_interim_work(in_work, work, false))
- ksmbd_debug(SMB, "failed to send notify cleanup\n");
- ksmbd_free_work_struct(in_work);
- work->send_no_response = 1;
- return 0;
- }
-
- /*
- * KSMBD does not implement a real change-notification backend.
- * Genuine SMB2 servers (and macOS smbfs) never complete a
- * CHANGE_NOTIFY spontaneously: it is satisfied only by a real
- * directory change, or with STATUS_NOTIFY_CLEANUP when the watched
- * handle is closed. Completing it early (e.g. on a timer) makes
- * Finder treat the cleanup as "directory changed" and re-enumerate
- * the directory forever, leaving items unopenable. Returning
- * STATUS_NOT_IMPLEMENTED here (like stock ksmbd) makes macOS smbfs
- * hard-freeze on unmount, so this must stay deferred.
- */
fp = ksmbd_lookup_fd_slow(work, req->VolatileFileId, req->PersistentFileId);
if (!fp) {
rsp->hdr.Status = STATUS_FILE_CLOSED;
- smb2_set_err_rsp(work);
- return 0;
+ err = -ENOENT;
+ goto out;
}
- in_work = ksmbd_alloc_work_struct();
- if (!in_work || allocate_interim_rsp_buf(in_work)) {
- if (in_work)
- ksmbd_free_work_struct(in_work);
- ksmbd_fd_put(work, fp);
+ argv = kmalloc(sizeof(void *), KSMBD_DEFAULT_GFP);
+ if (!argv) {
rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
- smb2_set_err_rsp(work);
- return 0;
+ err = -ENOMEM;
+ goto out;
}
+ init_waitqueue_head(¬ify_req.wait);
+ argv[0] = ¬ify_req;
+
+ err = setup_async_work(work, smb2_notify_cancel, argv);
+ if (err) {
+ rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
+ goto out;
+ }
+ async_work = true;
+
/*
- * in_work is synthetic (not from the normal request-receiving
- * pipeline), so it has no request_buf of its own. It gets registered
- * into conn->async_requests below, and smb2_cancel() unconditionally
- * computes smb_get_msg(iter->request_buf) for every entry in that
- * list while searching for a match -- give it its own small buffer
- * (not an alias of response_buf: ksmbd_free_work_struct() kvfree()s
- * both separately, so aliasing them would double-free) so that stays
- * a harmless read instead of a near-NULL dereference.
+ * Handle close holds the file-table write lock while it marks the
+ * handle closed and walks blocked_works. Hold the matching read lock
+ * across the state check and registration so close cannot finish its
+ * walk between the lookup above and this list insertion.
*/
- in_work->request_buf = kzalloc(MAX_CIFS_SMALL_BUFFER_SIZE, KSMBD_DEFAULT_GFP);
- if (!in_work->request_buf) {
- ksmbd_free_work_struct(in_work);
- ksmbd_fd_put(work, fp);
- rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
- smb2_set_err_rsp(work);
- return 0;
- }
- memcpy(smb_get_msg(in_work->request_buf), req,
- __SMB2_HEADER_STRUCTURE_SIZE);
-
- if (setup_async_work(work, NULL, NULL)) {
- ksmbd_free_work_struct(in_work);
- ksmbd_fd_put(work, fp);
- rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
- smb2_set_err_rsp(work);
- return 0;
+ read_lock(&work->sess->file_table.lock);
+ if (fp->f_state != FP_INITED) {
+ read_unlock(&work->sess->file_table.lock);
+ rsp->hdr.Status = STATUS_NOTIFY_CLEANUP;
+ err = -ENOENT;
+ goto out;
}
+ spin_lock(&fp->f_lock);
+ list_add_tail(&work->fp_entry, &fp->blocked_works);
+ spin_unlock(&fp->f_lock);
+ read_unlock(&work->sess->file_table.lock);
smb2_send_interim_resp(work, STATUS_PENDING);
- /* Keep the async IDA alive until the deferred work is released. */
- in_work->conn = ksmbd_conn_get(work->conn);
- in_work->owns_conn_ref = true;
- in_work->encrypted = work->encrypted;
- in_hdr = smb_get_msg(in_work->response_buf);
- memcpy(in_hdr, ksmbd_resp_buf_next(work), __SMB2_HEADER_STRUCTURE_SIZE);
- in_hdr->Flags |= SMB2_FLAGS_ASYNC_COMMAND;
- in_hdr->Id.AsyncId = cpu_to_le64(work->async_id);
- smb2_set_err_rsp(in_work);
- in_hdr->Status = STATUS_NOTIFY_CLEANUP;
-
- /*
- * Transfer ownership of the async id to in_work; it stays reserved
- * until in_work is freed after the deferred response is sent on
- * close, so it can't be reused for an unrelated async response.
- */
- in_work->async_id = work->async_id;
- work->async_id = 0;
- release_async_work(work);
-
- /*
- * work itself is about to be recycled by the normal request-processing
- * pipeline, so it can't stay the target of a future CANCEL -- register
- * in_work instead, reusing the same async_id, so a client-sent CANCEL
- * for this notify actually finds something to cancel instead of
- * silently doing nothing until the handle eventually closes.
- */
- in_work->asynchronous = true;
- in_work->cancel_argv = kmalloc_array(2, sizeof(void *), KSMBD_DEFAULT_GFP);
- if (in_work->cancel_argv) {
- in_work->cancel_argv[0] = in_work;
- in_work->cancel_argv[1] = fp;
- in_work->cancel_fn = smb2_notify_cancel_fn;
- }
-
- if (!ksmbd_conn_link_async_request(work->conn, in_work)) {
- kfree(in_work->cancel_argv);
- in_work->cancel_argv = NULL;
- in_work->cancel_fn = NULL;
- in_work->asynchronous = false;
- ksmbd_fd_put(work, fp);
- if (smb2_send_interim_work(in_work, work, false))
- ksmbd_debug(SMB, "failed to send notify cleanup\n");
- ksmbd_free_work_struct(in_work);
- work->send_no_response = 1;
- return 0;
+ err = wait_event_interruptible(notify_req.wait,
+ READ_ONCE(work->state) != KSMBD_WORK_ACTIVE);
+ if (err && READ_ONCE(work->state) == KSMBD_WORK_ACTIVE) {
+ /*
+ * Woken by a signal, not a real cancel/close. There is no
+ * notification backend yet to report anything else against,
+ * so treat this the same as a client-side cancel.
+ */
+ WRITE_ONCE(work->state, KSMBD_WORK_CANCELLED);
}
spin_lock(&fp->f_lock);
- list_add_tail(&in_work->notify_entry, &fp->notify_pendings);
+ list_del_init(&work->fp_entry);
spin_unlock(&fp->f_lock);
- ksmbd_fd_put(work, fp);
+ rsp->hdr.Status = work->state == KSMBD_WORK_CLOSED ?
+ STATUS_NOTIFY_CLEANUP : STATUS_CANCELLED;
+ smb2_send_interim_resp(work, rsp->hdr.Status);
work->send_no_response = 1;
- return 0;
+
+ out:
+ if (rsp->hdr.Status != STATUS_SUCCESS && !work->send_no_response)
+ smb2_set_err_rsp(work);
+ if (async_work)
+ release_async_work(work);
+ else
+ kfree(argv);
+ if (fp)
+ ksmbd_fd_put(work, fp);
+ return err;
}
/**
@@@ -12218,11 -12029,12 +12029,12 @@@ void smb3_set_sign_rsp(struct ksmbd_wor
struct channel *chann;
char signature[SMB2_CMACAES_SIZE];
struct kvec *iov;
- u16 command = conn->ops->get_cmd_val(work);
+ u16 command;
int n_vec;
char *signing_key;
hdr = ksmbd_resp_buf_curr(work);
+ command = le16_to_cpu(hdr->Command);
if (command == SMB2_SESSION_SETUP_HE &&
(!conn->binding || hdr->Status != STATUS_SUCCESS)) { Attachments
- signature.asc [application/pgp-signature] 488 bytes