Skip to content

Commit

Permalink
cifs: fix sharing of DFS connections
Browse files Browse the repository at this point in the history
When matching DFS connections, we can't rely on the values set in
cifs_sb_info::prepath and cifs_tcon::tree_name as they might change
during DFS failover.  The DFS referrals related to a specific DFS tcon
are already matched earlier in match_server(), therefore we can safely
skip those checks altogether as the connection is guaranteed to be
unique for the DFS tcon.

Besides, when creating or finding an SMB session, make sure to also
refcount any DFS root session related to it (cifs_ses::dfs_root_ses),
so if a new DFS mount ends up reusing the connection from the old
mount while there was an umount(2) still in progress (e.g. umount(2)
-> cifs_umount() -> reconnect -> cifs_put_tcon()), the connection
could potentially be put right after the umount(2) finished.

Patch has minor update to include fix for unused variable issue
noted by the kernel test robot

Reported-by: kernel test robot <lkp@intel.com>
Link: https://lore.kernel.org/oe-kbuild-all/202305041040.j7W2xQSy-lkp@intel.com/
Cc: stable@vger.kernel.org # v6.2+
Signed-off-by: Paulo Alcantara (SUSE) <pc@manguebit.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
  • Loading branch information
Paulo Alcantara authored and Steve French committed May 4, 2023
1 parent 6be2ea3 commit 8e35541
Showing 6 changed files with 147 additions and 80 deletions.
1 change: 0 additions & 1 deletion fs/cifs/cifsglob.h
Original file line number Diff line number Diff line change
@@ -1756,7 +1756,6 @@ struct cifs_mount_ctx {
struct TCP_Server_Info *server;
struct cifs_ses *ses;
struct cifs_tcon *tcon;
char *origin_fullpath, *leaf_fullpath;
struct list_head dfs_ses_list;
};

44 changes: 43 additions & 1 deletion fs/cifs/cifsproto.h
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@
#ifndef _CIFSPROTO_H
#define _CIFSPROTO_H
#include <linux/nls.h>
#include <linux/ctype.h>
#include "trace.h"
#ifdef CONFIG_CIFS_DFS_UPCALL
#include "dfs_cache.h"
@@ -572,7 +573,7 @@ extern int E_md4hash(const unsigned char *passwd, unsigned char *p16,
extern struct TCP_Server_Info *
cifs_find_tcp_session(struct smb3_fs_context *ctx);

extern void cifs_put_smb_ses(struct cifs_ses *ses);
void __cifs_put_smb_ses(struct cifs_ses *ses);

extern struct cifs_ses *
cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx);
@@ -696,4 +697,45 @@ struct super_block *cifs_get_tcon_super(struct cifs_tcon *tcon);
void cifs_put_tcon_super(struct super_block *sb);
int cifs_wait_for_server_reconnect(struct TCP_Server_Info *server, bool retry);

/* Put references of @ses and @ses->dfs_root_ses */
static inline void cifs_put_smb_ses(struct cifs_ses *ses)
{
struct cifs_ses *rses = ses->dfs_root_ses;

__cifs_put_smb_ses(ses);
if (rses)
__cifs_put_smb_ses(rses);
}

/* Get an active reference of @ses and @ses->dfs_root_ses.
*
* NOTE: make sure to call this function when incrementing reference count of
* @ses to ensure that any DFS root session attached to it (@ses->dfs_root_ses)
* will also get its reference count incremented.
*
* cifs_put_smb_ses() will put both references, so call it when you're done.
*/
static inline void cifs_smb_ses_inc_refcount(struct cifs_ses *ses)
{
lockdep_assert_held(&cifs_tcp_ses_lock);

ses->ses_count++;
if (ses->dfs_root_ses)
ses->dfs_root_ses->ses_count++;
}

static inline bool dfs_src_pathname_equal(const char *s1, const char *s2)
{
if (strlen(s1) != strlen(s2))
return false;
for (; *s1; s1++, s2++) {
if (*s1 == '/' || *s1 == '\\') {
if (*s2 != '/' && *s2 != '\\')
return false;
} else if (tolower(*s1) != tolower(*s2))
return false;
}
return true;
}

#endif /* _CIFSPROTO_H */
114 changes: 58 additions & 56 deletions fs/cifs/connect.c
Original file line number Diff line number Diff line change
@@ -996,10 +996,8 @@ static void clean_demultiplex_info(struct TCP_Server_Info *server)
*/
}

#ifdef CONFIG_CIFS_DFS_UPCALL
kfree(server->origin_fullpath);
kfree(server->leaf_fullpath);
#endif
kfree(server);

length = atomic_dec_return(&tcpSesAllocCount);
@@ -1387,23 +1385,8 @@ match_security(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
return true;
}

static bool dfs_src_pathname_equal(const char *s1, const char *s2)
{
if (strlen(s1) != strlen(s2))
return false;
for (; *s1; s1++, s2++) {
if (*s1 == '/' || *s1 == '\\') {
if (*s2 != '/' && *s2 != '\\')
return false;
} else if (tolower(*s1) != tolower(*s2))
return false;
}
return true;
}

/* this function must be called with srv_lock held */
static int match_server(struct TCP_Server_Info *server, struct smb3_fs_context *ctx,
bool dfs_super_cmp)
static int match_server(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
{
struct sockaddr *addr = (struct sockaddr *)&ctx->dstaddr;

@@ -1434,27 +1417,41 @@ static int match_server(struct TCP_Server_Info *server, struct smb3_fs_context *
(struct sockaddr *)&server->srcaddr))
return 0;
/*
* When matching DFS superblocks, we only check for original source pathname as the
* currently connected target might be different than the one parsed earlier in i.e.
* mount.cifs(8).
* - Match for an DFS tcon (@server->origin_fullpath).
* - Match for an DFS root server connection (@server->leaf_fullpath).
* - If none of the above and @ctx->leaf_fullpath is set, then
* it is a new DFS connection.
* - If 'nodfs' mount option was passed, then match only connections
* that have no DFS referrals set
* (e.g. can't failover to other targets).
*/
if (dfs_super_cmp) {
if (!ctx->source || !server->origin_fullpath ||
!dfs_src_pathname_equal(server->origin_fullpath, ctx->source))
return 0;
} else {
/* Skip addr, hostname and port matching for DFS connections */
if (server->leaf_fullpath) {
if (!ctx->nodfs) {
if (ctx->source && server->origin_fullpath) {
if (!dfs_src_pathname_equal(ctx->source,
server->origin_fullpath))
return 0;
} else if (server->leaf_fullpath) {
if (!ctx->leaf_fullpath ||
strcasecmp(server->leaf_fullpath, ctx->leaf_fullpath))
strcasecmp(server->leaf_fullpath,
ctx->leaf_fullpath))
return 0;
} else if (strcasecmp(server->hostname, ctx->server_hostname) ||
!match_server_address(server, addr) ||
!match_port(server, addr)) {
} else if (ctx->leaf_fullpath) {
return 0;
}
} else if (server->origin_fullpath || server->leaf_fullpath) {
return 0;
}

/*
* Match for a regular connection (address/hostname/port) which has no
* DFS referrals set.
*/
if (!server->origin_fullpath && !server->leaf_fullpath &&
(strcasecmp(server->hostname, ctx->server_hostname) ||
!match_server_address(server, addr) ||
!match_port(server, addr)))
return 0;

if (!match_security(server, ctx))
return 0;

@@ -1485,7 +1482,7 @@ cifs_find_tcp_session(struct smb3_fs_context *ctx)
* Skip ses channels since they're only handled in lower layers
* (e.g. cifs_send_recv).
*/
if (CIFS_SERVER_IS_CHAN(server) || !match_server(server, ctx, false)) {
if (CIFS_SERVER_IS_CHAN(server) || !match_server(server, ctx)) {
spin_unlock(&server->srv_lock);
continue;
}
@@ -1869,7 +1866,7 @@ cifs_free_ipc(struct cifs_ses *ses)
static struct cifs_ses *
cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
{
struct cifs_ses *ses;
struct cifs_ses *ses, *ret = NULL;

spin_lock(&cifs_tcp_ses_lock);
list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
@@ -1879,23 +1876,22 @@ cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
continue;
}
spin_lock(&ses->chan_lock);
if (!match_session(ses, ctx)) {
if (match_session(ses, ctx)) {
spin_unlock(&ses->chan_lock);
spin_unlock(&ses->ses_lock);
continue;
ret = ses;
break;
}
spin_unlock(&ses->chan_lock);
spin_unlock(&ses->ses_lock);

++ses->ses_count;
spin_unlock(&cifs_tcp_ses_lock);
return ses;
}
if (ret)
cifs_smb_ses_inc_refcount(ret);
spin_unlock(&cifs_tcp_ses_lock);
return NULL;
return ret;
}

void cifs_put_smb_ses(struct cifs_ses *ses)
void __cifs_put_smb_ses(struct cifs_ses *ses)
{
unsigned int rc, xid;
unsigned int chan_count;
@@ -2250,6 +2246,8 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
*/
spin_lock(&cifs_tcp_ses_lock);
ses->dfs_root_ses = ctx->dfs_root_ses;
if (ses->dfs_root_ses)
ses->dfs_root_ses->ses_count++;
list_add(&ses->smb_ses_list, &server->smb_ses_list);
spin_unlock(&cifs_tcp_ses_lock);

@@ -2266,12 +2264,15 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
}

/* this function must be called with tc_lock held */
static int match_tcon(struct cifs_tcon *tcon, struct smb3_fs_context *ctx, bool dfs_super_cmp)
static int match_tcon(struct cifs_tcon *tcon, struct smb3_fs_context *ctx)
{
struct TCP_Server_Info *server = tcon->ses->server;

if (tcon->status == TID_EXITING)
return 0;
/* Skip UNC validation when matching DFS superblocks */
if (!dfs_super_cmp && strncmp(tcon->tree_name, ctx->UNC, MAX_TREE_SIZE))
/* Skip UNC validation when matching DFS connections or superblocks */
if (!server->origin_fullpath && !server->leaf_fullpath &&
strncmp(tcon->tree_name, ctx->UNC, MAX_TREE_SIZE))
return 0;
if (tcon->seal != ctx->seal)
return 0;
@@ -2294,7 +2295,7 @@ cifs_find_tcon(struct cifs_ses *ses, struct smb3_fs_context *ctx)
spin_lock(&cifs_tcp_ses_lock);
list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
spin_lock(&tcon->tc_lock);
if (!match_tcon(tcon, ctx, false)) {
if (!match_tcon(tcon, ctx)) {
spin_unlock(&tcon->tc_lock);
continue;
}
@@ -2670,16 +2671,22 @@ compare_mount_options(struct super_block *sb, struct cifs_mnt_data *mnt_data)
return 1;
}

static int
match_prepath(struct super_block *sb, struct cifs_mnt_data *mnt_data)
static int match_prepath(struct super_block *sb,
struct TCP_Server_Info *server,
struct cifs_mnt_data *mnt_data)
{
struct smb3_fs_context *ctx = mnt_data->ctx;
struct cifs_sb_info *old = CIFS_SB(sb);
struct cifs_sb_info *new = mnt_data->cifs_sb;
bool old_set = (old->mnt_cifs_flags & CIFS_MOUNT_USE_PREFIX_PATH) &&
old->prepath;
bool new_set = (new->mnt_cifs_flags & CIFS_MOUNT_USE_PREFIX_PATH) &&
new->prepath;

if (server->origin_fullpath &&
dfs_src_pathname_equal(server->origin_fullpath, ctx->source))
return 1;

if (old_set && new_set && !strcmp(new->prepath, old->prepath))
return 1;
else if (!old_set && !new_set)
@@ -2698,7 +2705,6 @@ cifs_match_super(struct super_block *sb, void *data)
struct cifs_ses *ses;
struct cifs_tcon *tcon;
struct tcon_link *tlink;
bool dfs_super_cmp;
int rc = 0;

spin_lock(&cifs_tcp_ses_lock);
@@ -2713,18 +2719,16 @@ cifs_match_super(struct super_block *sb, void *data)
ses = tcon->ses;
tcp_srv = ses->server;

dfs_super_cmp = IS_ENABLED(CONFIG_CIFS_DFS_UPCALL) && tcp_srv->origin_fullpath;

ctx = mnt_data->ctx;

spin_lock(&tcp_srv->srv_lock);
spin_lock(&ses->ses_lock);
spin_lock(&ses->chan_lock);
spin_lock(&tcon->tc_lock);
if (!match_server(tcp_srv, ctx, dfs_super_cmp) ||
if (!match_server(tcp_srv, ctx) ||
!match_session(ses, ctx) ||
!match_tcon(tcon, ctx, dfs_super_cmp) ||
!match_prepath(sb, mnt_data)) {
!match_tcon(tcon, ctx) ||
!match_prepath(sb, tcp_srv, mnt_data)) {
rc = 0;
goto out;
}
@@ -3469,8 +3473,6 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)

error:
dfs_put_root_smb_sessions(&mnt_ctx.dfs_ses_list);
kfree(mnt_ctx.origin_fullpath);
kfree(mnt_ctx.leaf_fullpath);
cifs_mount_put_conns(&mnt_ctx);
return rc;
}
Loading

0 comments on commit 8e35541

Please sign in to comment.