Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.1/patches/2229606/?format=api
{ "id": 2229606, "url": "http://patchwork.ozlabs.org/api/1.1/patches/2229606/?format=api", "web_url": "http://patchwork.ozlabs.org/project/linux-cifs-client/patch/20260428140856.941847-3-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-3-charsyam@gmail.com>", "date": "2026-04-28T14:08:55", "name": "[v2,2/3] ksmbd: harden file lifetime during session teardown", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "dd75871a9feaaf5d70ab2d84d8bb0ffbc571e10f", "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-3-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/2229606/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2229606/checks/", "tags": {}, "headers": { "Return-Path": "\n <linux-cifs+bounces-11218-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=jFUrSbDg;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org\n (client-ip=172.232.135.74; helo=sto.lore.kernel.org;\n envelope-from=linux-cifs+bounces-11218-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=\"jFUrSbDg\"", "smtp.subspace.kernel.org;\n arc=none smtp.client-ip=209.85.210.175", "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 sto.lore.kernel.org (sto.lore.kernel.org [172.232.135.74])\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 4g4j6D59bXz1xvV\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 29 Apr 2026 00:11:32 +1000 (AEST)", "from smtp.subspace.kernel.org (conduit.subspace.kernel.org\n [100.90.174.1])\n\tby sto.lore.kernel.org (Postfix) with ESMTP id D75113035B4C\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 28 Apr 2026 14:09:26 +0000 (UTC)", "from localhost.localdomain (localhost.localdomain [127.0.0.1])\n\tby smtp.subspace.kernel.org (Postfix) with ESMTP id 8BB9E449EAB;\n\tTue, 28 Apr 2026 14:09:09 +0000 (UTC)", "from mail-pf1-f175.google.com (mail-pf1-f175.google.com\n [209.85.210.175])\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 C879E441036\n\tfor <linux-cifs@vger.kernel.org>; Tue, 28 Apr 2026 14:09:07 +0000 (UTC)", "by mail-pf1-f175.google.com with SMTP id\n d2e1a72fcca58-823be54d49cso856970b3a.3\n for <linux-cifs@vger.kernel.org>;\n Tue, 28 Apr 2026 07:09:07 -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.05\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Tue, 28 Apr 2026 07:09:06 -0700 (PDT)" ], "ARC-Seal": "i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;\n\tt=1777385349; cv=none;\n b=iSOLd1PIIGAHpOugIRm5w7NJdjv4fqbbeh9ndxg9LQL/cil1citeKsa2dq6NzMm4WlLAIuFDETjXpAliWxkojQdch6TH2qqQoIXBo4F8NLQ/JzHSHhIWkVyYILHv+e78Xrmdex1bAMAG/JYEsxLLvWu+etjIhC0xczQ45uwOoJ8=", "ARC-Message-Signature": "i=1; a=rsa-sha256; d=subspace.kernel.org;\n\ts=arc-20240116; t=1777385349; c=relaxed/simple;\n\tbh=7Y75CJc2/d6aHB2cQnB6UNXLLb91D24toWSFpWWyhQc=;\n\th=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:\n\t MIME-Version;\n b=d+g6XroKHYA4ukS1FkIK5sKtE8EqAF0UM4eUEm3ASDHt2MTCCG113xne9O8wKgqAwSPR0zo7sUxelf1flq9iZErAGqeBjdeiQL+aHVeRu48DIT0bocqrRErKgi3T4Mg1xOCa4KeBbsid3WkeTZhFXqR96q0qbo2nc16wEUHvdfs=", "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=jFUrSbDg; arc=none smtp.client-ip=209.85.210.175", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=gmail.com; s=20251104; t=1777385347; x=1777990147;\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=xlWHHqWJasISzgKRTEELiwHwHgmZ46NRjOQ3vo3IPy4=;\n b=jFUrSbDg/4oC2PkH4sl2GhXb7QY8/XCc0gAnSxpwC1ofvASx9wclP7c9dbcJ1w1goR\n zle6Uj5MGCAdyJZgOp+RUjObCE0EcJ6VHzHNhUbjw3nVnsA1UVVhiYlmVdhveF2G5bon\n cVVj8he/FVRD58nl+xOaGKJg/niRl7gsNpxaxH+g1nY8uxcqJ12l+118jIc/qZQC0sTG\n 3KBXWZOEl1HAj7akx3uXT8+lxUrfOXP9Uq7KfQ7s9Ou6z1g0n+uGjoi3FynXCTiL1+oF\n gw8SYTxM5a4BwCtgRPg8FPjiqPQHGYQng79zzJlWyWC+cQ3OmCOpEp7yiTi/wOkrPZEI\n +iHA==", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1777385347; x=1777990147;\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=xlWHHqWJasISzgKRTEELiwHwHgmZ46NRjOQ3vo3IPy4=;\n b=S6ZxOTjZ8ALnoTeSbfeB5S75+6r7C/tRVU0Ycp1S/uD/oIGVY2DWjLUbmuVZjr7AmO\n Frb6TkMXEtSeBX2oTg7cgmvkDeWtLeSfWyWIsu9YbeBPpVjgnioyjeMg/wy/Nk1v2bX9\n uOFvHTKUIDpc8A7/jn7HiYQ2+L+LOj18S30TWUyTNYMaL8+UnZzVytLfkfvFf0oXYG3f\n CNNT/PSb/HPPRYhIi7kRlBf5HMKCjNc0nOQ14WHWsH/KMERq1tBuNqQkqezVjSyNV45R\n gTGdD8+kVeiGL0GGuymBlzEmmKJf1SgBVK+KMIJcPpMfA8+MUfmq+77vstlsBMU3KE8x\n P+Zg==", "X-Forwarded-Encrypted": "i=1;\n AFNElJ+/2WRnPUguH0Rgovie9nbgSsjLhQ+EG/9GHnlmNAS6cKp2Jwlu5+jpaLi6Qv8hfOB64zfw5fCQEtkE@vger.kernel.org", "X-Gm-Message-State": "AOJu0YyIWKDMunLTPJHJmRMCGrn0YiN8nXrWJf0jSpT7wT10qHLLH0O9\n\tweye1/v3coPw4y3ATcHUKUteQh8F1wAgZv9EpPBt/kZC2E14XSsDJaHD", "X-Gm-Gg": "AeBDievZHEWYYj3nizbExvvyjoABNjX8t1pAL1HtkFDYn/uOR9N5cR5KJ11x85n2Uun\n\tW2gJWDnLT9mJfOeFo5V9KSc4RhvVWkluGpAIKQRXmYRa2GfYv8vvcYijbcn+1I4TJqtHuLk5Js5\n\tfL8b0rX1S8tIf4wBGm0rhISfumkBxJit1YcesIQDI46325pdWyFkeSKCbIvMO5qbb2enle4uQYC\n\tTmEoW29zAb2FKNG+qlN/qbnsghTSyhr9aMag1fiFlGpVKm8b2WpfvPdD36VVz8fyx44Y0RaJOrY\n\tXJwH3zgrp4AUQngomI4GdlBStpCbybW/n9iXhQwOJlppucc7S1pOV1+ewgaJejwFXABf5R0ry0i\n\tY8V63BAwenTDDcwtsEkJ+GLXU32mP06GxWH9XtSVG9p1mWmw4aE99n+WUivP4UnyUOMJsuhUjFn\n\tEkAEsRw0KuUMsXJpm5UIXNa5MgVrE=", "X-Received": "by 2002:a05:6a21:10e:b0:3a2:c72c:745d with SMTP id\n adf61e73a8af0-3a398db06e1mr2710162637.4.1777385346798;\n Tue, 28 Apr 2026 07:09:06 -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 2/3] ksmbd: harden file lifetime during session teardown", "Date": "Tue, 28 Apr 2026 23:08:55 +0900", "Message-ID": "<20260428140856.941847-3-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": "__close_file_table_ids() is the per-session teardown that closes every\nfp belonging to a session (or to one tree connect on that session) by\nwalking the session's volatile-id idr. The current loop has three\nrelated problems on busy or racing workloads:\n\n * Sleeping under ft->lock. The session-teardown skip callback,\n session_fd_check(), already sleeps in ksmbd_vfs_copy_durable_owner()\n -> kstrdup(GFP_KERNEL) and down_write(&fp->f_ci->m_lock) (a\n rw_semaphore). Running the callback inside write_lock(&ft->lock)\n trips CONFIG_DEBUG_ATOMIC_SLEEP / CONFIG_PROVE_LOCKING on a\n durable-fd workload.\n\n * Refcount accounting blind to f_state. The unconditional\n atomic_dec_and_test(&fp->refcount) does not distinguish\n FP_INITED (idr-owned reference still intact) from FP_CLOSED (an\n earlier ksmbd_close_fd() already consumed the idr-owned reference\n while leaving fp in the idr because a holder kept refcount\n non-zero). When the latter races with teardown the same path\n over-decrements into a holder reference and ksmbd_fd_put() later\n UAFs that holder.\n\n * FP_NEW window. Between __open_id() publishing fp into the\n session idr and ksmbd_update_fstate(..., FP_INITED) committing the\n transition at the end of smb2_open(), an fp is in FP_NEW and an\n intervening teardown that takes a transient reference and\n unpublishes the volatile id leaves the original idr-owned\n reference orphaned -- the opener is unaware that fp has been\n unpublished, returns success to the client, and the fp leaks at\n refcount = 1.\n\nRefactor __close_file_table_ids() to take a transient reference on fp\nand unpublish fp from the session idr *under ft->lock* before calling\nskip() outside the lock. A transient ref protects lifetime but not\nconcurrent field mutation, so the idr_remove() is what keeps\n__ksmbd_lookup_fd() through this session's idr from granting a new\nksmbd_fp_get() reference to an fp whose fp->conn / fp->tcon /\nfp->volatile_id / op->conn / lock_list links are about to be rewritten\nby session_fd_check(). Durable reconnect is unaffected because it\nreaches fp through the global durable table (ksmbd_lookup_durable_fd\n-> global_ft).\n\nDecide n_to_drop together with any FP_INITED -> FP_CLOSED transition\nunder ft->lock so teardown and ksmbd_close_fd() never both consume the\nidr-owned reference. See ksmbd_mark_fp_closed() for the per-state\naccounting. For the FP_NEW path to be safe, the opener has to learn\nthat fp was unpublished: ksmbd_update_fstate() now returns -ENOENT\nwhen an FP_NEW -> FP_INITED transition finds f_state already advanced\nor the volatile id cleared (both committed by teardown under\nft->lock); smb2_open() propagates that as STATUS_OBJECT_NAME_INVALID\nand drops the original reference via ksmbd_fd_put().\n\nThe list removal cannot be left for a deferred final putter because\nfp->volatile_id has already been cleared and __ksmbd_remove_fd() will\nintentionally skip both idr_remove() and list_del_init(). Move the\nm_fp_list unlink in __ksmbd_remove_fd() above the volatile-id check so\nthat an FP_NEW fp that happened to be added to m_fp_list (smb2_open()\nadds fp->node before ksmbd_update_fstate() runs) is still cleaned up\non the deferred putter path; list_del_init() on an empty node is a\nno-op and remains safe for fps that were never added.\n\nAdd a defensive guard in session_fd_check() that refuses non-FP_INITED\nfps so that even if a teardown reaches an FP_NEW fp it falls into the\nclose branch (where the n_to_drop = 1 accounting keeps the opener's\nreference alive) instead of the durable-preserve branch (which mutates\nfp->conn / fp->tcon).\n\nValidation on a debug kernel additionally built with CONFIG_DEBUG_LIST\nand CONFIG_DEBUG_OBJECTS_WORK used a same-session two-tcon workload\n(open/write storm on one tcon, 50 tree disconnects on the other) and\nreported no list-corruption, work_struct ODEBUG, sleep-in-atomic,\nlockdep or kmemleak reports. Reverting only the\n__close_file_table_ids() hunk while keeping a forced-is_reconnectable()\nharness produced the expected sleep-in-atomic at vfs_cache.c:1095,\nconfirming the ft->lock-out-of-sleepable-skip discipline.\n\nKASAN-enabled direct SMB2 coverage with durable handles enabled\nexercised ksmbd_close_tree_conn_fds(), ksmbd_close_session_fds(),\nthe FP_NEW failure path, tree_conn_fd_check(), and a non-zero\nsession_fd_check() durable-preserve return. This produced no KASAN,\nDEBUG_LIST, ODEBUG, or WARNING reports.\n\nFixes: f44158485826 (\"cifsd: add file operations\")\nSigned-off-by: DaeMyung Kang <charsyam@gmail.com>\n---\n fs/smb/server/smb2pdu.c | 6 +-\n fs/smb/server/vfs_cache.c | 179 +++++++++++++++++++++++++++++++++-----\n fs/smb/server/vfs_cache.h | 4 +-\n 3 files changed, 164 insertions(+), 25 deletions(-)", "diff": "diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c\nindex 21825a69c29a..c1240ca14253 100644\n--- a/fs/smb/server/smb2pdu.c\n+++ b/fs/smb/server/smb2pdu.c\n@@ -3767,8 +3767,10 @@ int smb2_open(struct ksmbd_work *work)\n \n err_out2:\n \tif (!rc) {\n-\t\tksmbd_update_fstate(&work->sess->file_table, fp, FP_INITED);\n-\t\trc = ksmbd_iov_pin_rsp(work, (void *)rsp, iov_len);\n+\t\trc = ksmbd_update_fstate(&work->sess->file_table, fp,\n+\t\t\t\t\t FP_INITED);\n+\t\tif (!rc)\n+\t\t\trc = ksmbd_iov_pin_rsp(work, (void *)rsp, iov_len);\n \t}\n \tif (rc) {\n \t\tif (rc == -EINVAL)\ndiff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c\nindex 4a18107937cc..dc4037ef1834 100644\n--- a/fs/smb/server/vfs_cache.c\n+++ b/fs/smb/server/vfs_cache.c\n@@ -431,13 +431,13 @@ static void ksmbd_remove_durable_fd(struct ksmbd_file *fp)\n \n static void __ksmbd_remove_fd(struct ksmbd_file_table *ft, struct ksmbd_file *fp)\n {\n-\tif (!has_file_id(fp->volatile_id))\n-\t\treturn;\n-\n \tdown_write(&fp->f_ci->m_lock);\n \tlist_del_init(&fp->node);\n \tup_write(&fp->f_ci->m_lock);\n \n+\tif (!has_file_id(fp->volatile_id))\n+\t\treturn;\n+\n \twrite_lock(&ft->lock);\n \tidr_remove(ft->idr, fp->volatile_id);\n \twrite_unlock(&ft->lock);\n@@ -798,15 +798,58 @@ struct ksmbd_file *ksmbd_open_fd(struct ksmbd_work *work, struct file *filp)\n \treturn ERR_PTR(ret);\n }\n \n-void ksmbd_update_fstate(struct ksmbd_file_table *ft, struct ksmbd_file *fp,\n-\t\t\t unsigned int state)\n+/**\n+ * ksmbd_update_fstate() - update an fp state under the file-table lock\n+ * @ft: file table that publishes @fp's volatile id\n+ * @fp: file pointer to update\n+ * @state: new state\n+ *\n+ * Return: 0 on success. The FP_NEW -> FP_INITED transition is special:\n+ * -ENOENT if teardown already unpublished @fp by advancing the state or\n+ * clearing the volatile id. Other state updates preserve the historical\n+ * fire-and-forget behavior.\n+ */\n+int ksmbd_update_fstate(struct ksmbd_file_table *ft, struct ksmbd_file *fp,\n+\t\t\tunsigned int state)\n {\n+\tint ret;\n+\n \tif (!fp)\n-\t\treturn;\n+\t\treturn -ENOENT;\n \n \twrite_lock(&ft->lock);\n-\tfp->f_state = state;\n+\tif (state == FP_INITED &&\n+\t (fp->f_state != FP_NEW || !has_file_id(fp->volatile_id))) {\n+\t\tret = -ENOENT;\n+\t} else {\n+\t\tfp->f_state = state;\n+\t\tret = 0;\n+\t}\n \twrite_unlock(&ft->lock);\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * ksmbd_mark_fp_closed() - mark fp closed under ft->lock and return how many\n+ * refs the teardown path owns.\n+ *\n+ * FP_INITED has a normal idr-owned reference, so teardown owns both that\n+ * reference and the transient lookup reference. FP_NEW is still owned by the\n+ * in-flight opener/reopener, which will drop the original reference after\n+ * ksmbd_update_fstate(..., FP_INITED) observes the cleared volatile id.\n+ * FP_CLOSED on entry means an earlier ksmbd_close_fd() already consumed the\n+ * idr-owned ref.\n+ */\n+static int ksmbd_mark_fp_closed(struct ksmbd_file *fp)\n+{\n+\tif (fp->f_state == FP_INITED) {\n+\t\tset_close_state_blocked_works(fp);\n+\t\tfp->f_state = FP_CLOSED;\n+\t\treturn 2;\n+\t}\n+\n+\treturn 1;\n }\n \n static int\n@@ -814,7 +857,8 @@ __close_file_table_ids(struct ksmbd_session *sess,\n \t\t struct ksmbd_tree_connect *tcon,\n \t\t bool (*skip)(struct ksmbd_tree_connect *tcon,\n \t\t\t\t struct ksmbd_file *fp,\n-\t\t\t\t struct ksmbd_user *user))\n+\t\t\t\t struct ksmbd_user *user),\n+\t\t bool skip_preserves_fp)\n {\n \tstruct ksmbd_file_table *ft = &sess->file_table;\n \tstruct ksmbd_file *fp;\n@@ -822,32 +866,120 @@ __close_file_table_ids(struct ksmbd_session *sess,\n \tint num = 0;\n \n \twhile (1) {\n+\t\tint n_to_drop;\n+\n \t\twrite_lock(&ft->lock);\n \t\tfp = idr_get_next(ft->idr, &id);\n \t\tif (!fp) {\n \t\t\twrite_unlock(&ft->lock);\n \t\t\tbreak;\n \t\t}\n-\n-\t\tif (skip(tcon, fp, sess->user) ||\n-\t\t !atomic_dec_and_test(&fp->refcount)) {\n+\t\tif (!atomic_inc_not_zero(&fp->refcount)) {\n \t\t\tid++;\n \t\t\twrite_unlock(&ft->lock);\n \t\t\tcontinue;\n \t\t}\n \n-\t\tset_close_state_blocked_works(fp);\n-\t\tidr_remove(ft->idr, fp->volatile_id);\n-\t\tfp->volatile_id = KSMBD_NO_FID;\n-\t\twrite_unlock(&ft->lock);\n+\t\tif (skip_preserves_fp) {\n+\t\t\t/*\n+\t\t\t * Session teardown: skip() is session_fd_check(),\n+\t\t\t * which may sleep and mutates fp->conn / fp->tcon /\n+\t\t\t * fp->volatile_id when it chooses to preserve fp\n+\t\t\t * for durable reconnect. Unpublish fp from the\n+\t\t\t * session idr here, under ft->lock, so that\n+\t\t\t * __ksmbd_lookup_fd() through this session cannot\n+\t\t\t * grant a new ksmbd_fp_get() reference to an fp\n+\t\t\t * whose fields are about to be rewritten outside\n+\t\t\t * the lock. Durable reconnect still reaches fp via\n+\t\t\t * global_ft.\n+\t\t\t */\n+\t\t\tidr_remove(ft->idr, id);\n+\t\t\tfp->volatile_id = KSMBD_NO_FID;\n+\t\t\twrite_unlock(&ft->lock);\n+\n+\t\t\tif (skip(tcon, fp, sess->user)) {\n+\t\t\t\t/*\n+\t\t\t\t * session_fd_check() has converted fp to\n+\t\t\t\t * durable-preserve state and cleared its\n+\t\t\t\t * per-conn fields. fp is already unpublished\n+\t\t\t\t * above; the original idr-owned ref keeps it\n+\t\t\t\t * alive for the durable scavenger. Drop only\n+\t\t\t\t * the transient ref. atomic_dec() is safe --\n+\t\t\t\t * atomic_inc_not_zero() succeeded on a\n+\t\t\t\t * positive value and we added one more, so\n+\t\t\t\t * refcount cannot be zero here.\n+\t\t\t\t */\n+\t\t\t\tatomic_dec(&fp->refcount);\n+\t\t\t\tid++;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\t/*\n+\t\t\t * Keep the close-state decision under the same lock\n+\t\t\t * observed by ksmbd_update_fstate(), which is how an\n+\t\t\t * in-flight FP_NEW opener learns that teardown has\n+\t\t\t * cleared its volatile id.\n+\t\t\t */\n+\t\t\twrite_lock(&ft->lock);\n+\t\t\tn_to_drop = ksmbd_mark_fp_closed(fp);\n+\t\t\twrite_unlock(&ft->lock);\n+\t\t} else {\n+\t\t\t/*\n+\t\t\t * Tree teardown: skip() is tree_conn_fd_check(), a\n+\t\t\t * cheap pointer compare that doesn't sleep and has\n+\t\t\t * no side effects, so keep the skip decision plus\n+\t\t\t * the unpublish-and-mark-closed sequence atomic\n+\t\t\t * under ft->lock. fps belonging to other tree\n+\t\t\t * connects (skip() == true) stay fully published in\n+\t\t\t * the session idr with no lock window.\n+\t\t\t */\n+\t\t\tif (skip(tcon, fp, sess->user)) {\n+\t\t\t\tatomic_dec(&fp->refcount);\n+\t\t\t\twrite_unlock(&ft->lock);\n+\t\t\t\tid++;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tidr_remove(ft->idr, id);\n+\t\t\tfp->volatile_id = KSMBD_NO_FID;\n+\t\t\tn_to_drop = ksmbd_mark_fp_closed(fp);\n+\t\t\twrite_unlock(&ft->lock);\n+\t\t}\n \n+\t\t/*\n+\t\t * fp->volatile_id is already cleared to prevent stale idr\n+\t\t * removal from a deferred final close. Remove fp from\n+\t\t * m_fp_list here because __ksmbd_remove_fd() will skip the\n+\t\t * list unlink when volatile_id is KSMBD_NO_FID.\n+\t\t */\n \t\tdown_write(&fp->f_ci->m_lock);\n \t\tlist_del_init(&fp->node);\n \t\tup_write(&fp->f_ci->m_lock);\n \n-\t\t__ksmbd_close_fd(ft, fp);\n-\n-\t\tnum++;\n+\t\t/*\n+\t\t * Drop the references this iteration owns:\n+\t\t *\n+\t\t * n_to_drop == 2: we observed FP_INITED and committed\n+\t\t * the FP_CLOSED transition ourselves, so we own the\n+\t\t * transient (+1) and the still-intact idr-owned ref.\n+\t\t *\n+\t\t * n_to_drop == 1: either a prior ksmbd_close_fd()\n+\t\t * already consumed the idr-owned ref, or fp was still\n+\t\t * FP_NEW and the in-flight opener/reopener must keep\n+\t\t * the original reference until ksmbd_update_fstate()\n+\t\t * observes the cleared volatile id.\n+\t\t *\n+\t\t * If we end up as the final putter, finalize fp and\n+\t\t * account the open_files_count decrement via the caller's\n+\t\t * atomic_sub(num, ...). Otherwise the remaining user's\n+\t\t * ksmbd_fd_put() reaches __put_fd_final(), which does its\n+\t\t * own atomic_dec(&open_files_count), so we must not count\n+\t\t * this fp here -- doing so would double-decrement the\n+\t\t * connection-wide counter.\n+\t\t */\n+\t\tif (atomic_sub_and_test(n_to_drop, &fp->refcount)) {\n+\t\t\t__ksmbd_close_fd(NULL, fp);\n+\t\t\tnum++;\n+\t\t}\n \t\tid++;\n \t}\n \n@@ -1082,6 +1214,9 @@ static bool session_fd_check(struct ksmbd_tree_connect *tcon,\n \tif (!is_reconnectable(fp))\n \t\treturn false;\n \n+\tif (fp->f_state != FP_INITED)\n+\t\treturn false;\n+\n \tif (WARN_ON_ONCE(!fp->conn))\n \t\treturn false;\n \n@@ -1127,7 +1262,8 @@ void ksmbd_close_tree_conn_fds(struct ksmbd_work *work)\n {\n \tint num = __close_file_table_ids(work->sess,\n \t\t\t\t\t work->tcon,\n-\t\t\t\t\t tree_conn_fd_check);\n+\t\t\t\t\t tree_conn_fd_check,\n+\t\t\t\t\t false);\n \n \tatomic_sub(num, &work->conn->stats.open_files_count);\n }\n@@ -1136,7 +1272,8 @@ void ksmbd_close_session_fds(struct ksmbd_work *work)\n {\n \tint num = __close_file_table_ids(work->sess,\n \t\t\t\t\t work->tcon,\n-\t\t\t\t\t session_fd_check);\n+\t\t\t\t\t session_fd_check,\n+\t\t\t\t\t true);\n \n \tatomic_sub(num, &work->conn->stats.open_files_count);\n }\n@@ -1268,7 +1405,7 @@ void ksmbd_destroy_file_table(struct ksmbd_session *sess)\n \tif (!ft->idr)\n \t\treturn;\n \n-\t__close_file_table_ids(sess, NULL, session_fd_check);\n+\t__close_file_table_ids(sess, NULL, session_fd_check, true);\n \tidr_destroy(ft->idr);\n \tkfree(ft->idr);\n \tft->idr = NULL;\ndiff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h\nindex 866f32c10d4d..e6871266a94b 100644\n--- a/fs/smb/server/vfs_cache.h\n+++ b/fs/smb/server/vfs_cache.h\n@@ -172,8 +172,8 @@ int ksmbd_close_inode_fds(struct ksmbd_work *work, struct inode *inode);\n int ksmbd_init_global_file_table(void);\n void ksmbd_free_global_file_table(void);\n void ksmbd_set_fd_limit(unsigned long limit);\n-void ksmbd_update_fstate(struct ksmbd_file_table *ft, struct ksmbd_file *fp,\n-\t\t\t unsigned int state);\n+int ksmbd_update_fstate(struct ksmbd_file_table *ft, struct ksmbd_file *fp,\n+\t\t\tunsigned int state);\n bool ksmbd_vfs_compare_durable_owner(struct ksmbd_file *fp,\n \t\tstruct ksmbd_user *user);\n \n", "prefixes": [ "v2", "2/3" ] }