* [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
* 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
* [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 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