public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH] smb/client: fix chan_max race and rollback in smb3_reconfigure
@ 2026-04-16  2:28 DaeMyung Kang
  2026-04-16  4:56 ` RAJASI MANDAL
  2026-04-16 15:18 ` [PATCH v2] smb/client: fix chan_max and UNC state corruption " DaeMyung Kang
  0 siblings, 2 replies; 9+ messages in thread
From: DaeMyung Kang @ 2026-04-16  2:28 UTC (permalink / raw)
  To: sfrench
  Cc: pc, ronniesahlberg, sprasad, tom, bharathsm, rajasimandal,
	linux-cifs, samba-technical, linux-kernel, DaeMyung Kang

smb3_reconfigure() has two bugs when handling multichannel remount:

1) smb3_sync_ses_chan_max() is called before acquiring
   CIFS_SES_FLAG_SCALE_CHANNELS. If a concurrent operation (e.g.
   smb2_reconnect) holds the flag, the current thread takes the
   loser path and returns -EINVAL, but ses->chan_max has already
   been updated to the new value. This leaves chan_max inconsistent
   with the actual channel state.

2) When smb3_update_ses_channels() fails, chan_max is not rolled
   back to its previous value and the error is not propagated to
   the caller. The mount command returns success even though the
   channel configuration was not applied, and repeated failures
   cause chan_max to drift further from reality.

Fix both by moving smb3_sync_ses_chan_max() after the
CIFS_SES_FLAG_SCALE_CHANNELS acquisition so the loser path cannot
corrupt chan_max, and by restoring chan_max from old_chan_max on
update failure while still holding the scaling flag to prevent a
concurrent reconfigure from observing the intermediate state.
Propagate the error to the caller so userspace is informed.

Note: smb3_reconfigure() is not fully transactional — credentials
are committed before the multichannel block, so any early return
(including the existing CIFS_SES_FLAG_SCALE_CHANNELS loser path)
can leave ses->password updated while cifs_sb->ctx is not yet
synced. Making the entire function atomic is a larger refactor
and is left for a separate patch.

Tested with a QEMU VM (ksmbd + cifs) using module-parameter-based
fault injection:
 - Forced smb3_update_ses_channels() failure via module param and
   verified chan_max is preserved at the old value after remount
   failure (fault-injection test for rollback).
 - Pre-set CIFS_SES_FLAG_SCALE_CHANNELS before entering the
   scaling path and verified the loser path returns -EINVAL
   without corrupting chan_max (loser-path invariant test).
 - Repeated 19 forced-failure remounts with varying max_channels
   (range 2-8) and confirmed no chan_max drift (stress test).
All three scenarios pass with the patch and fail without it.

Fixes: ef529f655a2c ("cifs: client: allow changing multichannel mount options on remount")
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
---
 fs/smb/client/fs_context.c | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)

diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c
index b9544eb0381b..99e62c2dbf50 100644
--- a/fs/smb/client/fs_context.c
+++ b/fs/smb/client/fs_context.c
@@ -1085,6 +1085,7 @@ static int smb3_reconfigure(struct fs_context *fc)
 	struct dentry *root = fc->root;
 	struct cifs_sb_info *cifs_sb = CIFS_SB(root->d_sb);
 	struct cifs_ses *ses = cifs_sb_master_tcon(cifs_sb)->ses;
+	unsigned int old_chan_max;
 	unsigned int rsize = ctx->rsize, wsize = ctx->wsize;
 	char *new_password = NULL, *new_password2 = NULL;
 	bool need_recon = false;
@@ -1170,9 +1171,6 @@ static int smb3_reconfigure(struct fs_context *fc)
 	if ((ctx->multichannel != cifs_sb->ctx->multichannel) ||
 	    (ctx->max_channels != cifs_sb->ctx->max_channels)) {
 
-		/* Synchronize ses->chan_max with the new mount context */
-		smb3_sync_ses_chan_max(ses, ctx->max_channels);
-		/* Now update the session's channels to match the new configuration */
 		/* Prevent concurrent scaling operations */
 		spin_lock(&ses->ses_lock);
 		if (ses->flags & CIFS_SES_FLAG_SCALE_CHANNELS) {
@@ -1183,16 +1181,31 @@ static int smb3_reconfigure(struct fs_context *fc)
 		ses->flags |= CIFS_SES_FLAG_SCALE_CHANNELS;
 		spin_unlock(&ses->ses_lock);
 
+		old_chan_max = ses->chan_max;
+		/* Synchronize ses->chan_max with the new mount context */
+		smb3_sync_ses_chan_max(ses, ctx->max_channels);
+
 		mutex_unlock(&ses->session_mutex);
 
 		rc = smb3_update_ses_channels(ses, ses->server,
 					       false /* from_reconnect */,
 					       false /* disable_mchan */);
 
+		/*
+		 * On failure, restore chan_max while still holding
+		 * CIFS_SES_FLAG_SCALE_CHANNELS so a concurrent reconfigure
+		 * cannot observe or race with the rollback.
+		 */
+		if (rc < 0)
+			smb3_sync_ses_chan_max(ses, old_chan_max);
+
 		/* Clear scaling flag after operation */
 		spin_lock(&ses->ses_lock);
 		ses->flags &= ~CIFS_SES_FLAG_SCALE_CHANNELS;
 		spin_unlock(&ses->ses_lock);
+
+		if (rc < 0)
+			return rc;
 	} else {
 		mutex_unlock(&ses->session_mutex);
 	}
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH] smb/client: fix chan_max race and rollback in smb3_reconfigure
  2026-04-16  2:28 [PATCH] smb/client: fix chan_max race and rollback in smb3_reconfigure DaeMyung Kang
@ 2026-04-16  4:56 ` RAJASI MANDAL
  2026-04-16 15:18 ` [PATCH v2] smb/client: fix chan_max and UNC state corruption " DaeMyung Kang
  1 sibling, 0 replies; 9+ messages in thread
From: RAJASI MANDAL @ 2026-04-16  4:56 UTC (permalink / raw)
  To: DaeMyung Kang
  Cc: sfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	rajasimandal, linux-cifs, samba-technical, linux-kernel

Hi DaeMyung,

I reviewed your patch. The two core fixes (moving
smb3_sync_ses_chan_max after the SCALE_CHANNELS check, and rolling
back chan_max on failure) are correct. However the error handling
paths corrupt the mount context leading to false future remount
failures.

Issue 1: return rc after STEAL_STRING nullifies UNC/source/username

Earlier in smb3_reconfigure(), STEAL_STRING moves UNC, source, and
username from cifs_sb->ctx into ctx, setting cifs_sb->ctx->UNC = NULL.
The new if (rc < 0) return rc at the end of the multichannel block
exits before smb3_fs_context_dup() can copy them back.

How to repro:
mount -t cifs //server/share /mnt -o multichannel,max_channels=2,...
mount -o remount,multichannel,max_channels=4 /mnt  # fails (server error)
grep /mnt /proc/mounts   # shows "none" instead of //server/share
mount -o remount,multichannel,max_channels=2 /mnt  # fails: "bad UNC (none)"


Issue 2: return -EINVAL on loser path has the same STEAL_STRING corruption

When CIFS_SES_FLAG_SCALE_CHANNELS is already set (concurrent remount),
the return -EINVAL exits after STEAL_STRING but before
smb3_fs_context_dup(), same permanent NULL UNC corruption.

How to repro: Tested with a module parameter that forces the
SCALE_CHANNELS flag check to take the loser path. After the failed
remount, /proc/mounts shows "none" and all future remounts fail.

Suggested fix:

--- a/fs/smb/client/fs_context.c
+++ b/fs/smb/client/fs_context.c
@@ -NNNN,7 +NNNN,7 @@ static int smb3_reconfigure(struct fs_context *fc)
     char *new_password = NULL, *new_password2 = NULL;
     bool need_recon = false;
- int rc;
+ int rc, mchan_rc = 0;

     if (ses->expired_pwd)
@@ -NNNN,8 +NNNN,9 @@ static int smb3_reconfigure(struct fs_context *fc)
         if (ses->flags & CIFS_SES_FLAG_SCALE_CHANNELS) {
             spin_unlock(&ses->ses_lock);
             mutex_unlock(&ses->session_mutex);
- return -EINVAL;
+ mchan_rc = -EINVAL;
+ goto out;
         }
         ses->flags |= CIFS_SES_FLAG_SCALE_CHANNELS;
@@ -NNNN,13 +NNNN,14 @@ static int smb3_reconfigure(struct fs_context *fc)
         ses->flags &= ~CIFS_SES_FLAG_SCALE_CHANNELS;
         spin_unlock(&ses->ses_lock);

- if (rc < 0)
- return rc;
+ if (rc < 0)
+ mchan_rc = rc;
     } else {
         mutex_unlock(&ses->session_mutex);
     }

+out:
     STEAL_STRING(cifs_sb, ctx, domainname);
     STEAL_STRING(cifs_sb, ctx, nodename);
     STEAL_STRING(cifs_sb, ctx, iocharset);
@@ -NNNN,9 +NNNN,22 @@ static int smb3_reconfigure(struct fs_context *fc)
     ctx->rsize = rsize ? CIFS_ALIGN_RSIZE(fc, rsize) : cifs_sb->ctx->rsize;
     ctx->wsize = wsize ? CIFS_ALIGN_WSIZE(fc, wsize) : cifs_sb->ctx->wsize;

+ /*
+ * On multichannel failure, restore the old multichannel settings in ctx
+ * so smb3_fs_context_dup does not desync cifs_sb->ctx from ses->chan_max.
+ */
+ if (mchan_rc) {
+ ctx->multichannel = cifs_sb->ctx->multichannel;
+ ctx->max_channels = cifs_sb->ctx->max_channels;
+ }
+
     smb3_cleanup_fs_context_contents(cifs_sb->ctx);
     rc = smb3_fs_context_dup(cifs_sb->ctx, ctx);
     smb3_update_mnt_flags(cifs_sb);
+
+ if (mchan_rc)
+ return mchan_rc;
+
 #ifdef CONFIG_CIFS_DFS_UPCALL
     if (!rc)
         rc = dfs_cache_remount_fs(cifs_sb);

On Wed, Apr 15, 2026 at 7:28 PM DaeMyung Kang <charsyam@gmail.com> wrote:
>
> smb3_reconfigure() has two bugs when handling multichannel remount:
>
> 1) smb3_sync_ses_chan_max() is called before acquiring
>    CIFS_SES_FLAG_SCALE_CHANNELS. If a concurrent operation (e.g.
>    smb2_reconnect) holds the flag, the current thread takes the
>    loser path and returns -EINVAL, but ses->chan_max has already
>    been updated to the new value. This leaves chan_max inconsistent
>    with the actual channel state.
>
> 2) When smb3_update_ses_channels() fails, chan_max is not rolled
>    back to its previous value and the error is not propagated to
>    the caller. The mount command returns success even though the
>    channel configuration was not applied, and repeated failures
>    cause chan_max to drift further from reality.
>
> Fix both by moving smb3_sync_ses_chan_max() after the
> CIFS_SES_FLAG_SCALE_CHANNELS acquisition so the loser path cannot
> corrupt chan_max, and by restoring chan_max from old_chan_max on
> update failure while still holding the scaling flag to prevent a
> concurrent reconfigure from observing the intermediate state.
> Propagate the error to the caller so userspace is informed.
>
> Note: smb3_reconfigure() is not fully transactional — credentials
> are committed before the multichannel block, so any early return
> (including the existing CIFS_SES_FLAG_SCALE_CHANNELS loser path)
> can leave ses->password updated while cifs_sb->ctx is not yet
> synced. Making the entire function atomic is a larger refactor
> and is left for a separate patch.
>
> Tested with a QEMU VM (ksmbd + cifs) using module-parameter-based
> fault injection:
>  - Forced smb3_update_ses_channels() failure via module param and
>    verified chan_max is preserved at the old value after remount
>    failure (fault-injection test for rollback).
>  - Pre-set CIFS_SES_FLAG_SCALE_CHANNELS before entering the
>    scaling path and verified the loser path returns -EINVAL
>    without corrupting chan_max (loser-path invariant test).
>  - Repeated 19 forced-failure remounts with varying max_channels
>    (range 2-8) and confirmed no chan_max drift (stress test).
> All three scenarios pass with the patch and fail without it.
>
> Fixes: ef529f655a2c ("cifs: client: allow changing multichannel mount options on remount")
> Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
> ---
>  fs/smb/client/fs_context.c | 19 ++++++++++++++++---
>  1 file changed, 16 insertions(+), 3 deletions(-)
>
> diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c
> index b9544eb0381b..99e62c2dbf50 100644
> --- a/fs/smb/client/fs_context.c
> +++ b/fs/smb/client/fs_context.c
> @@ -1085,6 +1085,7 @@ static int smb3_reconfigure(struct fs_context *fc)
>         struct dentry *root = fc->root;
>         struct cifs_sb_info *cifs_sb = CIFS_SB(root->d_sb);
>         struct cifs_ses *ses = cifs_sb_master_tcon(cifs_sb)->ses;
> +       unsigned int old_chan_max;
>         unsigned int rsize = ctx->rsize, wsize = ctx->wsize;
>         char *new_password = NULL, *new_password2 = NULL;
>         bool need_recon = false;
> @@ -1170,9 +1171,6 @@ static int smb3_reconfigure(struct fs_context *fc)
>         if ((ctx->multichannel != cifs_sb->ctx->multichannel) ||
>             (ctx->max_channels != cifs_sb->ctx->max_channels)) {
>
> -               /* Synchronize ses->chan_max with the new mount context */
> -               smb3_sync_ses_chan_max(ses, ctx->max_channels);
> -               /* Now update the session's channels to match the new configuration */
>                 /* Prevent concurrent scaling operations */
>                 spin_lock(&ses->ses_lock);
>                 if (ses->flags & CIFS_SES_FLAG_SCALE_CHANNELS) {
> @@ -1183,16 +1181,31 @@ static int smb3_reconfigure(struct fs_context *fc)
>                 ses->flags |= CIFS_SES_FLAG_SCALE_CHANNELS;
>                 spin_unlock(&ses->ses_lock);
>
> +               old_chan_max = ses->chan_max;
> +               /* Synchronize ses->chan_max with the new mount context */
> +               smb3_sync_ses_chan_max(ses, ctx->max_channels);
> +
>                 mutex_unlock(&ses->session_mutex);
>
>                 rc = smb3_update_ses_channels(ses, ses->server,
>                                                false /* from_reconnect */,
>                                                false /* disable_mchan */);
>
> +               /*
> +                * On failure, restore chan_max while still holding
> +                * CIFS_SES_FLAG_SCALE_CHANNELS so a concurrent reconfigure
> +                * cannot observe or race with the rollback.
> +                */
> +               if (rc < 0)
> +                       smb3_sync_ses_chan_max(ses, old_chan_max);
> +
>                 /* Clear scaling flag after operation */
>                 spin_lock(&ses->ses_lock);
>                 ses->flags &= ~CIFS_SES_FLAG_SCALE_CHANNELS;
>                 spin_unlock(&ses->ses_lock);
> +
> +               if (rc < 0)
> +                       return rc;
>         } else {
>                 mutex_unlock(&ses->session_mutex);
>         }
> --
> 2.43.0
>
>

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v2] smb/client: fix chan_max and UNC state corruption in smb3_reconfigure
  2026-04-16  2:28 [PATCH] smb/client: fix chan_max race and rollback in smb3_reconfigure DaeMyung Kang
  2026-04-16  4:56 ` RAJASI MANDAL
@ 2026-04-16 15:18 ` DaeMyung Kang
  2026-04-16 15:48   ` Henrique Carvalho
                     ` (2 more replies)
  1 sibling, 3 replies; 9+ messages in thread
From: DaeMyung Kang @ 2026-04-16 15:18 UTC (permalink / raw)
  To: sfrench
  Cc: pc, ronniesahlberg, sprasad, tom, bharathsm, rajasimandalos,
	rajasimandal, linux-cifs, linux-kernel, DaeMyung Kang

smb3_reconfigure() has four related bugs when handling a
multichannel remount that leave ses->chan_max or cifs_sb->ctx
inconsistent with the actual state.

1) smb3_sync_ses_chan_max() is called before acquiring
   CIFS_SES_FLAG_SCALE_CHANNELS. If a concurrent operation (e.g.
   smb2_reconnect) holds the flag, the current thread takes the
   loser path and returns -EINVAL, but ses->chan_max has already
   been updated to the new value. chan_max is then inconsistent
   with the actual channel state.

2) When smb3_update_ses_channels() fails, ses->chan_max is not
   rolled back and the error is not propagated to the caller.
   mount returns success even though the channel configuration
   was not applied, and repeated failures cause chan_max to
   drift further from reality.

3) Earlier in smb3_reconfigure(), STEAL_STRING moves UNC, source
   and username from cifs_sb->ctx into ctx, setting
   cifs_sb->ctx->UNC to NULL until smb3_fs_context_dup() copies
   them back near the end of the function. Both the existing
   CIFS_SES_FLAG_SCALE_CHANNELS loser-path 'return -EINVAL' and a
   naive 'if (rc < 0) return rc;' to propagate the failure above
   exit in this window. A single failed multichannel remount
   therefore permanently nulls cifs_sb->ctx->UNC; /proc/mounts
   shows the device as "none" and every subsequent
   mount.cifs-based remount is rejected by
   smb3_verify_reconfigure_ctx() because mount.cifs passes that
   "none" back as the new UNC.

4) If the multichannel update fails but smb3_fs_context_dup() is
   still allowed to run with the new ctx values, the rejected
   new max_channels is written into cifs_sb->ctx, undoing the
   ses->chan_max rollback from (2) and creating a fresh
   cifs_sb->ctx vs ses->chan_max mismatch.

Fix all four by:
 - Moving smb3_sync_ses_chan_max() after the SCALE_CHANNELS
   acquire so the loser path cannot corrupt chan_max.
 - Capturing old_chan_max before the sync and restoring it on
   failure while still holding SCALE_CHANNELS so a concurrent
   reconfigure cannot race with the rollback.
 - Recording any multichannel-path failure in a local mchan_rc
   and routing it through a common 'out:' label so every exit
   reaches smb3_fs_context_dup() and cifs_sb->ctx is restored.
 - Before the dup, restoring ctx->multichannel and
   ctx->max_channels from cifs_sb->ctx on mchan_rc so the dup
   does not desync cifs_sb->ctx from the already-rolled-back
   ses->chan_max.
 - Returning mchan_rc to userspace so a failed scaling remount
   surfaces as an error.
 - Surfacing smb3_fs_context_dup() failure in preference to
   mchan_rc when both occur. The dup is the final step of the
   ctx restore and runs after smb3_cleanup_fs_context_contents()
   has already freed cifs_sb->ctx's strings, so a dup failure
   (e.g. -ENOMEM) leaves cifs_sb->ctx partially restored with
   some strings still NULL. Reporting the scaling error in that
   case would hide the real recovery status from userspace;
   reporting the dup error exposes it so the caller can react
   accordingly.

Note: smb3_reconfigure() is not fully transactional. Credentials
(ses->password and ses->password2) are committed before the
multichannel block, so a failed multichannel remount can still
leave ses->password updated while reporting an error to
userspace. Making the entire function atomic is a larger
refactor and is left for a separate patch.

Tested with a QEMU VM (ksmbd + cifs) using module-parameter-based
fault injection:
 - Forced smb3_update_ses_channels() failure via module param
   and verified ses->chan_max is preserved at the old value
   after remount failure.
 - Pre-set CIFS_SES_FLAG_SCALE_CHANNELS before entering the
   scaling path and verified the loser path returns -EINVAL
   without corrupting ses->chan_max.
 - Repeated 19 forced-failure remounts with varying max_channels
   (range 2-8) and confirmed no chan_max drift.
 - After each failure path, verified /proc/mounts continues to
   show the original UNC (//127.0.0.1/share) so subsequent
   remounts are accepted.
All scenarios pass with the patch and fail without it.

Reported-by: RAJASI MANDAL <rajasimandalos@gmail.com>
Closes: https://lore.kernel.org/lkml/CAEY6_V1+dzW3OD5zqXhsWyXwrDTrg5tAMGZ1AJ7_GAuRE+aevA@mail.gmail.com/
Fixes: ef529f655a2c ("cifs: client: allow changing multichannel mount options on remount")
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
---
 fs/smb/client/fs_context.c | 48 ++++++++++++++++++++++++++++++++------
 1 file changed, 41 insertions(+), 7 deletions(-)

diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c
index b9544eb0381b..9efd73ce6910 100644
--- a/fs/smb/client/fs_context.c
+++ b/fs/smb/client/fs_context.c
@@ -1085,10 +1085,11 @@ static int smb3_reconfigure(struct fs_context *fc)
 	struct dentry *root = fc->root;
 	struct cifs_sb_info *cifs_sb = CIFS_SB(root->d_sb);
 	struct cifs_ses *ses = cifs_sb_master_tcon(cifs_sb)->ses;
+	unsigned int old_chan_max;
 	unsigned int rsize = ctx->rsize, wsize = ctx->wsize;
 	char *new_password = NULL, *new_password2 = NULL;
 	bool need_recon = false;
-	int rc;
+	int rc, mchan_rc = 0;
 
 	if (ses->expired_pwd)
 		need_recon = true;
@@ -1170,25 +1171,37 @@ static int smb3_reconfigure(struct fs_context *fc)
 	if ((ctx->multichannel != cifs_sb->ctx->multichannel) ||
 	    (ctx->max_channels != cifs_sb->ctx->max_channels)) {
 
-		/* Synchronize ses->chan_max with the new mount context */
-		smb3_sync_ses_chan_max(ses, ctx->max_channels);
-		/* Now update the session's channels to match the new configuration */
 		/* Prevent concurrent scaling operations */
 		spin_lock(&ses->ses_lock);
 		if (ses->flags & CIFS_SES_FLAG_SCALE_CHANNELS) {
 			spin_unlock(&ses->ses_lock);
 			mutex_unlock(&ses->session_mutex);
-			return -EINVAL;
+			mchan_rc = -EINVAL;
+			goto out;
 		}
 		ses->flags |= CIFS_SES_FLAG_SCALE_CHANNELS;
 		spin_unlock(&ses->ses_lock);
 
+		old_chan_max = ses->chan_max;
+		/* Synchronize ses->chan_max with the new mount context */
+		smb3_sync_ses_chan_max(ses, ctx->max_channels);
+
 		mutex_unlock(&ses->session_mutex);
 
 		rc = smb3_update_ses_channels(ses, ses->server,
 					       false /* from_reconnect */,
 					       false /* disable_mchan */);
 
+		/*
+		 * On failure, restore chan_max while still holding
+		 * CIFS_SES_FLAG_SCALE_CHANNELS so a concurrent reconfigure
+		 * cannot observe or race with the rollback.
+		 */
+		if (rc < 0) {
+			smb3_sync_ses_chan_max(ses, old_chan_max);
+			mchan_rc = rc;
+		}
+
 		/* Clear scaling flag after operation */
 		spin_lock(&ses->ses_lock);
 		ses->flags &= ~CIFS_SES_FLAG_SCALE_CHANNELS;
@@ -1197,6 +1210,7 @@ static int smb3_reconfigure(struct fs_context *fc)
 		mutex_unlock(&ses->session_mutex);
 	}
 
+out:
 	STEAL_STRING(cifs_sb, ctx, domainname);
 	STEAL_STRING(cifs_sb, ctx, nodename);
 	STEAL_STRING(cifs_sb, ctx, iocharset);
@@ -1205,12 +1219,32 @@ static int smb3_reconfigure(struct fs_context *fc)
 	ctx->rsize = rsize ? CIFS_ALIGN_RSIZE(fc, rsize) : cifs_sb->ctx->rsize;
 	ctx->wsize = wsize ? CIFS_ALIGN_WSIZE(fc, wsize) : cifs_sb->ctx->wsize;
 
+	/*
+	 * If the multichannel update failed, restore the old multichannel
+	 * settings in ctx so smb3_fs_context_dup() does not desync
+	 * cifs_sb->ctx from ses->chan_max (which was already rolled back).
+	 */
+	if (mchan_rc) {
+		ctx->multichannel = cifs_sb->ctx->multichannel;
+		ctx->max_channels = cifs_sb->ctx->max_channels;
+	}
+
 	smb3_cleanup_fs_context_contents(cifs_sb->ctx);
 	rc = smb3_fs_context_dup(cifs_sb->ctx, ctx);
 	smb3_update_mnt_flags(cifs_sb);
+
+	/*
+	 * If smb3_fs_context_dup() failed, cifs_sb->ctx is left in a
+	 * partially-restored state after the cleanup above. Surface that
+	 * failure to the caller before any mchan_rc, since it reflects the
+	 * real recovery status of cifs_sb->ctx rather than the scaling error.
+	 */
+	if (rc)
+		return rc;
+	if (mchan_rc)
+		return mchan_rc;
 #ifdef CONFIG_CIFS_DFS_UPCALL
-	if (!rc)
-		rc = dfs_cache_remount_fs(cifs_sb);
+	rc = dfs_cache_remount_fs(cifs_sb);
 #endif
 
 	return rc;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH v2] smb/client: fix chan_max and UNC state corruption in smb3_reconfigure
  2026-04-16 15:18 ` [PATCH v2] smb/client: fix chan_max and UNC state corruption " DaeMyung Kang
@ 2026-04-16 15:48   ` Henrique Carvalho
  2026-04-16 20:45   ` [PATCH v3] smb/client: fix state corruption in smb3_reconfigure multichannel path DaeMyung Kang
  2026-04-29 12:10   ` [PATCH v4] smb: client: fix state-consistency bugs in smb3_reconfigure() " DaeMyung Kang
  2 siblings, 0 replies; 9+ messages in thread
From: Henrique Carvalho @ 2026-04-16 15:48 UTC (permalink / raw)
  To: DaeMyung Kang
  Cc: sfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	rajasimandalos, rajasimandal, linux-cifs, linux-kernel

Isn't this a policy change? If multichannel fails all the other changes
up to that point are persisted (i.e. password change). Despite that, we
will be returning an error to the user, but have a state that we have no
idea what actually happened. Did the password change fail? Did the
multichannel change fail?

Before we had a best-effort remount and after this change we will be
having a partial success reported as failure.

So I would say that, for this change, if this is indeed a bug,
no need for mchan_rc, roll back all the changes that happened during
smb3_reconfigure.

Futhermore, we need to handle mchan update ses channels just like we do
during mount, because remount could end up being delayed the same way as
mount, AFAICS. I have a patch for this with the series of that
serialization smb3_update_ses_channels patch.

-- 
Henrique
SUSE Labs

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v3] smb/client: fix state corruption in smb3_reconfigure multichannel path
  2026-04-16 15:18 ` [PATCH v2] smb/client: fix chan_max and UNC state corruption " DaeMyung Kang
  2026-04-16 15:48   ` Henrique Carvalho
@ 2026-04-16 20:45   ` DaeMyung Kang
  2026-04-29  3:17     ` RAJASI MANDAL
  2026-04-29 12:10   ` [PATCH v4] smb: client: fix state-consistency bugs in smb3_reconfigure() " DaeMyung Kang
  2 siblings, 1 reply; 9+ messages in thread
From: DaeMyung Kang @ 2026-04-16 20:45 UTC (permalink / raw)
  To: sfrench
  Cc: pc, ronniesahlberg, sprasad, tom, bharathsm, rajasimandalos,
	rajasimandal, henrique.carvalho, linux-cifs, linux-kernel,
	DaeMyung Kang

smb3_reconfigure() has several state-consistency bugs when handling
a multichannel remount that leave ses->chan_max or cifs_sb->ctx
inconsistent with the actual state. This patch repairs internal
state only; the userspace-visible return value of
smb3_reconfigure() is preserved to match the pre-patch behaviour:
the concurrent-scale loser path still returns -EINVAL, and a
failed smb3_update_ses_channels() is not newly propagated to
userspace by this patch.

Bugs addressed:

1) smb3_sync_ses_chan_max() is called before acquiring
   CIFS_SES_FLAG_SCALE_CHANNELS. If a concurrent operation (e.g.
   smb2_reconnect) holds the flag, the current thread takes the
   loser path and returns -EINVAL, but ses->chan_max has already
   been updated to the new value. chan_max is then out of sync
   with the actual channel state.

2) When smb3_update_ses_channels() fails, ses->chan_max is not
   rolled back. Repeated failures cause chan_max to drift further
   from reality, and subsequent reconnect/reconfigure paths use
   the drifted target.

3) Earlier in smb3_reconfigure(), STEAL_STRING moves UNC, source
   and username from cifs_sb->ctx into ctx, setting
   cifs_sb->ctx->UNC to NULL until smb3_fs_context_dup() copies
   them back near the end of the function. The pre-existing
   CIFS_SES_FLAG_SCALE_CHANNELS loser-path 'return -EINVAL' exits
   inside this window. A loser-path failure therefore permanently
   nulls cifs_sb->ctx->UNC; /proc/mounts shows the device as
   "none" and every subsequent mount.cifs-based remount is
   rejected by smb3_verify_reconfigure_ctx() because mount.cifs
   passes that "none" back as the new UNC.

4) Once (2) rolls ses->chan_max back to the old value on update
   failure, smb3_fs_context_dup() would still copy the rejected
   new ctx->max_channels into cifs_sb->ctx, creating a fresh
   cifs_sb->ctx vs ses->chan_max mismatch. This must be handled
   together with (2) to keep the rollback complete.

Fix all four by:

 - Moving smb3_sync_ses_chan_max() after the SCALE_CHANNELS
   acquire so the loser path cannot corrupt chan_max.

 - Capturing old_chan_max before the sync and restoring it on
   failure while still holding SCALE_CHANNELS so a concurrent
   reconfigure cannot race with the rollback.

 - Recording any multichannel-path failure in a local mchan_rc
   and routing the loser path through a common 'out:' label so
   every exit reaches smb3_fs_context_dup() and cifs_sb->ctx is
   restored. mchan_rc is used only as internal control flow.

 - Before the dup, restoring ctx->multichannel and
   ctx->max_channels from cifs_sb->ctx on mchan_rc so the dup
   does not desync cifs_sb->ctx from the already-rolled-back
   ses->chan_max.

 - Tracking the concurrent-scale loser path with a separate
   scale_busy flag. After cifs_sb->ctx is fully restored, the
   return value is forced to -EINVAL for that path so userspace
   continues to see the pre-patch rejection. dup /
   dfs_cache_remount_fs() failures still take precedence because
   they reflect real state recovery errors.

Deliberately not changed by this patch: the return value of
smb3_reconfigure() on smb3_update_ses_channels() failure. That
path is not newly propagated to userspace here, matching the
asynchronous model used by the mount path (mchan_mount_work_fn).
Those return-semantics and deferred-handling questions are left
to follow-up discussion/patches.

smb3_reconfigure() is not fully transactional: ses->password and
ses->password2 are committed before the multichannel block, so
unrelated earlier state changes may still be visible after a
failed multichannel remount. That is a structural property of
the function and out of scope here.

Tested with a QEMU VM (ksmbd + cifs) using module-parameter
based fault injection:
 - Forced smb3_update_ses_channels() failure via module param
   and verified ses->chan_max is preserved at the old value
   after the remount path runs.
 - Pre-set CIFS_SES_FLAG_SCALE_CHANNELS before entering the
   scaling path and verified the loser path still returns
   -EINVAL, no longer corrupts ses->chan_max, and no longer nulls
   cifs_sb->ctx->UNC.
 - Repeated 19 forced-failure remounts with varying max_channels
   (range 2-8) and confirmed no chan_max drift.
 - After each failure path, verified /proc/mounts continues to
   show the original UNC (//127.0.0.1/share) so subsequent
   remounts are accepted.

Reported-by: RAJASI MANDAL <rajasimandalos@gmail.com>
Closes: https://lore.kernel.org/lkml/CAEY6_V1+dzW3OD5zqXhsWyXwrDTrg5tAMGZ1AJ7_GAuRE+aevA@mail.gmail.com/
Link: https://lore.kernel.org/lkml/xkr2dlvgibq5j6gkcxd3yhhnj4atgxw2uy4eug2pxm7wy7nbms@iq6cf5taa65v/
Fixes: ef529f655a2c ("cifs: client: allow changing multichannel mount options on remount")
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
---
v3: (feedback from Henrique Carvalho)
 - Drop propagation of smb3_update_ses_channels() failure to
   userspace; preserve the pre-patch best-effort semantics on that
   path.
 - Keep the pre-existing CIFS_SES_FLAG_SCALE_CHANNELS loser-path
   -EINVAL via a separate scale_busy flag so that return is not
   silently converted into success.
 - Reword commit message to describe accurately what userspace-
   visible semantics are preserved and what changed internally.

v2: (feedback from Rajasi Mandal)
 - Route loser-path and update-failure exits through a common
   'out:' label so smb3_fs_context_dup() always runs and
   cifs_sb->ctx->UNC is restored after STEAL_STRING.
 - Restore ctx->multichannel/ctx->max_channels from cifs_sb->ctx
   before the dup so dup does not re-desync cifs_sb->ctx from the
   rolled-back ses->chan_max.

 fs/smb/client/fs_context.c | 46 +++++++++++++++++++++++++++++++++-----
 1 file changed, 41 insertions(+), 5 deletions(-)

diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c
index b9544eb0381b..aaa364a3f60d 100644
--- a/fs/smb/client/fs_context.c
+++ b/fs/smb/client/fs_context.c
@@ -1085,10 +1085,12 @@ static int smb3_reconfigure(struct fs_context *fc)
 	struct dentry *root = fc->root;
 	struct cifs_sb_info *cifs_sb = CIFS_SB(root->d_sb);
 	struct cifs_ses *ses = cifs_sb_master_tcon(cifs_sb)->ses;
+	unsigned int old_chan_max;
 	unsigned int rsize = ctx->rsize, wsize = ctx->wsize;
 	char *new_password = NULL, *new_password2 = NULL;
 	bool need_recon = false;
-	int rc;
+	bool scale_busy = false;
+	int rc, mchan_rc = 0;
 
 	if (ses->expired_pwd)
 		need_recon = true;
@@ -1170,25 +1172,38 @@ static int smb3_reconfigure(struct fs_context *fc)
 	if ((ctx->multichannel != cifs_sb->ctx->multichannel) ||
 	    (ctx->max_channels != cifs_sb->ctx->max_channels)) {
 
-		/* Synchronize ses->chan_max with the new mount context */
-		smb3_sync_ses_chan_max(ses, ctx->max_channels);
-		/* Now update the session's channels to match the new configuration */
 		/* Prevent concurrent scaling operations */
 		spin_lock(&ses->ses_lock);
 		if (ses->flags & CIFS_SES_FLAG_SCALE_CHANNELS) {
 			spin_unlock(&ses->ses_lock);
 			mutex_unlock(&ses->session_mutex);
-			return -EINVAL;
+			scale_busy = true;
+			mchan_rc = -EINVAL;
+			goto out;
 		}
 		ses->flags |= CIFS_SES_FLAG_SCALE_CHANNELS;
 		spin_unlock(&ses->ses_lock);
 
+		old_chan_max = ses->chan_max;
+		/* Synchronize ses->chan_max with the new mount context */
+		smb3_sync_ses_chan_max(ses, ctx->max_channels);
+
 		mutex_unlock(&ses->session_mutex);
 
 		rc = smb3_update_ses_channels(ses, ses->server,
 					       false /* from_reconnect */,
 					       false /* disable_mchan */);
 
+		/*
+		 * On failure, restore chan_max while still holding
+		 * CIFS_SES_FLAG_SCALE_CHANNELS so a concurrent reconfigure
+		 * cannot observe or race with the rollback.
+		 */
+		if (rc < 0) {
+			smb3_sync_ses_chan_max(ses, old_chan_max);
+			mchan_rc = rc;
+		}
+
 		/* Clear scaling flag after operation */
 		spin_lock(&ses->ses_lock);
 		ses->flags &= ~CIFS_SES_FLAG_SCALE_CHANNELS;
@@ -1197,6 +1212,7 @@ static int smb3_reconfigure(struct fs_context *fc)
 		mutex_unlock(&ses->session_mutex);
 	}
 
+out:
 	STEAL_STRING(cifs_sb, ctx, domainname);
 	STEAL_STRING(cifs_sb, ctx, nodename);
 	STEAL_STRING(cifs_sb, ctx, iocharset);
@@ -1205,6 +1221,16 @@ static int smb3_reconfigure(struct fs_context *fc)
 	ctx->rsize = rsize ? CIFS_ALIGN_RSIZE(fc, rsize) : cifs_sb->ctx->rsize;
 	ctx->wsize = wsize ? CIFS_ALIGN_WSIZE(fc, wsize) : cifs_sb->ctx->wsize;
 
+	/*
+	 * If the multichannel update failed, restore the old multichannel
+	 * settings in ctx so smb3_fs_context_dup() does not desync
+	 * cifs_sb->ctx from ses->chan_max (which was already rolled back).
+	 */
+	if (mchan_rc) {
+		ctx->multichannel = cifs_sb->ctx->multichannel;
+		ctx->max_channels = cifs_sb->ctx->max_channels;
+	}
+
 	smb3_cleanup_fs_context_contents(cifs_sb->ctx);
 	rc = smb3_fs_context_dup(cifs_sb->ctx, ctx);
 	smb3_update_mnt_flags(cifs_sb);
@@ -1213,6 +1239,16 @@ static int smb3_reconfigure(struct fs_context *fc)
 		rc = dfs_cache_remount_fs(cifs_sb);
 #endif
 
+	/*
+	 * Preserve the pre-existing loser-path semantics: a concurrent
+	 * scaling operation causes the remount to be rejected with
+	 * -EINVAL. smb3_fs_context_dup() / dfs_cache_remount_fs()
+	 * failures take precedence because they reflect real state
+	 * recovery errors. Other multichannel failures remain best-effort.
+	 */
+	if (!rc && scale_busy)
+		rc = -EINVAL;
+
 	return rc;
 }
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH v3] smb/client: fix state corruption in smb3_reconfigure multichannel path
  2026-04-16 20:45   ` [PATCH v3] smb/client: fix state corruption in smb3_reconfigure multichannel path DaeMyung Kang
@ 2026-04-29  3:17     ` RAJASI MANDAL
  2026-04-29 12:12       ` CharSyam
  0 siblings, 1 reply; 9+ messages in thread
From: RAJASI MANDAL @ 2026-04-29  3:17 UTC (permalink / raw)
  To: DaeMyung Kang
  Cc: sfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	rajasimandal, henrique.carvalho, linux-cifs, linux-kernel

Hi DaeMyung,

Thanks for the v3. One minor suggestion

> + old_chan_max = ses->chan_max;
> + /* Synchronize ses->chan_max with the new mount context */
> + smb3_sync_ses_chan_max(ses, ctx->max_channels);

This reads ses->chan_max without holding chan_lock.
smb3_sync_ses_chan_max() itself takes chan_lock for the write, and
cifs_try_adding_channels() / cifs_chan_skip_or_disable() also access
chan_max under chan_lock. So there is a potential data race between
this unlocked read and a concurrent reconnect path writing chan_max.

Other than that, this  patch looks good to me.

Thanks,
Rajasi

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v4] smb: client: fix state-consistency bugs in smb3_reconfigure() multichannel path
  2026-04-16 15:18 ` [PATCH v2] smb/client: fix chan_max and UNC state corruption " DaeMyung Kang
  2026-04-16 15:48   ` Henrique Carvalho
  2026-04-16 20:45   ` [PATCH v3] smb/client: fix state corruption in smb3_reconfigure multichannel path DaeMyung Kang
@ 2026-04-29 12:10   ` DaeMyung Kang
  2026-04-29 20:45     ` Henrique Carvalho
  2 siblings, 1 reply; 9+ messages in thread
From: DaeMyung Kang @ 2026-04-29 12:10 UTC (permalink / raw)
  To: Steve French, Paulo Alcantara
  Cc: Ronnie Sahlberg, Shyam Prasad N, Tom Talpey, Bharath SM,
	Rajasi Mandal, Rajasi Mandal, linux-cifs, samba-technical,
	linux-kernel, DaeMyung Kang

smb3_reconfigure() has several state-consistency bugs when handling
a multichannel remount that leave ses->chan_max or cifs_sb->ctx
inconsistent with the actual state. This patch repairs internal
state only; the userspace-visible return value of
smb3_reconfigure() is preserved to match the pre-patch behaviour:
the concurrent-scale loser path still returns -EINVAL, and a
failed smb3_update_ses_channels() is not newly propagated to
userspace by this patch.

Bugs addressed:

1) Before this patch, smb3_sync_ses_chan_max() is called before
   acquiring CIFS_SES_FLAG_SCALE_CHANNELS. If a concurrent
   operation (e.g. smb2_reconnect) holds the flag, the current
   thread takes the loser path and returns -EINVAL, but
   ses->chan_max has already been updated to the new value.
   chan_max is then out of sync with the actual channel state.

2) When smb3_update_ses_channels() fails, ses->chan_max is not
   rolled back. Repeated failures cause chan_max to drift further
   from reality, and subsequent reconnect/reconfigure paths use
   the drifted target.

3) Earlier in smb3_reconfigure(), STEAL_STRING moves UNC, source
   and username from cifs_sb->ctx into ctx, setting
   cifs_sb->ctx->UNC to NULL until smb3_fs_context_dup() copies
   them back near the end of the function. The pre-existing
   CIFS_SES_FLAG_SCALE_CHANNELS loser-path 'return -EINVAL' exits
   inside this window. A loser-path failure therefore permanently
   nulls cifs_sb->ctx->UNC; /proc/mounts shows the device as
   "none" and every subsequent mount.cifs-based remount is
   rejected by smb3_verify_reconfigure_ctx() because mount.cifs
   passes that "none" back as the new UNC.

4) Once (2) rolls ses->chan_max back to the old value on update
   failure, smb3_fs_context_dup() would still copy the rejected
   new ctx->max_channels into cifs_sb->ctx, creating a fresh
   cifs_sb->ctx vs ses->chan_max mismatch. This must be handled
   together with (2) to keep the rollback complete.

Fix all four by:

 - Moving smb3_sync_ses_chan_max() after the SCALE_CHANNELS
   acquire so the loser path cannot corrupt chan_max.

 - Capturing old_chan_max atomically with the sync via the new
   smb3_sync_ses_chan_max() return value (under chan_lock), and
   restoring it on failure while still holding SCALE_CHANNELS so
   a concurrent reconfigure cannot race with the rollback.

 - Recording any multichannel-path failure in a local mchan_rc
   and routing the loser path through a common 'out:' label so
   every exit reaches smb3_fs_context_dup() and cifs_sb->ctx is
   restored. mchan_rc is used only as internal control flow.

 - Before the dup, restoring ctx->multichannel,
   ctx->multichannel_specified, ctx->max_channels and
   ctx->max_channels_specified from cifs_sb->ctx on mchan_rc so
   the dup does not desync cifs_sb->ctx from the already-rolled-
   back ses->chan_max (the *_specified bits must travel with the
   values to avoid a "user-specified" flag mismatching the
   restored value).

 - Tracking the concurrent-scale loser path with a separate
   scale_busy flag. After cifs_sb->ctx is fully restored, the
   return value is forced to -EINVAL for that path so userspace
   continues to see the pre-patch rejection. dup /
   dfs_cache_remount_fs() failures still take precedence because
   they reflect real state recovery errors.

Deliberately not changed by this patch: the return value of
smb3_reconfigure() on smb3_update_ses_channels() failure. That
path is not newly propagated to userspace here, matching the
asynchronous model used by the mount path (mchan_mount_work_fn).
Those return-semantics and deferred-handling questions are left
to follow-up discussion/patches.

smb3_reconfigure() is not fully transactional: ses->password and
ses->password2 are committed before the multichannel block, so
unrelated earlier state changes may still be visible after a
failed multichannel remount. That is a structural property of
the function and out of scope here.

Tested with a QEMU VM (ksmbd + cifs) using module-parameter
based fault injection:
 - Forced smb3_update_ses_channels() failure via module param
   and verified ses->chan_max is preserved at the old value
   after the remount path runs.
 - Pre-set CIFS_SES_FLAG_SCALE_CHANNELS before entering the
   scaling path and verified the loser path still returns
   -EINVAL, no longer corrupts ses->chan_max, and no longer nulls
   cifs_sb->ctx->UNC.
 - Repeated dozens of forced-failure remounts with varying
   max_channels (range 2-8) and confirmed no chan_max drift.
 - After each failure path, verified /proc/mounts continues to
   show the original UNC (//127.0.0.1/share) so subsequent
   remounts are accepted.

Reported-by: RAJASI MANDAL <rajasimandalos@gmail.com>
Closes: https://lore.kernel.org/lkml/CAEY6_V1+dzW3OD5zqXhsWyXwrDTrg5tAMGZ1AJ7_GAuRE+aevA@mail.gmail.com/
Link: https://lore.kernel.org/lkml/xkr2dlvgibq5j6gkcxd3yhhnj4atgxw2uy4eug2pxm7wy7nbms@iq6cf5taa65v/
Fixes: ef529f655a2c ("cifs: client: allow changing multichannel mount options on remount")
Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
---
v4: (feedback from Rajasi Mandal)
 - Fix unlocked read of ses->chan_max when capturing the rollback
   value. smb3_sync_ses_chan_max() now returns the previous
   chan_max so the read+write happens atomically under chan_lock,
   removing the data race against cifs_try_adding_channels() /
   cifs_chan_skip_or_disable() which also access chan_max under
   chan_lock.
 - Switch smb3_sync_ses_chan_max()'s parameter/return type to
   size_t to match struct cifs_ses::chan_max.
 - On the multichannel rollback path, also restore
   ctx->multichannel_specified and ctx->max_channels_specified
   from cifs_sb->ctx so the user-specified flags do not desync
   from the restored values when smb3_fs_context_dup() copies
   ctx back.

v3: (feedback from Henrique Carvalho)
 - Drop propagation of smb3_update_ses_channels() failure to
   userspace; preserve the pre-patch best-effort semantics on that
   path.
 - Keep the pre-existing CIFS_SES_FLAG_SCALE_CHANNELS loser-path
   -EINVAL via a separate scale_busy flag so that return is not
   silently converted into success.
 - Reword commit message to describe accurately what userspace-
   visible semantics are preserved and what changed internally.

v2: (feedback from Rajasi Mandal)
 - Route loser-path and update-failure exits through a common
   'out:' label so smb3_fs_context_dup() always runs and
   cifs_sb->ctx->UNC is restored after STEAL_STRING.
 - Restore ctx->multichannel/ctx->max_channels from cifs_sb->ctx
   before the dup so dup does not re-desync cifs_sb->ctx from the
   rolled-back ses->chan_max.

 fs/smb/client/fs_context.c | 61 +++++++++++++++++++++++++++++++++++---------
 1 file changed, 52 insertions(+), 9 deletions(-)

diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c
index b9544eb0381b..252420bd7d5d 100644
--- a/fs/smb/client/fs_context.c
+++ b/fs/smb/client/fs_context.c
@@ -767,7 +767,7 @@ static int smb3_fs_context_parse_param(struct fs_context *fc,
 static int smb3_fs_context_parse_monolithic(struct fs_context *fc,
 					    void *data);
 static int smb3_get_tree(struct fs_context *fc);
-static void smb3_sync_ses_chan_max(struct cifs_ses *ses, unsigned int max_channels);
+static size_t smb3_sync_ses_chan_max(struct cifs_ses *ses, size_t max_channels);
 static int smb3_reconfigure(struct fs_context *fc);

 static const struct fs_context_operations smb3_fs_context_ops = {
@@ -1069,14 +1069,20 @@ int smb3_sync_session_ctx_passwords(struct cifs_sb_info *cifs_sb, struct cifs_se
  * @max_channels: new maximum number of channels to allow
  *
  * Updates the session's chan_max field to the new value, protecting the update
- * with the session's channel lock. This should be called whenever the maximum
- * allowed channels for a session changes (e.g., after a remount or reconfigure).
+ * with the session's channel lock, and returns the previous chan_max value.
+ * This should be called whenever the maximum allowed channels for a session
+ * changes (e.g., after a remount or reconfigure).
  */
-static void smb3_sync_ses_chan_max(struct cifs_ses *ses, unsigned int max_channels)
+static size_t smb3_sync_ses_chan_max(struct cifs_ses *ses, size_t max_channels)
 {
+	size_t old_chan_max;
+
 	spin_lock(&ses->chan_lock);
+	old_chan_max = ses->chan_max;
 	ses->chan_max = max_channels;
 	spin_unlock(&ses->chan_lock);
+
+	return old_chan_max;
 }

 static int smb3_reconfigure(struct fs_context *fc)
@@ -1085,10 +1091,12 @@ static int smb3_reconfigure(struct fs_context *fc)
 	struct dentry *root = fc->root;
 	struct cifs_sb_info *cifs_sb = CIFS_SB(root->d_sb);
 	struct cifs_ses *ses = cifs_sb_master_tcon(cifs_sb)->ses;
+	size_t old_chan_max;
 	unsigned int rsize = ctx->rsize, wsize = ctx->wsize;
 	char *new_password = NULL, *new_password2 = NULL;
 	bool need_recon = false;
-	int rc;
+	bool scale_busy = false;
+	int rc, mchan_rc = 0;

 	if (ses->expired_pwd)
 		need_recon = true;
@@ -1170,25 +1178,37 @@ static int smb3_reconfigure(struct fs_context *fc)
 	if ((ctx->multichannel != cifs_sb->ctx->multichannel) ||
 	    (ctx->max_channels != cifs_sb->ctx->max_channels)) {

-		/* Synchronize ses->chan_max with the new mount context */
-		smb3_sync_ses_chan_max(ses, ctx->max_channels);
-		/* Now update the session's channels to match the new configuration */
 		/* Prevent concurrent scaling operations */
 		spin_lock(&ses->ses_lock);
 		if (ses->flags & CIFS_SES_FLAG_SCALE_CHANNELS) {
 			spin_unlock(&ses->ses_lock);
 			mutex_unlock(&ses->session_mutex);
-			return -EINVAL;
+			scale_busy = true;
+			mchan_rc = -EINVAL;
+			goto out;
 		}
 		ses->flags |= CIFS_SES_FLAG_SCALE_CHANNELS;
 		spin_unlock(&ses->ses_lock);

+		/* Synchronize ses->chan_max with the new mount context */
+		old_chan_max = smb3_sync_ses_chan_max(ses, ctx->max_channels);
+
 		mutex_unlock(&ses->session_mutex);

 		rc = smb3_update_ses_channels(ses, ses->server,
 					       false /* from_reconnect */,
 					       false /* disable_mchan */);

+		/*
+		 * On failure, restore chan_max while still holding
+		 * CIFS_SES_FLAG_SCALE_CHANNELS so a concurrent reconfigure
+		 * cannot observe or race with the rollback.
+		 */
+		if (rc < 0) {
+			smb3_sync_ses_chan_max(ses, old_chan_max);
+			mchan_rc = rc;
+		}
+
 		/* Clear scaling flag after operation */
 		spin_lock(&ses->ses_lock);
 		ses->flags &= ~CIFS_SES_FLAG_SCALE_CHANNELS;
@@ -1197,6 +1217,7 @@ static int smb3_reconfigure(struct fs_context *fc)
 		mutex_unlock(&ses->session_mutex);
 	}

+out:
 	STEAL_STRING(cifs_sb, ctx, domainname);
 	STEAL_STRING(cifs_sb, ctx, nodename);
 	STEAL_STRING(cifs_sb, ctx, iocharset);
@@ -1205,6 +1226,18 @@ static int smb3_reconfigure(struct fs_context *fc)
 	ctx->rsize = rsize ? CIFS_ALIGN_RSIZE(fc, rsize) : cifs_sb->ctx->rsize;
 	ctx->wsize = wsize ? CIFS_ALIGN_WSIZE(fc, wsize) : cifs_sb->ctx->wsize;

+	/*
+	 * If the multichannel update failed, restore the old multichannel
+	 * settings in ctx so smb3_fs_context_dup() does not desync
+	 * cifs_sb->ctx from ses->chan_max (which was already rolled back).
+	 */
+	if (mchan_rc) {
+		ctx->multichannel = cifs_sb->ctx->multichannel;
+		ctx->multichannel_specified = cifs_sb->ctx->multichannel_specified;
+		ctx->max_channels = cifs_sb->ctx->max_channels;
+		ctx->max_channels_specified = cifs_sb->ctx->max_channels_specified;
+	}
+
 	smb3_cleanup_fs_context_contents(cifs_sb->ctx);
 	rc = smb3_fs_context_dup(cifs_sb->ctx, ctx);
 	smb3_update_mnt_flags(cifs_sb);
@@ -1213,6 +1246,16 @@ static int smb3_reconfigure(struct fs_context *fc)
 		rc = dfs_cache_remount_fs(cifs_sb);
 #endif

+	/*
+	 * Preserve the pre-existing loser-path semantics: a concurrent
+	 * scaling operation causes the remount to be rejected with
+	 * -EINVAL. smb3_fs_context_dup() / dfs_cache_remount_fs()
+	 * failures take precedence because they reflect real state
+	 * recovery errors. Other multichannel failures remain best-effort.
+	 */
+	if (!rc && scale_busy)
+		rc = -EINVAL;
+
 	return rc;
 }

--
2.43.0


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH v3] smb/client: fix state corruption in smb3_reconfigure multichannel path
  2026-04-29  3:17     ` RAJASI MANDAL
@ 2026-04-29 12:12       ` CharSyam
  0 siblings, 0 replies; 9+ messages in thread
From: CharSyam @ 2026-04-29 12:12 UTC (permalink / raw)
  To: RAJASI MANDAL
  Cc: sfrench, pc, ronniesahlberg, sprasad, tom, bharathsm,
	rajasimandal, henrique.carvalho, linux-cifs, linux-kernel

Hi Rajasi,

Thanks a lot for the careful review — you're right, that read of
ses->chan_max was unprotected and races with chan_lock writers in
cifs_try_adding_channels() / cifs_chan_skip_or_disable().

v4 fixes this by folding the read into smb3_sync_ses_chan_max()
itself: the helper now returns the previous chan_max value, so the
read+write happens atomically under chan_lock. The caller no longer
reads ses->chan_max outside the lock. I also switched the helper's
parameter/return type to size_t to match struct cifs_ses::chan_max.

While I was at it, I also restored ctx->multichannel_specified and
ctx->max_channels_specified together
with multichannel / max_channels on the rollback path,
so the user-specified flags don't end up out of sync with
the restored values once smb3_fs_context_dup() copies the ctx back.
v4 just went out — would appreciate another look when you get a chance.

Thanks,
DaeMyung


2026년 4월 29일 (수) 오후 12:17, RAJASI MANDAL <rajasimandalos@gmail.com>님이 작성:
>
> Hi DaeMyung,
>
> Thanks for the v3. One minor suggestion
>
> > + old_chan_max = ses->chan_max;
> > + /* Synchronize ses->chan_max with the new mount context */
> > + smb3_sync_ses_chan_max(ses, ctx->max_channels);
>
> This reads ses->chan_max without holding chan_lock.
> smb3_sync_ses_chan_max() itself takes chan_lock for the write, and
> cifs_try_adding_channels() / cifs_chan_skip_or_disable() also access
> chan_max under chan_lock. So there is a potential data race between
> this unlocked read and a concurrent reconnect path writing chan_max.
>
> Other than that, this  patch looks good to me.
>
> Thanks,
> Rajasi

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v4] smb: client: fix state-consistency bugs in smb3_reconfigure() multichannel path
  2026-04-29 12:10   ` [PATCH v4] smb: client: fix state-consistency bugs in smb3_reconfigure() " DaeMyung Kang
@ 2026-04-29 20:45     ` Henrique Carvalho
  0 siblings, 0 replies; 9+ messages in thread
From: Henrique Carvalho @ 2026-04-29 20:45 UTC (permalink / raw)
  To: DaeMyung Kang
  Cc: Steve French, Paulo Alcantara, Ronnie Sahlberg, Shyam Prasad N,
	Tom Talpey, Bharath SM, Rajasi Mandal, Rajasi Mandal, linux-cifs,
	samba-technical, linux-kernel

Hi DaeMyung,

Thank you for the update.

This version is better than the previous one, but I think it still needs
some work.

First, `mchan_rc` appears to be vestigial in its current form, so I do
not think it should stay.

Second, I think remount/reconfigure should be all-or-nothing. If any
part of the reconfigure attempt fails, we should not commit the rest of
the changes, otherwise userspace gets an error without a clear view of
what actually took effect. As written, I do not understand why
`scale_busy` is handled specially while other failure paths are not.

Lastly, please shorten the commit message. It would be better to follow
the style of nearby CIFS commit logs.

Thanks,

Henrique

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2026-04-29 20:46 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-16  2:28 [PATCH] smb/client: fix chan_max race and rollback in smb3_reconfigure DaeMyung Kang
2026-04-16  4:56 ` RAJASI MANDAL
2026-04-16 15:18 ` [PATCH v2] smb/client: fix chan_max and UNC state corruption " DaeMyung Kang
2026-04-16 15:48   ` Henrique Carvalho
2026-04-16 20:45   ` [PATCH v3] smb/client: fix state corruption in smb3_reconfigure multichannel path DaeMyung Kang
2026-04-29  3:17     ` RAJASI MANDAL
2026-04-29 12:12       ` CharSyam
2026-04-29 12:10   ` [PATCH v4] smb: client: fix state-consistency bugs in smb3_reconfigure() " DaeMyung Kang
2026-04-29 20:45     ` Henrique Carvalho

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox