[PATCH v2 1/9] smb: client: block non-reconfigurable option changes on remount

Rajasi Mandal
2026-04-13T07:33:01Z

Options like seal, sign, vers, ip, rdma and others require tearing
down the connection to take effect, so they cannot be changed on
remount. Previously they were silently accepted and ignored.

Add checks in smb3_verify_reconfigure_ctx() to reject changes to
these options on remount. Options shown in /proc/mounts use a bare
!= comparison since libmount feeds them back automatically. Options
not shown in /proc/mounts use an 'if (new && new != old)' guard so
their init default does not cause a false mismatch.

Introduce smb3_preserve_non_reconfig_opts() to copy hidden option
values from the old context into the new one before
smb3_fs_context_dup() overwrites them with init defaults. Without
this, a bare remount silently corrupts the stored mount context by
replacing live values with zero/init defaults for every option not
shown in /proc/mounts.

Introduce smb3_sync_ctx_from_negotiated() to sync the stored mount
context with runtime state (persistenthandles, unix, nosharesock,
resilienthandles, domainname, ops/vals) before the verify step, since
cifs_show_options() reads these from tcon/server and they can diverge
from ctx when the server auto-enables a feature or negotiates a
different dialect than what was parsed (e.g. default -> SMB 3.1.1).
Without this sync, the fed-back /proc/mounts values mismatch the
stale ctx and the verify step incorrectly rejects a bare remount.

Signed-off-by: Rajasi Mandal <rajasimandal@microsoft.com>
---
fs/smb/client/fs_context.c | 235 +++++++++++++++++++++++++++++++++++++
1 file changed, 235 insertions(+)

diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c
index 3f0faae99ed5..a0217b5641a2 100644
--- a/fs/smb/client/fs_context.c
+++ b/fs/smb/client/fs_context.c
@@ -1021,10 +1021,241 @@ static int smb3_verify_reconfigure_ctx(struct fs_context *fc,
cifs_errorf(fc, "can not change nbsessinit during remount\n");
return -EINVAL;
}
+ /* init default: compress = false */
+ if (new_ctx->compress &&
+ new_ctx->compress != old_ctx->compress) {
+ cifs_errorf(fc, "can not change compress during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->noblocksnd != old_ctx->noblocksnd) {
+ cifs_errorf(fc, "can not change noblocksend during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->noautotune != old_ctx->noautotune) {
+ cifs_errorf(fc, "can not change noautotune during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->no_sparse != old_ctx->no_sparse) {
+ cifs_errorf(fc, "can not change nosparse during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->nodelete != old_ctx->nodelete) {
+ cifs_errorf(fc, "can not change nodelete during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->cruid_specified &&
+ !uid_eq(new_ctx->cred_uid, old_ctx->cred_uid)) {
+ cifs_errorf(fc, "can not change cruid during remount\n");
+ return -EINVAL;
+ }
+ /* init default: port = 0 */
+ if (new_ctx->port &&
+ new_ctx->port != old_ctx->port) {
+ cifs_errorf(fc, "can not change port during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->min_offload != old_ctx->min_offload) {
+ cifs_errorf(fc, "can not change min_enc_offload during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->snapshot_time != old_ctx->snapshot_time) {
+ cifs_errorf(fc, "can not change snapshot during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->max_credits != old_ctx->max_credits) {
+ cifs_errorf(fc, "can not change max_credits during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->handle_timeout != old_ctx->handle_timeout) {
+ cifs_errorf(fc, "can not change handletimeout during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->got_ip &&
+ !cifs_match_ipaddr((struct sockaddr *)&new_ctx->dstaddr,
+ (struct sockaddr *)&old_ctx->dstaddr)) {
+ cifs_errorf(fc, "can not change ip during remount\n");
+ return -EINVAL;
+ }
+ if (((struct sockaddr *)&new_ctx->srcaddr)->sa_family != AF_UNSPEC &&
+ memcmp(&new_ctx->srcaddr, &old_ctx->srcaddr, sizeof(new_ctx->srcaddr))) {
+ cifs_errorf(fc, "can not change srcaddr during remount\n");
+ return -EINVAL;
+ }
+ /* source_rfc1001_name: both init from same nodename, safe to compare */
+ if (memcmp(new_ctx->source_rfc1001_name, old_ctx->source_rfc1001_name,
+ RFC1001_NAME_LEN)) {
+ cifs_errorf(fc, "can not change netbiosname during remount\n");
+ return -EINVAL;
+ }
+ /* init default: target_rfc1001_name[0] = 0 */
+ if (new_ctx->target_rfc1001_name[0] &&
+ memcmp(new_ctx->target_rfc1001_name, old_ctx->target_rfc1001_name,
+ RFC1001_NAME_LEN)) {
+ cifs_errorf(fc, "can not change servern during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->got_version &&
+ (new_ctx->ops != old_ctx->ops || new_ctx->vals != old_ctx->vals)) {
+ cifs_errorf(fc, "can not change vers during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->witness != old_ctx->witness) {
+ cifs_errorf(fc, "can not change witness during remount\n");
+ return -EINVAL;
+ }
+ /* init default: rootfs = false */
+ if (new_ctx->rootfs &&
+ new_ctx->rootfs != old_ctx->rootfs) {
+ cifs_errorf(fc, "can not change rootfs during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->linux_ext != old_ctx->linux_ext ||
+ new_ctx->no_linux_ext != old_ctx->no_linux_ext) {
+ cifs_errorf(fc, "can not change unix during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->nocase != old_ctx->nocase) {
+ cifs_errorf(fc, "can not change nocase during remount\n");
+ return -EINVAL;
+ }
+ /* init default: intr = false */
+ if (new_ctx->intr &&
+ new_ctx->intr != old_ctx->intr) {
+ cifs_errorf(fc, "can not change intr during remount\n");
+ return -EINVAL;
+ }
+ /* init default: no_psx_acl = 0 */
+ if (new_ctx->no_psx_acl &&
+ new_ctx->no_psx_acl != old_ctx->no_psx_acl) {
+ cifs_errorf(fc, "can not change acl during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->local_lease != old_ctx->local_lease) {
+ cifs_errorf(fc, "can not change locallease during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->sign != old_ctx->sign) {
+ cifs_errorf(fc, "can not change sign during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->ignore_signature != old_ctx->ignore_signature) {
+ cifs_errorf(fc, "can not change ignore_signature during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->seal != old_ctx->seal) {
+ cifs_errorf(fc, "can not change seal during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->nosharesock != old_ctx->nosharesock) {
+ cifs_errorf(fc, "can not change nosharesock during remount\n");
+ return -EINVAL;
+ }
+ /*
+ * persistent is shown in /proc/mounts (bare !=).
+ * nopersistent is NOT shown, so guard with init default check.
+ */
+ if (new_ctx->persistent != old_ctx->persistent ||
+ (new_ctx->nopersistent &&
+ new_ctx->nopersistent != old_ctx->nopersistent)) {
+ cifs_errorf(fc, "can not change persistenthandles during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->resilient != old_ctx->resilient) {
+ cifs_errorf(fc, "can not change resilienthandles during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->sockopt_tcp_nodelay != old_ctx->sockopt_tcp_nodelay) {
+ cifs_errorf(fc, "can not change tcpnodelay during remount\n");
+ return -EINVAL;
+ }
+ /* init default: domainauto = false */
+ if (new_ctx->domainauto &&
+ new_ctx->domainauto != old_ctx->domainauto) {
+ cifs_errorf(fc, "can not change domainauto during remount\n");
+ return -EINVAL;
+ }
+ /* init default: rdma = false */
+ if (new_ctx->rdma &&
+ new_ctx->rdma != old_ctx->rdma) {
+ cifs_errorf(fc, "can not change rdma during remount\n");
+ return -EINVAL;
+ }

return 0;
}

+/*
+ * Sync cifs_sb->ctx with negotiated runtime values for fields that
+ * cifs_show_options() reads from tcon/server rather than from ctx.
+ * The mount.cifs helper reads /proc/mounts and feeds these values back
+ * on remount, so ctx must match the runtime state to avoid false
+ * rejection by smb3_verify_reconfigure_ctx().
+ */
+static void smb3_sync_ctx_from_negotiated(struct cifs_sb_info *cifs_sb)
+{
+ struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
+ struct TCP_Server_Info *server = tcon->ses->server;
+ struct smb3_fs_context *ctx = cifs_sb->ctx;
+
+ /* /proc/mounts shows persistenthandles from tcon->use_persistent */
+ if (tcon->use_persistent && !ctx->persistent) {
+ ctx->persistent = true;
+ ctx->nopersistent = false;
+ }
+ /* /proc/mounts shows resilienthandles from tcon->use_resilient */
+ ctx->resilient = tcon->use_resilient;
+ /* /proc/mounts shows nounix/unix/posix from tcon */
+ if (tcon->posix_extensions) {
+ ctx->linux_ext = 1;
+ ctx->no_linux_ext = 0;
+ } else if (tcon->unix_ext) {
+ ctx->linux_ext = 1;
+ ctx->no_linux_ext = 0;
+ } else {
+ ctx->linux_ext = 0;
+ ctx->no_linux_ext = 1;
+ }
+ /* /proc/mounts shows nosharesock from server->nosharesock */
+ if (server->nosharesock)
+ ctx->nosharesock = true;
+ /*
+ * /proc/mounts shows vers= from server->vals->version_string,
+ * which reflects the negotiated dialect. When mounted without
+ * an explicit vers=, ctx stores smbdefault ops/vals while the
+ * server may have negotiated e.g. SMB 3.1.1. Sync so the
+ * verify check does not reject the fed-back vers= on remount.
+ */
+ ctx->ops = server->ops;
+ ctx->vals = server->vals;
+}
+
+/*
+ * Carry forward non-reconfigurable mount options that are NOT
+ * displayed by cifs_show_options() into the new remount context
+ * before smb3_fs_context_dup() overwrites cifs_sb->ctx.
+ *
+ * Options that cifs_show_options() outputs are automatically
+ * re-fed by libmount (which reads /proc/mounts) on remount,
+ * so they do not need to be preserved here.
+ *
+ * Without this, a bare remount would silently reset these hidden
+ * fields to their init defaults.
+ */
+static void smb3_preserve_non_reconfig_opts(struct smb3_fs_context *new_ctx,
+ struct smb3_fs_context *old_ctx)
+{
+ new_ctx->compress = old_ctx->compress;
+ new_ctx->port = old_ctx->port;
+ new_ctx->rootfs = old_ctx->rootfs;
+ new_ctx->intr = old_ctx->intr;
+ new_ctx->no_psx_acl = old_ctx->no_psx_acl;
+ new_ctx->domainauto = old_ctx->domainauto;
+ new_ctx->rdma = old_ctx->rdma;
+ new_ctx->nopersistent = old_ctx->nopersistent;
+ memcpy(new_ctx->target_rfc1001_name, old_ctx->target_rfc1001_name,
+ RFC1001_NAME_LEN_WITH_NULL);
+}
+
#define STEAL_STRING(cifs_sb, ctx, field) \
do { \
kfree(ctx->field); \
@@ -1093,6 +1324,8 @@ static int smb3_reconfigure(struct fs_context *fc)
if (ses->expired_pwd)
need_recon = true;

+ smb3_sync_ctx_from_negotiated(cifs_sb);
+
rc = smb3_verify_reconfigure_ctx(fc, ctx, cifs_sb->ctx, need_recon);
if (rc)
return rc;
@@ -1205,6 +1438,8 @@ 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;

+ smb3_preserve_non_reconfig_opts(ctx, cifs_sb->ctx);
+
smb3_cleanup_fs_context_contents(cifs_sb->ctx);
rc = smb3_fs_context_dup(cifs_sb->ctx, ctx);
smb3_update_mnt_flags(cifs_sb);
--
2.43.0