From: Winston Wen Date: Mon, 26 Jun 2023 03:42:55 +0000 (+0800) Subject: cifs: fix session state transition to avoid use-after-free issue X-Git-Tag: v6.5-rc1~8^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ff7d80a9f2711bf3d9fe1cfb70b3fd15c50584b7;p=thirdparty%2Fkernel%2Flinux.git cifs: fix session state transition to avoid use-after-free issue We switch session state to SES_EXITING without cifs_tcp_ses_lock now, it may lead to potential use-after-free issue. Consider the following execution processes: Thread 1: __cifs_put_smb_ses() spin_lock(&cifs_tcp_ses_lock) if (--ses->ses_count > 0) spin_unlock(&cifs_tcp_ses_lock) return spin_unlock(&cifs_tcp_ses_lock) ---> **GAP** spin_lock(&ses->ses_lock) if (ses->ses_status == SES_GOOD) ses->ses_status = SES_EXITING spin_unlock(&ses->ses_lock) Thread 2: cifs_find_smb_ses() spin_lock(&cifs_tcp_ses_lock) list_for_each_entry(ses, ...) spin_lock(&ses->ses_lock) if (ses->ses_status == SES_EXITING) spin_unlock(&ses->ses_lock) continue ... spin_unlock(&ses->ses_lock) if (ret) cifs_smb_ses_inc_refcount(ret) spin_unlock(&cifs_tcp_ses_lock) If thread 1 is preempted in the gap and thread 2 start executing, thread 2 will get the session, and soon thread 1 will switch the session state to SES_EXITING and start releasing it, even though thread 1 had increased the session's refcount and still uses it. So switch session state under cifs_tcp_ses_lock to eliminate this gap. Signed-off-by: Winston Wen Signed-off-by: Steve French --- diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index dab7bc8765078..85dd1b373974a 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -1967,15 +1967,16 @@ void __cifs_put_smb_ses(struct cifs_ses *ses) spin_unlock(&cifs_tcp_ses_lock); return; } + spin_lock(&ses->ses_lock); + if (ses->ses_status == SES_GOOD) + ses->ses_status = SES_EXITING; + spin_unlock(&ses->ses_lock); spin_unlock(&cifs_tcp_ses_lock); /* ses_count can never go negative */ WARN_ON(ses->ses_count < 0); spin_lock(&ses->ses_lock); - if (ses->ses_status == SES_GOOD) - ses->ses_status = SES_EXITING; - if (ses->ses_status == SES_EXITING && server->ops->logoff) { spin_unlock(&ses->ses_lock); cifs_free_ipc(ses);