From nobody Mon May 25 05:54:18 2026 Received: from mail-qk1-f172.google.com (mail-qk1-f172.google.com [209.85.222.172]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D27AD2B9BA for ; Mon, 18 May 2026 00:12:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.222.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779063130; cv=none; b=Tnwn2kY5+ho0G0mlRo2WJckM6UqLhZlSF6pYJZ2z/YdifOl8fAuQDHqoSYaOQ/3obaOFcfdgTVPRW6QLF0hGkQcAO1yBgOyrpat6fl7sEZwe8FGvJyZLDFClqM5g86Suftd0Ae7G2AeZAU/ZGSz+T5Pzs/QrvOPP4DkWI6J+smg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779063130; c=relaxed/simple; bh=u9rKlafB0cbGDe+Y5BVcballPoO8b0BKYN15opAQGJo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=CQxAVYNOjo7AgOLIHA5919mkkzdRzxsct6BPGdVcDk6WeJd75Eft01Y7jfIkjmXs3Mv6bJNvnu98rRZuCZ9tRtvTt+wOJIEri4+6PEbuzhJqLlOqPcgcZng0aFoOVAGH7LHWmGr8gezPHEjyaheyeQSxX2qk5oTO/CW8q2tv51I= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=WFr5W4EI; arc=none smtp.client-ip=209.85.222.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="WFr5W4EI" Received: by mail-qk1-f172.google.com with SMTP id af79cd13be357-910bb291688so224528985a.1 for ; Sun, 17 May 2026 17:12:06 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1779063126; x=1779667926; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=WwGA5GB63IhNwPMjjFH3Te+keCl3mwMoXkCx0/RFc2E=; b=WFr5W4EIjkAHNPVuBKKcnzx2kMpwA+zfth7dGSQAc+svgS6TBaMgXFH4PH0TOxm6zI MPDHhSDYhJQC6iZ53q+jMmVAdsBSw+yeATUtPEl6c8v5+eDyI0Bk6duhIbEEP5vnPRxH wVNXxtlgvSR1OQS9YbbdXMzVv5ApCEDz08dnb4JdFRh5A71Eq0qP/aZLEoV0JM7d/xD8 uLHdlrycvt0EZOhDdpKpRejErl2sMFpSoYE8QjU7sakmqRTMdswdnuRTQqhaq3sLgUEa PLFNQzqjAfe+S5B17GmuEJNcJ7papKaToEXYLbGO0gh+y8UYl8LjJjQfDgzyjplsYanv xOog== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779063126; x=1779667926; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=WwGA5GB63IhNwPMjjFH3Te+keCl3mwMoXkCx0/RFc2E=; b=NCMFiaeDx8eRP+DfghgldQn+yj90LNA/yP4y9u5djlqhJrm3aFMD6YUvCyl5tnVtA1 u/nPaKSUmbDgN3s1tMQBC61EPhHjmYdN5GP41z3g9wedlwGyfn19QbIECI7GQ05+h728 r+PlS2Ms9gGKKODwXiM9Au9w/innVEBKpfzHQvL7zlSeO+jg0YvaL7MmqBUCXpSm6qb9 ynJQRPOW+s4CZzJqnqRU9SySXw9tgI0Q8zxiWUxdZPGk6hYTjfdVW7eYMigt9QKSPJZv G4fkH/Zqo97yvk9rDqn++YcXzO5VcTmhKo3yrFlW6jqWO45bMHC8cQbuzZD/GfTF+jno u1XA== X-Forwarded-Encrypted: i=1; AFNElJ8AFff/RA0cjYZGEhOSv53S/IFa9nS/S0a9MWnRkQTDv7/zv0feVxSru3fsH9j6KQV5qgygM4cCJTmKtTU=@vger.kernel.org X-Gm-Message-State: AOJu0YyhW2+7MqITnbWSLQj1qc1CnPQOrL0Ajxe0JjnQjeHOzW93GA4a J2JCmceUm+krvGgqCbYnOI5qEavuw7qkRgEKF0hToJ2W9BS9RnK0lNWq X-Gm-Gg: Acq92OF8SVeHMGKfffaG7xi+VmM0TnKesfkd6a5+o9KbQSYC2+mT7asJg23dww5eFTR SAe4nfoRiVg4Ho8xxSxbwli3JEQRcElbzxLlH+xjWVvtynIohy+7dWmjotVpQ8Tdkm8vzekVO+k pUUZyDk+1SZnWCFgBt9Ofu5ZIZtIct5C3rr1k67lflCBMQB1U3E/u3/XJ290uIF5RaFpbgZboR9 12jdIi6ea+x7qZCwzM5ldoaTzjRBvnWpzwrh6zwTCt3sawyVJgzGc+GN/XKFxaLDChqdEe7vAPw tJ/Lo1NbSB72ejRUslkLZSevH8Hik82RIIc15qdVKhsc3nB3+R+MBwmM8mNmbVyMSN8EIXHVATH D02+Xc0VPMWKkkbu9HPSkLKuUf3Mu22Je4FbL9cHSaL+W5nnN5qsfM5jGmNtbTi8yPA6Soj2HtY A/pVOaxJS8Bd+HMd6LxtxN8Rso6z2u7k1KBsmqFAt/VMc8Z8roImMrPuLSAEL36zwaonRwlvati +kzJzbdsq5XVgAaB+S8W87sm24SEK64p053Nvgki8FPmDrgW8mlBQ== X-Received: by 2002:a05:620a:46a3:b0:8ea:ed9a:21f5 with SMTP id af79cd13be357-911cca9d84dmr2041569685a.4.1779063125709; Sun, 17 May 2026 17:12:05 -0700 (PDT) Received: from server0.tail6e7dd.ts.net (c-68-48-65-54.hsd1.mi.comcast.net. [68.48.65.54]) by smtp.gmail.com with ESMTPSA id af79cd13be357-910ba463814sm1302131285a.5.2026.05.17.17.12.04 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 17 May 2026 17:12:05 -0700 (PDT) From: Michael Bommarito To: Steve French Cc: Paulo Alcantara , Ronnie Sahlberg , Shyam Prasad N , Tom Talpey , Bharath SM , Samuel Cabrero , Aurelien Aptel , linux-cifs@vger.kernel.org, samba-technical@lists.samba.org, linux-kernel@vger.kernel.org Subject: [PATCH v2 1/2] smb: client: resolve SWN tcon from live registrations Date: Sun, 17 May 2026 20:11:49 -0400 Message-ID: <20260518001150.1323245-2-michael.bommarito@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260518001150.1323245-1-michael.bommarito@gmail.com> References: <20260518001150.1323245-1-michael.bommarito@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" cifs_swn_notify() looks up a witness registration by id under cifs_swnreg_idr_mutex, drops the mutex, and then uses the registration's cached tcon pointer. That pointer is not a lifetime reference, and it is not a stable representative once cifs_get_swn_reg() lets multiple tcons for the same net/share name share one registration id. A same-share second mount can keep the cifs_swn_reg alive after the first tcon unregisters and is freed. The registration then still points at the freed first tcon, so taking tc_lock or incrementing tc_count through swnreg->tcon only moves the use-after-free earlier. Taking tc_lock while holding cifs_swnreg_idr_mutex also violates the documented CIFS lock order. Fix this by making the registration store only the stable witness identity: id, net name, share name, and notify flags. When a notify arrives, copy that identity under cifs_swnreg_idr_mutex, drop the mutex, then find and pin a live witness tcon that currently matches the net/share pair under the normal cifs_tcp_ses_lock -> tc_lock order. The notification path uses that pinned tcon directly and drops the reference when done. Registration and unregister messages now use the live tcon passed by the caller instead of a cached tcon in the registration. The final unregister send is folded into cifs_swn_unregister() while the registration is still protected by cifs_swnreg_idr_mutex. This removes the previous find/drop/reacquire raw-pointer window. The release path only removes the idr entry and frees the stable identity strings. This preserves the intended one-registration/many-tcon behavior: a registration id represents a net/share pair, and notify handling acts on a live representative selected at use time. It also preserves CLIENT_MOVE ordering for the representative tcon because the old-IP unregister is sent before cifs_swn_register() sends the new-IP register. Fixes: fed979a7e082 ("cifs: Set witness notification handler for messages f= rom userspace daemon") Cc: stable@vger.kernel.org Signed-off-by: Michael Bommarito Assisted-by: Claude:claude-opus-4-7 --- fs/smb/client/cifs_swn.c | 314 ++++++++++++++++++++++++++++++++------- fs/smb/client/trace.h | 2 + 2 files changed, 262 insertions(+), 54 deletions(-) diff --git a/fs/smb/client/cifs_swn.c b/fs/smb/client/cifs_swn.c index 9753a432d0998..9951817d0d7ff 100644 --- a/fs/smb/client/cifs_swn.c +++ b/fs/smb/client/cifs_swn.c @@ -28,10 +28,54 @@ struct cifs_swn_reg { bool net_name_notify; bool share_name_notify; bool ip_notify; +}; =20 - struct cifs_tcon *tcon; +struct cifs_swn_reg_info { + int id; + unsigned int ref_count; + const char *net_name; + const char *share_name; + bool net_name_notify; + bool share_name_notify; + bool ip_notify; }; =20 +static void cifs_swn_snapshot_reg(struct cifs_swn_reg *swnreg, + struct cifs_swn_reg_info *info) +{ + info->id =3D swnreg->id; + info->ref_count =3D kref_read(&swnreg->ref_count); + info->net_name =3D swnreg->net_name; + info->share_name =3D swnreg->share_name; + info->net_name_notify =3D swnreg->net_name_notify; + info->share_name_notify =3D swnreg->share_name_notify; + info->ip_notify =3D swnreg->ip_notify; +} + +static int cifs_swn_dup_reg(struct cifs_swn_reg *swnreg, + struct cifs_swn_reg_info *info) +{ + cifs_swn_snapshot_reg(swnreg, info); + + info->net_name =3D kstrdup(swnreg->net_name, GFP_KERNEL); + if (!info->net_name) + return -ENOMEM; + + info->share_name =3D kstrdup(swnreg->share_name, GFP_KERNEL); + if (!info->share_name) { + kfree(info->net_name); + return -ENOMEM; + } + + return 0; +} + +static void cifs_swn_free_reg_info(struct cifs_swn_reg_info *info) +{ + kfree(info->net_name); + kfree(info->share_name); +} + static int cifs_swn_auth_info_krb(struct cifs_tcon *tcon, struct sk_buff *= skb) { int ret; @@ -73,7 +117,8 @@ static int cifs_swn_auth_info_ntlm(struct cifs_tcon *tco= n, struct sk_buff *skb) * The authentication information to connect to the witness service is bun= dled * into the message. */ -static int cifs_swn_send_register_message(struct cifs_swn_reg *swnreg) +static int cifs_swn_send_register_message(struct cifs_swn_reg_info *swnreg, + struct cifs_tcon *tcon) { struct sk_buff *skb; struct genlmsghdr *hdr; @@ -109,10 +154,10 @@ static int cifs_swn_send_register_message(struct cifs= _swn_reg *swnreg) * told to switch to it (client move message). In these cases we unregist= er from the * server address and register to the new address when we receive the not= ification. */ - if (swnreg->tcon->ses->server->use_swn_dstaddr) - addr =3D &swnreg->tcon->ses->server->swn_dstaddr; + if (tcon->ses->server->use_swn_dstaddr) + addr =3D &tcon->ses->server->swn_dstaddr; else - addr =3D &swnreg->tcon->ses->server->dstaddr; + addr =3D &tcon->ses->server->dstaddr; =20 ret =3D nla_put(skb, CIFS_GENL_ATTR_SWN_IP, sizeof(struct sockaddr_storag= e), addr); if (ret < 0) @@ -136,10 +181,10 @@ static int cifs_swn_send_register_message(struct cifs= _swn_reg *swnreg) goto nlmsg_fail; } =20 - authtype =3D cifs_select_sectype(swnreg->tcon->ses->server, swnreg->tcon-= >ses->sectype); + authtype =3D cifs_select_sectype(tcon->ses->server, tcon->ses->sectype); switch (authtype) { case Kerberos: - ret =3D cifs_swn_auth_info_krb(swnreg->tcon, skb); + ret =3D cifs_swn_auth_info_krb(tcon, skb); if (ret < 0) { cifs_dbg(VFS, "%s: Failed to get kerberos auth info: %d\n", __func__, r= et); goto nlmsg_fail; @@ -147,7 +192,7 @@ static int cifs_swn_send_register_message(struct cifs_s= wn_reg *swnreg) break; case NTLMv2: case RawNTLMSSP: - ret =3D cifs_swn_auth_info_ntlm(swnreg->tcon, skb); + ret =3D cifs_swn_auth_info_ntlm(tcon, skb); if (ret < 0) { cifs_dbg(VFS, "%s: Failed to get NTLM auth info: %d\n", __func__, ret); goto nlmsg_fail; @@ -176,7 +221,8 @@ static int cifs_swn_send_register_message(struct cifs_s= wn_reg *swnreg) /* * Sends an uregister message to the userspace daemon based on the registr= ation */ -static int cifs_swn_send_unregister_message(struct cifs_swn_reg *swnreg) +static int cifs_swn_send_unregister_message(struct cifs_swn_reg_info *swnr= eg, + struct cifs_tcon *tcon) { struct sk_buff *skb; struct genlmsghdr *hdr; @@ -205,7 +251,7 @@ static int cifs_swn_send_unregister_message(struct cifs= _swn_reg *swnreg) goto nlmsg_fail; =20 ret =3D nla_put(skb, CIFS_GENL_ATTR_SWN_IP, sizeof(struct sockaddr_storag= e), - &swnreg->tcon->ses->server->dstaddr); + &tcon->ses->server->dstaddr); if (ret < 0) goto nlmsg_fail; =20 @@ -241,6 +287,88 @@ static int cifs_swn_send_unregister_message(struct cif= s_swn_reg *swnreg) return ret; } =20 +/* + * Allocation-free mirror of extract_hostname() + extract_sharename() from + * fs/smb/client/unc.c. Those helpers kmalloc(GFP_KERNEL); this runs under + * cifs_tcp_ses_lock and tcon->tc_lock, both spinlocks, so we mirror their + * parsing in place against the caller's stable net_name/share_name string= s. + * Keep in sync with unc.c. + */ +static bool cifs_swn_tcon_matches(struct cifs_tcon *tcon, + const char *net_name, + const char *share_name) +{ + const char *unc =3D tcon->tree_name; + const char *host, *share, *delim; + size_t host_len, share_len; + + if (!tcon->use_witness) + return false; + + /* extract_hostname: require strlen(unc) >=3D 3 */ + if (strnlen(unc, 3) < 3) + return false; + /* extract_hostname: skip all leading '\' characters */ + for (host =3D unc; *host =3D=3D '\\'; host++) + ; + if (!*host) + return false; + delim =3D strchr(host, '\\'); + if (!delim) + return false; + host_len =3D delim - host; + if (strlen(net_name) !=3D host_len || + strncasecmp(host, net_name, host_len)) + return false; + + /* extract_sharename: start at unc + 2, then first '\' onward */ + share =3D unc + 2; + delim =3D strchr(share, '\\'); + if (!delim) + return false; + share =3D delim + 1; + share_len =3D strlen(share); + + return strlen(share_name) =3D=3D share_len && + !strncasecmp(share, share_name, share_len); +} + +/* + * One SWN registration id represents one net/share name pair. Multiple + * mounted tcons can therefore share the id. Pick a live representative at + * use time instead of caching the first tcon pointer in the registration. + */ +static struct cifs_tcon *cifs_swn_get_tcon(struct cifs_swn_reg_info *swnre= g) +{ + struct TCP_Server_Info *server; + struct cifs_ses *ses; + struct cifs_tcon *tcon; + + spin_lock(&cifs_tcp_ses_lock); + list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) { + list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) { + list_for_each_entry(tcon, &ses->tcon_list, tcon_list) { + spin_lock(&tcon->tc_lock); + if (tcon->status =3D=3D TID_EXITING || + !cifs_swn_tcon_matches(tcon, swnreg->net_name, + swnreg->share_name)) { + spin_unlock(&tcon->tc_lock); + continue; + } + ++tcon->tc_count; + trace_smb3_tcon_ref(tcon->debug_id, + tcon->tc_count, + netfs_trace_tcon_ref_get_swn_notify); + spin_unlock(&tcon->tc_lock); + spin_unlock(&cifs_tcp_ses_lock); + return tcon; + } + } + } + spin_unlock(&cifs_tcp_ses_lock); + return NULL; +} + /* * Try to find a matching registration for the tcon's server name and shar= e name. * Calls to this function must be protected by cifs_swnreg_idr_mutex. @@ -347,8 +475,6 @@ static struct cifs_swn_reg *cifs_get_swn_reg(struct cif= s_tcon *tcon) reg->net_name_notify =3D true; reg->share_name_notify =3D true; reg->ip_notify =3D (tcon->capabilities & SMB2_SHARE_CAP_SCALEOUT); - - reg->tcon =3D tcon; unlock: mutex_unlock(&cifs_swnreg_idr_mutex); =20 @@ -368,11 +494,6 @@ static struct cifs_swn_reg *cifs_get_swn_reg(struct ci= fs_tcon *tcon) static void cifs_swn_reg_release(struct kref *ref) { struct cifs_swn_reg *swnreg =3D container_of(ref, struct cifs_swn_reg, re= f_count); - int ret; - - ret =3D cifs_swn_send_unregister_message(swnreg); - if (ret < 0) - cifs_dbg(VFS, "%s: Failed to send unregister message: %d\n", __func__, r= et); =20 idr_remove(&cifs_swnreg_idr, swnreg->id); kfree(swnreg->net_name); @@ -380,23 +501,33 @@ static void cifs_swn_reg_release(struct kref *ref) kfree(swnreg); } =20 -static void cifs_put_swn_reg(struct cifs_swn_reg *swnreg) +static void cifs_put_swn_reg_locked(struct cifs_swn_reg *swnreg, + struct cifs_tcon *tcon) { - mutex_lock(&cifs_swnreg_idr_mutex); + if (kref_read(&swnreg->ref_count) =3D=3D 1) { + struct cifs_swn_reg_info swnreg_info; + int ret; + + cifs_swn_snapshot_reg(swnreg, &swnreg_info); + ret =3D cifs_swn_send_unregister_message(&swnreg_info, tcon); + if (ret < 0) + cifs_dbg(VFS, "%s: Failed to send unregister message: %d\n", + __func__, ret); + } + kref_put(&swnreg->ref_count, cifs_swn_reg_release); - mutex_unlock(&cifs_swnreg_idr_mutex); } =20 -static int cifs_swn_resource_state_changed(struct cifs_swn_reg *swnreg, co= nst char *name, int state) +static int cifs_swn_resource_state_changed(struct cifs_tcon *tcon, const c= har *name, int state) { switch (state) { case CIFS_SWN_RESOURCE_STATE_UNAVAILABLE: cifs_dbg(FYI, "%s: resource name '%s' become unavailable\n", __func__, n= ame); - cifs_signal_cifsd_for_reconnect(swnreg->tcon->ses->server, true); + cifs_signal_cifsd_for_reconnect(tcon->ses->server, true); break; case CIFS_SWN_RESOURCE_STATE_AVAILABLE: cifs_dbg(FYI, "%s: resource name '%s' become available\n", __func__, nam= e); - cifs_signal_cifsd_for_reconnect(swnreg->tcon->ses->server, true); + cifs_signal_cifsd_for_reconnect(tcon->ses->server, true); break; case CIFS_SWN_RESOURCE_STATE_UNKNOWN: cifs_dbg(FYI, "%s: resource name '%s' changed to unknown state\n", __fun= c__, name); @@ -502,7 +633,7 @@ static int cifs_swn_reconnect(struct cifs_tcon *tcon, s= truct sockaddr_storage *a return ret; } =20 -static int cifs_swn_client_move(struct cifs_swn_reg *swnreg, struct sockad= dr_storage *addr) +static int cifs_swn_client_move(struct cifs_tcon *tcon, struct sockaddr_st= orage *addr) { struct sockaddr_in *ipv4 =3D (struct sockaddr_in *)addr; struct sockaddr_in6 *ipv6 =3D (struct sockaddr_in6 *)addr; @@ -512,14 +643,17 @@ static int cifs_swn_client_move(struct cifs_swn_reg *= swnreg, struct sockaddr_sto else if (addr->ss_family =3D=3D AF_INET6) cifs_dbg(FYI, "%s: move to %pI6\n", __func__, &ipv6->sin6_addr); =20 - return cifs_swn_reconnect(swnreg->tcon, addr); + return cifs_swn_reconnect(tcon, addr); } =20 int cifs_swn_notify(struct sk_buff *skb, struct genl_info *info) { struct cifs_swn_reg *swnreg; + struct cifs_swn_reg_info swnreg_info; + struct cifs_tcon *tcon; char name[256]; int type; + int ret =3D 0; =20 if (info->attrs[CIFS_GENL_ATTR_SWN_REGISTRATION_ID]) { int swnreg_id; @@ -527,21 +661,34 @@ int cifs_swn_notify(struct sk_buff *skb, struct genl_= info *info) swnreg_id =3D nla_get_u32(info->attrs[CIFS_GENL_ATTR_SWN_REGISTRATION_ID= ]); mutex_lock(&cifs_swnreg_idr_mutex); swnreg =3D idr_find(&cifs_swnreg_idr, swnreg_id); - mutex_unlock(&cifs_swnreg_idr_mutex); if (swnreg =3D=3D NULL) { + mutex_unlock(&cifs_swnreg_idr_mutex); cifs_dbg(FYI, "%s: registration id %d not found\n", __func__, swnreg_id= ); return -EINVAL; } + ret =3D cifs_swn_dup_reg(swnreg, &swnreg_info); + mutex_unlock(&cifs_swnreg_idr_mutex); + if (ret) + return ret; } else { cifs_dbg(FYI, "%s: missing registration id attribute\n", __func__); return -EINVAL; } =20 + tcon =3D cifs_swn_get_tcon(&swnreg_info); + if (!tcon) { + cifs_dbg(FYI, "%s: registration id %d has no live tcon\n", + __func__, swnreg_info.id); + ret =3D -ENODEV; + goto free_info; + } + if (info->attrs[CIFS_GENL_ATTR_SWN_NOTIFICATION_TYPE]) { type =3D nla_get_u32(info->attrs[CIFS_GENL_ATTR_SWN_NOTIFICATION_TYPE]); } else { cifs_dbg(FYI, "%s: missing notification type attribute\n", __func__); - return -EINVAL; + ret =3D -EINVAL; + goto out; } =20 switch (type) { @@ -553,15 +700,18 @@ int cifs_swn_notify(struct sk_buff *skb, struct genl_= info *info) sizeof(name)); } else { cifs_dbg(FYI, "%s: missing resource name attribute\n", __func__); - return -EINVAL; + ret =3D -EINVAL; + goto out; } if (info->attrs[CIFS_GENL_ATTR_SWN_RESOURCE_STATE]) { state =3D nla_get_u32(info->attrs[CIFS_GENL_ATTR_SWN_RESOURCE_STATE]); } else { cifs_dbg(FYI, "%s: missing resource state attribute\n", __func__); - return -EINVAL; + ret =3D -EINVAL; + goto out; } - return cifs_swn_resource_state_changed(swnreg, name, state); + ret =3D cifs_swn_resource_state_changed(tcon, name, state); + break; } case CIFS_SWN_NOTIFICATION_CLIENT_MOVE: { struct sockaddr_storage addr; @@ -570,28 +720,36 @@ int cifs_swn_notify(struct sk_buff *skb, struct genl_= info *info) nla_memcpy(&addr, info->attrs[CIFS_GENL_ATTR_SWN_IP], sizeof(addr)); } else { cifs_dbg(FYI, "%s: missing IP address attribute\n", __func__); - return -EINVAL; + ret =3D -EINVAL; + goto out; } - return cifs_swn_client_move(swnreg, &addr); + ret =3D cifs_swn_client_move(tcon, &addr); + break; } default: cifs_dbg(FYI, "%s: unknown notification type %d\n", __func__, type); break; } =20 - return 0; +out: + cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_swn_notify); +free_info: + cifs_swn_free_reg_info(&swnreg_info); + return ret; } =20 int cifs_swn_register(struct cifs_tcon *tcon) { struct cifs_swn_reg *swnreg; + struct cifs_swn_reg_info swnreg_info; int ret; =20 swnreg =3D cifs_get_swn_reg(tcon); if (IS_ERR(swnreg)) return PTR_ERR(swnreg); =20 - ret =3D cifs_swn_send_register_message(swnreg); + cifs_swn_snapshot_reg(swnreg, &swnreg_info); + ret =3D cifs_swn_send_register_message(&swnreg_info, tcon); if (ret < 0) { cifs_dbg(VFS, "%s: Failed to send swn register message: %d\n", __func__,= ret); /* Do not put the swnreg or return error, the echo task will retry */ @@ -612,35 +770,68 @@ int cifs_swn_unregister(struct cifs_tcon *tcon) return PTR_ERR(swnreg); } =20 + cifs_put_swn_reg_locked(swnreg, tcon); mutex_unlock(&cifs_swnreg_idr_mutex); =20 - cifs_put_swn_reg(swnreg); - return 0; } =20 -void cifs_swn_dump(struct seq_file *m) +/* + * Snapshot one registration under cifs_swnreg_idr_mutex and return. Call= ers + * intentionally do the per-registration network/genlmsg work without the + * mutex held, both to keep the critical section short and to avoid nesting + * cifs_swnreg_idr_mutex inside the higher tc_lock when a live tcon is then + * pinned for the send. + */ +static int cifs_swn_get_next_reg_info(int *id, struct cifs_swn_reg_info *i= nfo) { struct cifs_swn_reg *swnreg; + int ret =3D 0; + + mutex_lock(&cifs_swnreg_idr_mutex); + swnreg =3D idr_get_next(&cifs_swnreg_idr, id); + if (swnreg) { + ret =3D cifs_swn_dup_reg(swnreg, info); + if (!ret) { + *id =3D swnreg->id + 1; + ret =3D 1; + } + } + mutex_unlock(&cifs_swnreg_idr_mutex); + + return ret; +} + +void cifs_swn_dump(struct seq_file *m) +{ + struct cifs_swn_reg_info swnreg_info; + struct cifs_tcon *tcon; struct sockaddr_in *sa; struct sockaddr_in6 *sa6; - int id; + int id =3D 0; + int ret; =20 seq_puts(m, "Witness registrations:"); =20 - mutex_lock(&cifs_swnreg_idr_mutex); - idr_for_each_entry(&cifs_swnreg_idr, swnreg, id) { + while ((ret =3D cifs_swn_get_next_reg_info(&id, &swnreg_info)) > 0) { seq_printf(m, "\nId: %u Refs: %u Network name: '%s'%s Share name: '%s'%s= Ip address: ", - id, kref_read(&swnreg->ref_count), - swnreg->net_name, swnreg->net_name_notify ? "(y)" : "(n)", - swnreg->share_name, swnreg->share_name_notify ? "(y)" : "(n)"); - switch (swnreg->tcon->ses->server->dstaddr.ss_family) { + swnreg_info.id, swnreg_info.ref_count, + swnreg_info.net_name, swnreg_info.net_name_notify ? "(y)" : "(n)", + swnreg_info.share_name, swnreg_info.share_name_notify ? "(y)" : "(n)= "); + + tcon =3D cifs_swn_get_tcon(&swnreg_info); + if (!tcon) { + seq_puts(m, "(no live tcon)"); + goto next; + } + + switch (tcon->ses->server->dstaddr.ss_family) { case AF_INET: - sa =3D (struct sockaddr_in *) &swnreg->tcon->ses->server->dstaddr; + sa =3D (struct sockaddr_in *)&tcon->ses->server->dstaddr; seq_printf(m, "%pI4", &sa->sin_addr.s_addr); break; case AF_INET6: - sa6 =3D (struct sockaddr_in6 *) &swnreg->tcon->ses->server->dstaddr; + sa6 =3D (struct sockaddr_in6 *)&tcon->ses->server->dstaddr; seq_printf(m, "%pI6", &sa6->sin6_addr.s6_addr); if (sa6->sin6_scope_id) seq_printf(m, "%%%u", sa6->sin6_scope_id); @@ -648,23 +839,38 @@ void cifs_swn_dump(struct seq_file *m) default: seq_puts(m, "(unknown)"); } - seq_printf(m, "%s", swnreg->ip_notify ? "(y)" : "(n)"); + cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_swn_notify); +next: + seq_printf(m, "%s", swnreg_info.ip_notify ? "(y)" : "(n)"); + cifs_swn_free_reg_info(&swnreg_info); } - mutex_unlock(&cifs_swnreg_idr_mutex); + if (ret < 0) + seq_printf(m, "\nFailed to snapshot witness registration: %d", ret); seq_puts(m, "\n"); } =20 void cifs_swn_check(void) { - struct cifs_swn_reg *swnreg; - int id; + struct cifs_swn_reg_info swnreg_info; + struct cifs_tcon *tcon; + int id =3D 0; int ret; =20 - mutex_lock(&cifs_swnreg_idr_mutex); - idr_for_each_entry(&cifs_swnreg_idr, swnreg, id) { - ret =3D cifs_swn_send_register_message(swnreg); + while ((ret =3D cifs_swn_get_next_reg_info(&id, &swnreg_info)) > 0) { + tcon =3D cifs_swn_get_tcon(&swnreg_info); + if (!tcon) { + cifs_dbg(FYI, "%s: registration id %d has no live tcon\n", + __func__, swnreg_info.id); + goto free_info; + } + + ret =3D cifs_swn_send_register_message(&swnreg_info, tcon); if (ret < 0) cifs_dbg(FYI, "%s: Failed to send register message: %d\n", __func__, re= t); + cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_swn_notify); +free_info: + cifs_swn_free_reg_info(&swnreg_info); } - mutex_unlock(&cifs_swnreg_idr_mutex); + if (ret < 0) + cifs_dbg(FYI, "%s: Failed to snapshot registration: %d\n", __func__, ret= ); } diff --git a/fs/smb/client/trace.h b/fs/smb/client/trace.h index b99ec5a417fad..5b21ad3c15fb5 100644 --- a/fs/smb/client/trace.h +++ b/fs/smb/client/trace.h @@ -181,6 +181,7 @@ EM(netfs_trace_tcon_ref_get_find, "GET Find ") \ EM(netfs_trace_tcon_ref_get_find_sess_tcon, "GET FndSes") \ EM(netfs_trace_tcon_ref_get_reconnect_server, "GET Reconn") \ + EM(netfs_trace_tcon_ref_get_swn_notify, "GET SwnNot") \ EM(netfs_trace_tcon_ref_new, "NEW ") \ EM(netfs_trace_tcon_ref_new_ipc, "NEW Ipc ") \ EM(netfs_trace_tcon_ref_new_reconnect_server, "NEW Reconn") \ @@ -192,6 +193,7 @@ EM(netfs_trace_tcon_ref_put_mnt_ctx, "PUT MntCtx") \ EM(netfs_trace_tcon_ref_put_dfs_refer, "PUT DfsRfr") \ EM(netfs_trace_tcon_ref_put_reconnect_server, "PUT Reconn") \ + EM(netfs_trace_tcon_ref_put_swn_notify, "PUT SwnNot") \ EM(netfs_trace_tcon_ref_put_tlink, "PUT Tlink ") \ EM(netfs_trace_tcon_ref_see_cancelled_close, "SEE Cn-Cls") \ EM(netfs_trace_tcon_ref_see_fscache_collision, "SEE FV-CO!") \ --=20 2.53.0 From nobody Mon May 25 05:54:18 2026 Received: from mail-qk1-f177.google.com (mail-qk1-f177.google.com [209.85.222.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 22EFD481DD for ; Mon, 18 May 2026 00:12:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.222.177 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779063130; cv=none; b=AppzPvZhJJuYKQzP9WzfnwmYV8MGU7n4uXpTgRlJdWXwBO4wCIaNXIlEV7F6ZPKCSCRQfSTFRN6H7BY7IaM/DGEuD1/35DzajLyd5UmMpErx7GR5WzEpDajjP4eI1LTE3Bwzgbk46p4EhwcQU/7huke5crZE7B6V6b1yCblJ2uI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779063130; c=relaxed/simple; bh=hZ2s3bYyC849WhRpbjz47wBYW2bqeoYAepeRDbHrjMs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=pnD/OXEVbfveSSx71VL4miqFvVSPcLL8TdDfqgLhz5mVwFh+qXZgyfZqT7ruXHG7tlQ/OKQDXw2D7XFHa2lhhUHO8BqaMjFedlu2QG1FqaI76XJzngPSrIihDNzJY4w14x8Q8HreT5jwGoevyOR5gA2knQZ7b0Y+GHNxMhU/ysk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=Kek2f7E8; arc=none smtp.client-ip=209.85.222.177 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Kek2f7E8" Received: by mail-qk1-f177.google.com with SMTP id af79cd13be357-9102e90bcbeso210583085a.1 for ; Sun, 17 May 2026 17:12:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1779063128; x=1779667928; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=ezHZ13i3eCkMlaokSKeUT4qhol7hZOeBasCQQK8buDM=; b=Kek2f7E8Qs9uFQ/ka5yuKSrY+BR2iS5vnyCWRlw92s+XXcZhpECk1xtYmJl+x7kgOq 58oM5ZkXl2mzEyi0EgfhLHUb15IAO/WvRm5VWJ2AxluC77Ttfv5OgeIJ/wZ26pxmHUqM zgLpQiRIC6pd9my/CZEo16Ebnli5eKK6ytjynJgoYnAnddK/s4QD5JsnOUUZqJW4Cvif MpfAPpKEjpnDUk8O+AxTm/WMdYAspWhwJTYLofHV9hsxWPF2JonxrjJMFKdUYJMTo8ki XO/0W/4BGOt63LvVjCC45/39TMOsUd82HdK2jNgDIv9EQtG7EYgbhDw/oKQrQ1K8rmJw FmAQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779063128; x=1779667928; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=ezHZ13i3eCkMlaokSKeUT4qhol7hZOeBasCQQK8buDM=; b=HB9Ro7Zvf/i80+aCtkO2h74hhWQe6/lKD/R8kTOX9bI9XzcNDUDnT3fy9KLM98w+6D aE/qty9YZmSgUZi+6sw62dSV2tE3UG4aFG2hph9Up2aqMzMbC5p1NzgrL1Y1SP69Bvw0 zWhVyrkWCqEKtk7ceozBASI0+sTnxaKB4WPbdP+/SogRGNf+lPuzp7h3/K9m1zSQueBc uNHn9EnAMEwnb0rFFwP9pyiAaPVQO+aBx46vmSaN/M2xlKU1CAhA50oKTF2ylGR/lgKP XpwKI0JhCtpakBz0z5siLs+ABcEsiysENId1QyVITmNCUtFiGFXf1k+CHxJlNLZ3sgjM dPgg== X-Forwarded-Encrypted: i=1; AFNElJ9u8WyKLT+4EIavEtTuFJQv3g9JrQKFoLgQRsr+sARhZoleg+s7nFi1fm2vjlv+nR2NAZ2FlzN9GYy6ekM=@vger.kernel.org X-Gm-Message-State: AOJu0YxHrYzQ1gbxnrEzXcZYSIkUrSqbs26bdyB1EhQgVxoRfbtEan1y jRT5xkQyE3TCNTHCFSuId7s9Be5QrWI2cHY/nIu6oRwmmAsGGWtqhpa0 X-Gm-Gg: Acq92OEJ8Gxon0SmWF/BtgtARE78kJLNvkboSf5m7G5GrtWJahUbnXme2nSsPtTpb8F jGIXpgJm3bqlVwUSug4UmveEF6g0TlmozDssktgshBq/5xzxpcFdsx/m4BVGf3fKti0aLjfpRW7 cB3ER/l54y4W++EGCb6LtT8kpGnwjeXwzL4h90oWUBhseLs9Mtfn+eiiw8akzRTs4GWf2LD6p1+ 8V9POyiKJNbY6aD+eSuKSzSypBbSCTyMyh2lekDpq62Tbu1Wl19OP8uUtxJc9PpQcjqmUPjNuKv RbpsBFoCWZ06RD8xB323+QL9tlJzB95RAUabhr6gIYGHu3ISsjxOiohoFbJLsIpL6PuSq2xcgL1 ArLBJNn+whvDjoWMwobCpX13cUCR8jcX1q93NKNlZ49dAUpdEcQMD6kqyqGteDLkEbYFsz0XPBw /8c3hnr+h+I+tlKIwUxb9y4UewskhoxXfMPFjiM3Zyeq0OcSCO6i2qg/z65ubtlTZ5nLxRirTXL M90rxSRkf6eUWPp4v9W7lRlh0fiBuGRj9MeGr4yY2Q= X-Received: by 2002:a05:620a:6cc1:b0:910:3019:f463 with SMTP id af79cd13be357-911cce86fbfmr1906344285a.8.1779063127992; Sun, 17 May 2026 17:12:07 -0700 (PDT) Received: from server0.tail6e7dd.ts.net (c-68-48-65-54.hsd1.mi.comcast.net. [68.48.65.54]) by smtp.gmail.com with ESMTPSA id af79cd13be357-910ba463814sm1302131285a.5.2026.05.17.17.12.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 17 May 2026 17:12:07 -0700 (PDT) From: Michael Bommarito To: Steve French Cc: Paulo Alcantara , Ronnie Sahlberg , Shyam Prasad N , Tom Talpey , Bharath SM , Samuel Cabrero , Aurelien Aptel , linux-cifs@vger.kernel.org, samba-technical@lists.samba.org, linux-kernel@vger.kernel.org Subject: [PATCH v2 2/2] smb: client: require net admin for CIFS SWN netlink Date: Sun, 17 May 2026 20:11:50 -0400 Message-ID: <20260518001150.1323245-3-michael.bommarito@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260518001150.1323245-1-michael.bommarito@gmail.com> References: <20260518001150.1323245-1-michael.bommarito@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" CIFS_GENL_CMD_SWN_NOTIFY is the userspace witness-notify command. The intended sender is the cifs.witness helper, but the generic-netlink operation currently has no capability flag, so any local process can send RESOURCE_CHANGE or CLIENT_MOVE notifications to the in-kernel witness handler. The same family exposes CIFS_GENL_MCGRP_SWN without multicast-group capability flags. Register messages sent to that group include the witness registration id and, for NTLM-authenticated mounts, the username, domain, and password attributes copied from the CIFS session. An unprivileged local process should not be able to join that group and receive those messages. Require CAP_NET_ADMIN for incoming SWN_NOTIFY commands with GENL_ADMIN_PERM, and require CAP_NET_ADMIN over the network namespace for joining the SWN multicast group with GENL_MCAST_CAP_NET_ADMIN. The cifs.witness service runs with the privileges needed for both operations. Fixes: fed979a7e082 ("cifs: Set witness notification handler for messages f= rom userspace daemon") Cc: stable@vger.kernel.org Signed-off-by: Michael Bommarito Assisted-by: Claude:claude-opus-4-7 --- fs/smb/client/netlink.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/netlink.c b/fs/smb/client/netlink.c index 147d9409252cd..0dd10913c37a0 100644 --- a/fs/smb/client/netlink.c +++ b/fs/smb/client/netlink.c @@ -33,13 +33,17 @@ static const struct nla_policy cifs_genl_policy[CIFS_GE= NL_ATTR_MAX + 1] =3D { static const struct genl_ops cifs_genl_ops[] =3D { { .cmd =3D CIFS_GENL_CMD_SWN_NOTIFY, + .flags =3D GENL_ADMIN_PERM, .validate =3D GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, .doit =3D cifs_swn_notify, }, }; =20 static const struct genl_multicast_group cifs_genl_mcgrps[] =3D { - [CIFS_GENL_MCGRP_SWN] =3D { .name =3D CIFS_GENL_MCGRP_SWN_NAME }, + [CIFS_GENL_MCGRP_SWN] =3D { + .name =3D CIFS_GENL_MCGRP_SWN_NAME, + .flags =3D GENL_MCAST_CAP_NET_ADMIN, + }, }; =20 struct genl_family cifs_genl_family =3D { --=20 2.53.0