get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/1.1/patches/2229610/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 2229610,
    "url": "http://patchwork.ozlabs.org/api/1.1/patches/2229610/?format=api",
    "web_url": "http://patchwork.ozlabs.org/project/linux-cifs-client/patch/20260428140856.941847-2-charsyam@gmail.com/",
    "project": {
        "id": 12,
        "url": "http://patchwork.ozlabs.org/api/1.1/projects/12/?format=api",
        "name": "Linux CIFS Client",
        "link_name": "linux-cifs-client",
        "list_id": "linux-cifs.vger.kernel.org",
        "list_email": "linux-cifs@vger.kernel.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": ""
    },
    "msgid": "<20260428140856.941847-2-charsyam@gmail.com>",
    "date": "2026-04-28T14:08:54",
    "name": "[v2,1/3] ksmbd: centralize ksmbd_conn final release to plug transport leak",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "00a147a446bede1f4b3ab9841fcd928bca49ea00",
    "submitter": {
        "id": 93166,
        "url": "http://patchwork.ozlabs.org/api/1.1/people/93166/?format=api",
        "name": "DaeMyung Kang",
        "email": "charsyam@gmail.com"
    },
    "delegate": null,
    "mbox": "http://patchwork.ozlabs.org/project/linux-cifs-client/patch/20260428140856.941847-2-charsyam@gmail.com/mbox/",
    "series": [
        {
            "id": 501865,
            "url": "http://patchwork.ozlabs.org/api/1.1/series/501865/?format=api",
            "web_url": "http://patchwork.ozlabs.org/project/linux-cifs-client/list/?series=501865",
            "date": "2026-04-28T14:08:55",
            "name": "ksmbd: fix connection and durable handle teardown races",
            "version": 2,
            "mbox": "http://patchwork.ozlabs.org/series/501865/mbox/"
        }
    ],
    "comments": "http://patchwork.ozlabs.org/api/patches/2229610/comments/",
    "check": "pending",
    "checks": "http://patchwork.ozlabs.org/api/patches/2229610/checks/",
    "tags": {},
    "headers": {
        "Return-Path": "\n <linux-cifs+bounces-11217-incoming=patchwork.ozlabs.org@vger.kernel.org>",
        "X-Original-To": [
            "incoming@patchwork.ozlabs.org",
            "linux-cifs@vger.kernel.org"
        ],
        "Delivered-To": "patchwork-incoming@legolas.ozlabs.org",
        "Authentication-Results": [
            "legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256\n header.s=20251104 header.b=Nz0374Sj;\n\tdkim-atps=neutral",
            "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org\n (client-ip=2600:3c0a:e001:db::12fc:5321; helo=sea.lore.kernel.org;\n envelope-from=linux-cifs+bounces-11217-incoming=patchwork.ozlabs.org@vger.kernel.org;\n receiver=patchwork.ozlabs.org)",
            "smtp.subspace.kernel.org;\n\tdkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com\n header.b=\"Nz0374Sj\"",
            "smtp.subspace.kernel.org;\n arc=none smtp.client-ip=209.85.214.176",
            "smtp.subspace.kernel.org;\n dmarc=pass (p=none dis=none) header.from=gmail.com",
            "smtp.subspace.kernel.org;\n spf=pass smtp.mailfrom=gmail.com"
        ],
        "Received": [
            "from sea.lore.kernel.org (sea.lore.kernel.org\n [IPv6:2600:3c0a:e001:db::12fc:5321])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4g4jT54XzMz1yHv\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 29 Apr 2026 00:27:53 +1000 (AEST)",
            "from smtp.subspace.kernel.org (conduit.subspace.kernel.org\n [100.90.174.1])\n\tby sea.lore.kernel.org (Postfix) with ESMTP id 8286C31B8F68\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 28 Apr 2026 14:09:23 +0000 (UTC)",
            "from localhost.localdomain (localhost.localdomain [127.0.0.1])\n\tby smtp.subspace.kernel.org (Postfix) with ESMTP id 885FA44102D;\n\tTue, 28 Apr 2026 14:09:06 +0000 (UTC)",
            "from mail-pl1-f176.google.com (mail-pl1-f176.google.com\n [209.85.214.176])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits))\n\t(No client certificate requested)\n\tby smtp.subspace.kernel.org (Postfix) with ESMTPS id ADBE9441022\n\tfor <linux-cifs@vger.kernel.org>; Tue, 28 Apr 2026 14:09:04 +0000 (UTC)",
            "by mail-pl1-f176.google.com with SMTP id\n d9443c01a7336-2ae3a007bd1so12993995ad.2\n        for <linux-cifs@vger.kernel.org>;\n Tue, 28 Apr 2026 07:09:04 -0700 (PDT)",
            "from ser8.. ([221.156.231.192])\n        by smtp.gmail.com with ESMTPSA id\n 41be03b00d2f7-c7fcade0f56sm2190526a12.21.2026.04.28.07.09.02\n        (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n        Tue, 28 Apr 2026 07:09:03 -0700 (PDT)"
        ],
        "ARC-Seal": "i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;\n\tt=1777385346; cv=none;\n b=X20SBVRWL11VyMwE8JUJejMJtgtiS+NCJ4lBEftNhOo95qzem/C42HgmiYci7n9E+5dP81Wm8a4VBfAbdq0Py5y10c+rucaGv1mC9pmyP0IoGhTOX6pJuQ7+xa5FVDLdkjeIBS2inXufPKCq2L6kG35hNf0KEnP0E//nRQi04QI=",
        "ARC-Message-Signature": "i=1; a=rsa-sha256; d=subspace.kernel.org;\n\ts=arc-20240116; t=1777385346; c=relaxed/simple;\n\tbh=KGBe/gwXXssI0g1Bu4ClISnGFi2PC+zHggOckt79b48=;\n\th=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:\n\t MIME-Version;\n b=d2W91fWQy5eGqaqhK+w0tFqayJIz15oW7IjhnxDUiozQ7DiaQhHRdyNddhVLBf0cVwuRUcAWkumkDHNf8I8HwbhCdtt8fbutBwfX2yHiihEGnD0qN5S33oF1Qu9TuVjtEp+bbxNPKk66XTTwU9Tv+oBhlLzgmt7FKb7VacpQky8=",
        "ARC-Authentication-Results": "i=1; smtp.subspace.kernel.org;\n dmarc=pass (p=none dis=none) header.from=gmail.com;\n spf=pass smtp.mailfrom=gmail.com;\n dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com\n header.b=Nz0374Sj; arc=none smtp.client-ip=209.85.214.176",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n        d=gmail.com; s=20251104; t=1777385344; x=1777990144;\n darn=vger.kernel.org;\n        h=content-transfer-encoding:mime-version:references:in-reply-to\n         :message-id:date:subject:cc:to:from:from:to:cc:subject:date\n         :message-id:reply-to;\n        bh=uEgePd5kGimwA7FpQCQXmU75w1iF7H1t3sq7wQi6GBA=;\n        b=Nz0374SjEAiL7moB3moFwnI6h8XRFjSgEzQE3MdlB33qVntujG+Z3Rkxr5jN3io4wh\n         qK39zCSBRcuHD9jxmZ4Pu/7NL+uf5r+F2hKxu/fRkJMciJCh/XbMSu+WjRwNHBaVX1tO\n         ouzuhIcv4mqftuf5MqACYSda3WJ5EsfH9J+jK8nn5YeGgtqsK7/966j/Y/PcMyNuri7Q\n         ivTP8egx6APQk2k2uwjaO5cDaCU/7p5OMYpbftCQMmiOxrqilIwRQ2lilYNZbljBSoLl\n         KKohNRtKc/2OWejfcTpwqtR3ebVRzRiD0AgCfNE5mIUxvtJyIDjuuKVKC9rwW5CWR/+y\n         5KGg==",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n        d=1e100.net; s=20251104; t=1777385344; x=1777990144;\n        h=content-transfer-encoding:mime-version:references:in-reply-to\n         :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from\n         :to:cc:subject:date:message-id:reply-to;\n        bh=uEgePd5kGimwA7FpQCQXmU75w1iF7H1t3sq7wQi6GBA=;\n        b=d0Gs3VMHTtwg1u1ntqb3BSwBODrjxJ3ehF2AKjftj+U1Cp6qqT2LgKHVZXM03Ym/Ay\n         4EmV9uQUzUlee3NtAUSpsfZuVJlqjn6zV/HilLtfBWmxDFicRTBeLFTKtAQXgzagmgZK\n         Sa5JJtC266mFS3OA+IUc9zGITkbWTncH84BYVfAb+omi4phcID8PlqXVKATAWticxWzC\n         jw3hcchpCu5PkWjXKI6fhzuxTF8uq7xUgJs93mWkKzf8HUuygGyG/YZxcZ6vKhjN6KWI\n         Hb6ScMdS3Y57dTy0eNYvVMRL/D3jN+Zic24mhi2XZ9o4zLfqdtrzU9jADfYEchZrue4Q\n         N3Hg==",
        "X-Forwarded-Encrypted": "i=1;\n AFNElJ+afwijMZMhesK6w3pbYCLQf2a/peWd/B7bPhPmGsILAqjqF0eStNCkzy9tREq96ZLDzgUrSA+KMpNI@vger.kernel.org",
        "X-Gm-Message-State": "AOJu0Yx/iGXZ+ph893irOkeaHnGuQGNMFDBwDXHDzBnsq8qZeKr0vH6s\n\tjyIbWHsvRcWiHlQyTNbiUnBn1pKD4YRI0MlXOgQjJilnk77YOna+GbT8",
        "X-Gm-Gg": "AeBDiesdldWfoQ+oKnuFMqqIfNMz77Bitn/H3VWLmU13wimozhSe6h3/WeSfCR2Q9Uq\n\tvyuR6ccIjeRSeZuVACxd54WF9cCmrLlgOjKZf3cT/kXZfh7sJeTd4KDC69V7L796UziSJA3s8uT\n\tBMIy5PwkFdNyRyrpxbDtXYRJBXEdDzBXDh5sP5mv/m54FIM+d5s+C6kqidtCnxUCAaKpfb6lb61\n\ty/mK2kQyvQfUAmWhmdiqMY0NTyrgDh5ZDs3zd+dZnOslXJoRCkdyMlf/lkq9cJJz2P7xA70URIR\n\tSrY2+fu2SeiLtB5JBJt8xg/CHniB/eVX9HAtkY5KA/Ms39kp1K79k1rw0nY6CZFU/7y4FEY4SvP\n\tY/hVVfogbZoDEH0bbGRB+swzpRKj4ygt3webRq/G55MDOS++pwlZlCkXMpMTgWsATmDE8U1WD2/\n\tREz6MOpitvRXs7c7SO/HJ4ZYjnruc=",
        "X-Received": "by 2002:a05:6300:4047:b0:3a1:6a7c:dba5 with SMTP id\n adf61e73a8af0-3a398e6360emr2347405637.6.1777385343908;\n        Tue, 28 Apr 2026 07:09:03 -0700 (PDT)",
        "From": "DaeMyung Kang <charsyam@gmail.com>",
        "To": "Namjae Jeon <linkinjeon@kernel.org>,\n\tSteve French <smfrench@gmail.com>",
        "Cc": "Sergey Senozhatsky <senozhatsky@chromium.org>,\n\tTom Talpey <tom@talpey.com>,\n\tHyunchul Lee <hyc.lee@gmail.com>,\n\tRonnie Sahlberg <lsahlber@redhat.com>,\n\tlinux-cifs@vger.kernel.org,\n\tlinux-kernel@vger.kernel.org,\n\tDaeMyung Kang <charsyam@gmail.com>",
        "Subject": "[PATCH v2 1/3] ksmbd: centralize ksmbd_conn final release to plug\n transport leak",
        "Date": "Tue, 28 Apr 2026 23:08:54 +0900",
        "Message-ID": "<20260428140856.941847-2-charsyam@gmail.com>",
        "X-Mailer": "git-send-email 2.43.0",
        "In-Reply-To": "<20260428140856.941847-1-charsyam@gmail.com>",
        "References": "<20260428140856.941847-1-charsyam@gmail.com>",
        "Precedence": "bulk",
        "X-Mailing-List": "linux-cifs@vger.kernel.org",
        "List-Id": "<linux-cifs.vger.kernel.org>",
        "List-Subscribe": "<mailto:linux-cifs+subscribe@vger.kernel.org>",
        "List-Unsubscribe": "<mailto:linux-cifs+unsubscribe@vger.kernel.org>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit"
    },
    "content": "ksmbd_conn_free() is one of four sites that can observe the last\nrefcount drop of a struct ksmbd_conn.  The other three\n\n    fs/smb/server/connection.c    ksmbd_conn_r_count_dec()\n    fs/smb/server/oplock.c        __free_opinfo()\n    fs/smb/server/vfs_cache.c     session_fd_check()\n\nend the conn with a bare kfree(), skipping\nida_destroy(&conn->async_ida) and\nconn->transport->ops->free_transport(conn->transport).  Whenever one\nof them is the last putter, the embedded async_ida and the entire\ntransport struct leak -- for TCP, that is also the struct socket and\nthe kvec iov.\n\n__free_opinfo() being a final putter is not theoretical.  opinfo_put()\nqueues the callback via call_rcu(&opinfo->rcu, free_opinfo_rcu), so\nksmbd_server_terminate_conn() can deposit N opinfo releases in RCU and\nhave ksmbd_conn_free() run in the handler thread before any of them\nfire.  ksmbd_conn_free() then observes refcnt > 0 and short-circuits;\nthe last RCU-delivered __free_opinfo() falls onto its bare kfree(conn)\nbranch and the transport is lost.\n\nA/B validation in a QEMU/virtme guest, mounting //127.0.0.1/testshare:\neach iteration holds 8 files open via sleep processes, force-closes\nTCP with \"ss -K sport = :445\", kills the holders, lazy-umounts;\nrepeated 10 times, then ksmbd shutdown and kmemleak scan.\n\n    state         conn_alloc  conn_free  tcp_free  opi_rcu  kmemleak\n    ----------    ----------  ---------  --------  -------  --------\n    pre-patch         20          20        10       160        7\n    with patch        20          20        20       160        0\n\nPre-patch conn_free=20 with tcp_free=10 directly demonstrates the\nbare-kfree paths skipping transport cleanup; kmemleak backtraces point\ninto struct tcp_transport / iov.  With this patch tcp_free matches\nconn_free at 20/20 and kmemleak is clean.\n\nMove the per-struct final release into __ksmbd_conn_release_work() and\nroute the three bare-kfree final-put sites through a new\nksmbd_conn_put().  Those sites now pair ida_destroy() and\nfree_transport() with kfree(conn) regardless of which holder happens\nto release the last reference.  stop_sessions() only triggers the\ntransport shutdown and does not itself drop the last conn reference,\nso it is unaffected.\n\nThe centralized release reaches sock_release() -> tcp_close() ->\nlock_sock_nested() (might_sleep) from every final putter, including\n__free_opinfo() invoked from an RCU softirq callback, which trips\nCONFIG_DEBUG_ATOMIC_SLEEP.  Defer the release to a dedicated\nksmbd_conn_wq workqueue so ksmbd_conn_put() is safe from any\nnon-sleeping context.\n\nMake ksmbd_file own a strong connection reference while fp->conn is\nnon-NULL so durable-preserve and final-close paths cannot dereference\na stale connection.  ksmbd_open_fd() and ksmbd_reopen_durable_fd()\ntake the reference via ksmbd_conn_get() (the latter also reorders the\nfp->conn / fp->tcon assignments before __open_id() so the published fp\nis never observed with fp->conn == NULL); session_fd_check() and\n__ksmbd_close_fd() drop it via ksmbd_conn_put().  With that invariant,\nsession_fd_check() can take a local conn pointer once and use it\nacross the m_op_list and lock_list iterations even though op->conn\nputs may otherwise drop the last reference.\n\nAt module exit the workqueue is flushed and destroyed after\nrcu_barrier(), so any release queued by a trailing RCU callback is\ndrained before the inode hash and module text go away.\n\nFixes: ee426bfb9d09 (\"ksmbd: add refcnt to ksmbd_conn struct\")\nSigned-off-by: DaeMyung Kang <charsyam@gmail.com>\n---\n fs/smb/server/connection.c | 101 +++++++++++++++++++++++++++++++------\n fs/smb/server/connection.h |   6 +++\n fs/smb/server/oplock.c     |   7 +--\n fs/smb/server/server.c     |  12 +++++\n fs/smb/server/vfs_cache.c  |  60 ++++++++++++++++++----\n 5 files changed, 156 insertions(+), 30 deletions(-)",
    "diff": "diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c\nindex fbbc0529743f..fc9b35d41185 100644\n--- a/fs/smb/server/connection.c\n+++ b/fs/smb/server/connection.c\n@@ -79,6 +79,81 @@ static int create_proc_clients(void) { return 0; }\n static void delete_proc_clients(void) {}\n #endif\n \n+static struct workqueue_struct *ksmbd_conn_wq;\n+\n+int ksmbd_conn_wq_init(void)\n+{\n+\tksmbd_conn_wq = alloc_workqueue(\"ksmbd-conn-release\",\n+\t\t\t\t\tWQ_UNBOUND | WQ_MEM_RECLAIM, 0);\n+\tif (!ksmbd_conn_wq)\n+\t\treturn -ENOMEM;\n+\treturn 0;\n+}\n+\n+void ksmbd_conn_wq_destroy(void)\n+{\n+\tif (ksmbd_conn_wq) {\n+\t\tdestroy_workqueue(ksmbd_conn_wq);\n+\t\tksmbd_conn_wq = NULL;\n+\t}\n+}\n+\n+/*\n+ * __ksmbd_conn_release_work() - perform the final, once-per-struct cleanup\n+ * of a ksmbd_conn whose refcount has just dropped to zero.\n+ *\n+ * This is the common release path used by ksmbd_conn_put() for the embedded\n+ * state that outlives the connection thread: async_ida and the attached\n+ * transport (which owns the socket and iov for TCP).  Called from a workqueue\n+ * so that sleep-allowed teardown (sock_release -> tcp_close ->\n+ * lock_sock_nested) never runs from an RCU softirq callback (free_opinfo_rcu)\n+ * or any other non-sleeping putter context.\n+ */\n+static void __ksmbd_conn_release_work(struct work_struct *work)\n+{\n+\tstruct ksmbd_conn *conn =\n+\t\tcontainer_of(work, struct ksmbd_conn, release_work);\n+\n+\tida_destroy(&conn->async_ida);\n+\tconn->transport->ops->free_transport(conn->transport);\n+\tkfree(conn);\n+}\n+\n+/**\n+ * ksmbd_conn_get() - take a reference on @conn and return it.\n+ *\n+ * Returns @conn unchanged so callers can write\n+ * \"fp->conn = ksmbd_conn_get(work->conn);\" in one expression.  Returns NULL\n+ * if @conn is NULL.\n+ */\n+struct ksmbd_conn *ksmbd_conn_get(struct ksmbd_conn *conn)\n+{\n+\tif (!conn)\n+\t\treturn NULL;\n+\n+\tatomic_inc(&conn->refcnt);\n+\treturn conn;\n+}\n+\n+/**\n+ * ksmbd_conn_put() - drop a reference and, if it was the last, queue the\n+ * release onto ksmbd_conn_wq so it runs from process context.\n+ *\n+ * Callable from any context including RCU softirq callbacks and non-sleeping\n+ * locks; the actual release is deferred to the workqueue.  ksmbd_conn_wq is\n+ * created in ksmbd_server_init() before any conn can be allocated and is\n+ * destroyed in ksmbd_server_exit() after rcu_barrier(), so it is always\n+ * non-NULL while a conn reference is held.\n+ */\n+void ksmbd_conn_put(struct ksmbd_conn *conn)\n+{\n+\tif (!conn)\n+\t\treturn;\n+\n+\tif (atomic_dec_and_test(&conn->refcnt))\n+\t\tqueue_work(ksmbd_conn_wq, &conn->release_work);\n+}\n+\n /**\n  * ksmbd_conn_free() - free resources of the connection instance\n  *\n@@ -93,23 +168,19 @@ void ksmbd_conn_free(struct ksmbd_conn *conn)\n \thash_del(&conn->hlist);\n \tup_write(&conn_list_lock);\n \n+\t/*\n+\t * request_buf / preauth_info / mechToken are only ever accessed by the\n+\t * connection handler thread that owns @conn.  ksmbd_conn_free() is\n+\t * called from the transport free_transport() path when that thread is\n+\t * exiting, so it is safe to release them unconditionally even when\n+\t * ksmbd_conn_put() below is not the final putter (oplock / ksmbd_file\n+\t * holders only retain the conn pointer, not these per-thread buffers).\n+\t */\n \txa_destroy(&conn->sessions);\n \tkvfree(conn->request_buf);\n \tkfree(conn->preauth_info);\n \tkfree(conn->mechToken);\n-\tif (atomic_dec_and_test(&conn->refcnt)) {\n-\t\t/*\n-\t\t * async_ida is embedded in struct ksmbd_conn, so pair\n-\t\t * ida_destroy() with the final kfree() rather than with\n-\t\t * the unconditional field teardown above.  This keeps\n-\t\t * the IDA valid for the entire lifetime of the struct,\n-\t\t * even while other refcount holders (oplock / vfs\n-\t\t * durable handles) still reference the connection.\n-\t\t */\n-\t\tida_destroy(&conn->async_ida);\n-\t\tconn->transport->ops->free_transport(conn->transport);\n-\t\tkfree(conn);\n-\t}\n+\tksmbd_conn_put(conn);\n }\n \n /**\n@@ -136,6 +207,7 @@ struct ksmbd_conn *ksmbd_conn_alloc(void)\n \t\tconn->um = ERR_PTR(-EOPNOTSUPP);\n \tif (IS_ERR(conn->um))\n \t\tconn->um = NULL;\n+\tINIT_WORK(&conn->release_work, __ksmbd_conn_release_work);\n \tatomic_set(&conn->req_running, 0);\n \tatomic_set(&conn->r_count, 0);\n \tatomic_set(&conn->refcnt, 1);\n@@ -512,8 +584,7 @@ void ksmbd_conn_r_count_dec(struct ksmbd_conn *conn)\n \tif (!atomic_dec_return(&conn->r_count) && waitqueue_active(&conn->r_count_q))\n \t\twake_up(&conn->r_count_q);\n \n-\tif (atomic_dec_and_test(&conn->refcnt))\n-\t\tkfree(conn);\n+\tksmbd_conn_put(conn);\n }\n \n int ksmbd_conn_transport_init(void)\ndiff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h\nindex ae21a1bd4c70..e95cb4ed0b3f 100644\n--- a/fs/smb/server/connection.h\n+++ b/fs/smb/server/connection.h\n@@ -16,6 +16,7 @@\n #include <linux/kthread.h>\n #include <linux/nls.h>\n #include <linux/unicode.h>\n+#include <linux/workqueue.h>\n \n #include \"smb_common.h\"\n #include \"ksmbd_work.h\"\n@@ -119,6 +120,7 @@ struct ksmbd_conn {\n \tbool\t\t\t\tbinding;\n \tatomic_t\t\t\trefcnt;\n \tbool\t\t\t\tis_aapl;\n+\tstruct work_struct\t\trelease_work;\n };\n \n struct ksmbd_conn_ops {\n@@ -163,6 +165,10 @@ void ksmbd_conn_wait_idle(struct ksmbd_conn *conn);\n int ksmbd_conn_wait_idle_sess_id(struct ksmbd_conn *curr_conn, u64 sess_id);\n struct ksmbd_conn *ksmbd_conn_alloc(void);\n void ksmbd_conn_free(struct ksmbd_conn *conn);\n+struct ksmbd_conn *ksmbd_conn_get(struct ksmbd_conn *conn);\n+void ksmbd_conn_put(struct ksmbd_conn *conn);\n+int ksmbd_conn_wq_init(void);\n+void ksmbd_conn_wq_destroy(void);\n bool ksmbd_conn_lookup_dialect(struct ksmbd_conn *c);\n int ksmbd_conn_write(struct ksmbd_work *work);\n int ksmbd_conn_rdma_read(struct ksmbd_conn *conn,\ndiff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c\nindex cd3f28b0e7cb..8feca02ddbf2 100644\n--- a/fs/smb/server/oplock.c\n+++ b/fs/smb/server/oplock.c\n@@ -30,7 +30,6 @@ static DEFINE_RWLOCK(lease_list_lock);\n static struct oplock_info *alloc_opinfo(struct ksmbd_work *work,\n \t\t\t\t\tu64 id, __u16 Tid)\n {\n-\tstruct ksmbd_conn *conn = work->conn;\n \tstruct ksmbd_session *sess = work->sess;\n \tstruct oplock_info *opinfo;\n \n@@ -39,7 +38,7 @@ static struct oplock_info *alloc_opinfo(struct ksmbd_work *work,\n \t\treturn NULL;\n \n \topinfo->sess = sess;\n-\topinfo->conn = conn;\n+\topinfo->conn = ksmbd_conn_get(work->conn);\n \topinfo->level = SMB2_OPLOCK_LEVEL_NONE;\n \topinfo->op_state = OPLOCK_STATE_NONE;\n \topinfo->pending_break = 0;\n@@ -50,7 +49,6 @@ static struct oplock_info *alloc_opinfo(struct ksmbd_work *work,\n \tinit_waitqueue_head(&opinfo->oplock_brk);\n \tatomic_set(&opinfo->refcount, 1);\n \tatomic_set(&opinfo->breaking_cnt, 0);\n-\tatomic_inc(&opinfo->conn->refcnt);\n \n \treturn opinfo;\n }\n@@ -132,8 +130,7 @@ static void __free_opinfo(struct oplock_info *opinfo)\n {\n \tif (opinfo->is_lease)\n \t\tfree_lease(opinfo);\n-\tif (opinfo->conn && atomic_dec_and_test(&opinfo->conn->refcnt))\n-\t\tkfree(opinfo->conn);\n+\tksmbd_conn_put(opinfo->conn);\n \tkfree(opinfo);\n }\n \ndiff --git a/fs/smb/server/server.c b/fs/smb/server/server.c\nindex 58ef02c423fc..5d799b2d4c62 100644\n--- a/fs/smb/server/server.c\n+++ b/fs/smb/server/server.c\n@@ -596,8 +596,14 @@ static int __init ksmbd_server_init(void)\n \tif (ret)\n \t\tgoto err_crypto_destroy;\n \n+\tret = ksmbd_conn_wq_init();\n+\tif (ret)\n+\t\tgoto err_workqueue_destroy;\n+\n \treturn 0;\n \n+err_workqueue_destroy:\n+\tksmbd_workqueue_destroy();\n err_crypto_destroy:\n \tksmbd_crypto_destroy();\n err_release_inode_hash:\n@@ -623,6 +629,12 @@ static void __exit ksmbd_server_exit(void)\n {\n \tksmbd_server_shutdown();\n \trcu_barrier();\n+\t/*\n+\t * ksmbd_conn_put() defers the final release onto ksmbd_conn_wq,\n+\t * so drain it after rcu_barrier() has fired any pending RCU\n+\t * callbacks that may have queued a release.\n+\t */\n+\tksmbd_conn_wq_destroy();\n \tksmbd_release_inode_hash();\n }\n \ndiff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c\nindex 3551f01a3fa0..4a18107937cc 100644\n--- a/fs/smb/server/vfs_cache.c\n+++ b/fs/smb/server/vfs_cache.c\n@@ -475,6 +475,17 @@ static void __ksmbd_close_fd(struct ksmbd_file_table *ft, struct ksmbd_file *fp)\n \t\tkfree(smb_lock);\n \t}\n \n+\t/*\n+\t * Drop fp's strong reference on conn (taken in ksmbd_open_fd() /\n+\t * ksmbd_reopen_durable_fd()).  Durable fps that reached the\n+\t * scavenger have already had fp->conn cleared by session_fd_check(),\n+\t * in which case there is nothing to drop here.\n+\t */\n+\tif (fp->conn) {\n+\t\tksmbd_conn_put(fp->conn);\n+\t\tfp->conn = NULL;\n+\t}\n+\n \tif (ksmbd_stream_fd(fp))\n \t\tkfree(fp->stream.name);\n \tkfree(fp->owner.name);\n@@ -752,7 +763,14 @@ struct ksmbd_file *ksmbd_open_fd(struct ksmbd_work *work, struct file *filp)\n \tatomic_set(&fp->refcount, 1);\n \n \tfp->filp\t\t= filp;\n-\tfp->conn\t\t= work->conn;\n+\t/*\n+\t * fp owns a strong reference on fp->conn for as long as fp->conn is\n+\t * non-NULL, so session_fd_check() and __ksmbd_close_fd() never\n+\t * dereference a dangling pointer.  Paired with ksmbd_conn_put() in\n+\t * session_fd_check() (durable preserve), in __ksmbd_close_fd()\n+\t * (final close), and on the error paths below.\n+\t */\n+\tfp->conn\t\t= ksmbd_conn_get(work->conn);\n \tfp->tcon\t\t= work->tcon;\n \tfp->volatile_id\t\t= KSMBD_NO_FID;\n \tfp->persistent_id\t= KSMBD_NO_FID;\n@@ -774,6 +792,8 @@ struct ksmbd_file *ksmbd_open_fd(struct ksmbd_work *work, struct file *filp)\n \treturn fp;\n \n err_out:\n+\t/* fp->conn was set and refcounted before every branch here. */\n+\tksmbd_conn_put(fp->conn);\n \tkmem_cache_free(filp_cache, fp);\n \treturn ERR_PTR(ret);\n }\n@@ -1062,25 +1082,32 @@ static bool session_fd_check(struct ksmbd_tree_connect *tcon,\n \tif (!is_reconnectable(fp))\n \t\treturn false;\n \n+\tif (WARN_ON_ONCE(!fp->conn))\n+\t\treturn false;\n+\n \tif (ksmbd_vfs_copy_durable_owner(fp, user))\n \t\treturn false;\n \n+\t/*\n+\t * fp owns a strong reference on fp->conn (taken in ksmbd_open_fd()\n+\t * / ksmbd_reopen_durable_fd()), so conn stays valid for the whole\n+\t * body of this function regardless of any op->conn puts below.\n+\t */\n \tconn = fp->conn;\n \tci = fp->f_ci;\n \tdown_write(&ci->m_lock);\n \tlist_for_each_entry_rcu(op, &ci->m_op_list, op_entry) {\n \t\tif (op->conn != conn)\n \t\t\tcontinue;\n-\t\tif (op->conn && atomic_dec_and_test(&op->conn->refcnt))\n-\t\t\tkfree(op->conn);\n+\t\tksmbd_conn_put(op->conn);\n \t\top->conn = NULL;\n \t}\n \tup_write(&ci->m_lock);\n \n \tlist_for_each_entry_safe(smb_lock, tmp_lock, &fp->lock_list, flist) {\n-\t\tspin_lock(&fp->conn->llist_lock);\n+\t\tspin_lock(&conn->llist_lock);\n \t\tlist_del_init(&smb_lock->clist);\n-\t\tspin_unlock(&fp->conn->llist_lock);\n+\t\tspin_unlock(&conn->llist_lock);\n \t}\n \n \tfp->conn = NULL;\n@@ -1091,6 +1118,8 @@ static bool session_fd_check(struct ksmbd_tree_connect *tcon,\n \t\tfp->durable_scavenger_timeout =\n \t\t\tjiffies_to_msecs(jiffies) + fp->durable_timeout;\n \n+\t/* Drop fp's own reference on conn. */\n+\tksmbd_conn_put(conn);\n \treturn true;\n }\n \n@@ -1178,15 +1207,27 @@ int ksmbd_reopen_durable_fd(struct ksmbd_work *work, struct ksmbd_file *fp)\n \n \told_f_state = fp->f_state;\n \tfp->f_state = FP_NEW;\n+\n+\t/*\n+\t * Initialize fp's connection binding before publishing fp into the\n+\t * session's file table.  If __open_id() is ordered first, a\n+\t * concurrent teardown that iterates the table can observe a valid\n+\t * volatile_id with fp->conn == NULL and preserve a\n+\t * partially-initialized fp.  fp owns a strong reference on the new\n+\t * conn (see ksmbd_open_fd()); undo it on __open_id() failure.\n+\t */\n+\tfp->conn = ksmbd_conn_get(conn);\n+\tfp->tcon = work->tcon;\n+\n \t__open_id(&work->sess->file_table, fp, OPEN_ID_TYPE_VOLATILE_ID);\n \tif (!has_file_id(fp->volatile_id)) {\n+\t\tfp->conn = NULL;\n+\t\tfp->tcon = NULL;\n+\t\tksmbd_conn_put(conn);\n \t\tfp->f_state = old_f_state;\n \t\treturn -EBADF;\n \t}\n \n-\tfp->conn = conn;\n-\tfp->tcon = work->tcon;\n-\n \tlist_for_each_entry(smb_lock, &fp->lock_list, flist) {\n \t\tspin_lock(&conn->llist_lock);\n \t\tlist_add_tail(&smb_lock->clist, &conn->lock_list);\n@@ -1198,8 +1239,7 @@ int ksmbd_reopen_durable_fd(struct ksmbd_work *work, struct ksmbd_file *fp)\n \tlist_for_each_entry_rcu(op, &ci->m_op_list, op_entry) {\n \t\tif (op->conn)\n \t\t\tcontinue;\n-\t\top->conn = fp->conn;\n-\t\tatomic_inc(&op->conn->refcnt);\n+\t\top->conn = ksmbd_conn_get(fp->conn);\n \t}\n \tup_write(&ci->m_lock);\n \n",
    "prefixes": [
        "v2",
        "1/3"
    ]
}