[PATCH 2/2] smb: client: fix double conversion of DFS path in setup_dfs_ref()

From: Fredric Cover

Date: Fri Nov 06 2026 - 21:04:24 EST


setup_dfs_ref() converts the path from the mount charset to the DFS
cache charset (utf8) via dfs_get_path(), and then passes that already
converted string to dfs_get_referral(). dfs_cache_find() converts its
input again, assuming it is still in the mount charset.

When the mount charset is not utf8 (iocharset=iso8859-1, or a kernel
built with a non-utf8 CONFIG_NLS_DEFAULT) and the DFS path contains
non-ASCII characters, the utf8 bytes are interpreted as mount-charset
characters and encoded a second time. For example, "é" is sent as
c3 83 c2 a9 instead of c3 a9. The server then fails the second-level
referral with STATUS_OBJECT_PATH_NOT_FOUND and the mount fails with
-ENOENT, even though the path exists.

Pass the unconverted path to dfs_get_referral() so that it is converted
exactly once.

Such mounts now get past the second-level referral and reach
dfs_parse_target_referral(), which is why this change depends on
"smb: client: fix out-of-bounds read in dfs_parse_target_referral()".

Tested with a Samba DFS root containing a link named "café", mounted
with iocharset=iso8859-1 and the single Latin-1 byte 0xe9 in the path:
before, the mount failed with -ENOENT; with both patches applied, it
succeeds.

Signed-off-by: Fredric Cover <fredric.cover.lkernel@xxxxxxxxx>
---
fs/smb/client/dfs.c | 16 +++++++++++-----
1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/fs/smb/client/dfs.c b/fs/smb/client/dfs.c
index e6dcaf9b8682..0660c0a1eefb 100644
--- a/fs/smb/client/dfs.c
+++ b/fs/smb/client/dfs.c
@@ -103,6 +103,7 @@ static int setup_dfs_ref(struct dfs_info3_param *tgt, struct dfs_ref_walk *rw)
struct cifs_sb_info *cifs_sb = rw->mnt_ctx->cifs_sb;
struct smb3_fs_context *ctx = rw->mnt_ctx->fs_ctx;
char *ref_path, *full_path;
+ const char *raw_path;
int rc;

set_root_smb_session(rw->mnt_ctx);
@@ -114,9 +115,10 @@ static int setup_dfs_ref(struct dfs_info3_param *tgt, struct dfs_ref_walk *rw)

if (!tgt || (tgt->server_type == DFS_TYPE_LINK &&
DFS_INTERLINK(tgt->flags)))
- ref_path = dfs_get_path(cifs_sb, ctx->UNC);
+ raw_path = ctx->UNC;
else
- ref_path = dfs_get_path(cifs_sb, full_path);
+ raw_path = full_path;
+ ref_path = dfs_get_path(cifs_sb, raw_path);
if (IS_ERR(ref_path)) {
rc = PTR_ERR(ref_path);
kfree(full_path);
@@ -125,9 +127,13 @@ static int setup_dfs_ref(struct dfs_info3_param *tgt, struct dfs_ref_walk *rw)
ref_walk_path(rw) = ref_path;
ref_walk_fpath(rw) = full_path;

- return dfs_get_referral(rw->mnt_ctx,
- ref_walk_path(rw) + 1,
- ref_walk_tl(rw));
+ /*
+ * dfs_cache_find() canonicalizes its input from the mount's charset, so
+ * give it the raw path. Passing ref_walk_path() (already in the cache
+ * charset) would convert it a second time.
+ */
+
+ return dfs_get_referral(rw->mnt_ctx, raw_path + 1, ref_walk_tl(rw));
}

static int __dfs_referral_walk(struct dfs_ref_walk *rw)
--
2.53.0