Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ksmbd: fix user-after-free from session log off

There is racy issue between smb2 session log off and smb2 session setup.
It will cause user-after-free from session log off.
This add session_lock when setting SMB2_SESSION_EXPIRED and referece
count to session struct not to free session while it is being used.

Cc: stable@vger.kernel.org # v5.15+
Reported-by: zdi-disclosures@trendmicro.com # ZDI-CAN-25282
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Steve French <stfrench@microsoft.com>

authored by

Namjae Jeon and committed by
Steve French
7aa8804c 8cf0b939

+34 -6
+21 -5
fs/smb/server/mgmt/user_session.c
··· 177 177 178 178 down_write(&conn->session_lock); 179 179 xa_for_each(&conn->sessions, id, sess) { 180 - if (sess->state != SMB2_SESSION_VALID || 181 - time_after(jiffies, 182 - sess->last_active + SMB2_SESSION_TIMEOUT)) { 180 + if (atomic_read(&sess->refcnt) == 0 && 181 + (sess->state != SMB2_SESSION_VALID || 182 + time_after(jiffies, 183 + sess->last_active + SMB2_SESSION_TIMEOUT))) { 183 184 xa_erase(&conn->sessions, sess->id); 184 185 hash_del(&sess->hlist); 185 186 ksmbd_session_destroy(sess); ··· 270 269 271 270 down_read(&sessions_table_lock); 272 271 sess = __session_lookup(id); 273 - if (sess) 274 - sess->last_active = jiffies; 275 272 up_read(&sessions_table_lock); 276 273 277 274 return sess; ··· 286 287 if (sess && sess->state != SMB2_SESSION_VALID) 287 288 sess = NULL; 288 289 return sess; 290 + } 291 + 292 + void ksmbd_user_session_get(struct ksmbd_session *sess) 293 + { 294 + atomic_inc(&sess->refcnt); 295 + } 296 + 297 + void ksmbd_user_session_put(struct ksmbd_session *sess) 298 + { 299 + if (!sess) 300 + return; 301 + 302 + if (atomic_read(&sess->refcnt) <= 0) 303 + WARN_ON(1); 304 + else 305 + atomic_dec(&sess->refcnt); 289 306 } 290 307 291 308 struct preauth_session *ksmbd_preauth_session_alloc(struct ksmbd_conn *conn, ··· 408 393 xa_init(&sess->rpc_handle_list); 409 394 sess->sequence_number = 1; 410 395 rwlock_init(&sess->tree_conns_lock); 396 + atomic_set(&sess->refcnt, 1); 411 397 412 398 ret = __init_smb2_session(sess); 413 399 if (ret)
+4
fs/smb/server/mgmt/user_session.h
··· 61 61 struct ksmbd_file_table file_table; 62 62 unsigned long last_active; 63 63 rwlock_t tree_conns_lock; 64 + 65 + atomic_t refcnt; 64 66 }; 65 67 66 68 static inline int test_session_flag(struct ksmbd_session *sess, int bit) ··· 106 104 int ksmbd_session_rpc_open(struct ksmbd_session *sess, char *rpc_name); 107 105 void ksmbd_session_rpc_close(struct ksmbd_session *sess, int id); 108 106 int ksmbd_session_rpc_method(struct ksmbd_session *sess, int id); 107 + void ksmbd_user_session_get(struct ksmbd_session *sess); 108 + void ksmbd_user_session_put(struct ksmbd_session *sess); 109 109 #endif /* __USER_SESSION_MANAGEMENT_H__ */
+2
fs/smb/server/server.c
··· 238 238 } while (is_chained == true); 239 239 240 240 send: 241 + if (work->sess) 242 + ksmbd_user_session_put(work->sess); 241 243 if (work->tcon) 242 244 ksmbd_tree_connect_put(work->tcon); 243 245 smb3_preauth_hash_rsp(work);
+7 -1
fs/smb/server/smb2pdu.c
··· 605 605 606 606 /* Check for validity of user session */ 607 607 work->sess = ksmbd_session_lookup_all(conn, sess_id); 608 - if (work->sess) 608 + if (work->sess) { 609 + ksmbd_user_session_get(work->sess); 609 610 return 1; 611 + } 610 612 ksmbd_debug(SMB, "Invalid user session, Uid %llu\n", sess_id); 611 613 return -ENOENT; 612 614 } ··· 1742 1740 } 1743 1741 1744 1742 conn->binding = true; 1743 + ksmbd_user_session_get(sess); 1745 1744 } else if ((conn->dialect < SMB30_PROT_ID || 1746 1745 server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL) && 1747 1746 (req->Flags & SMB2_SESSION_REQ_FLAG_BINDING)) { ··· 1769 1766 } 1770 1767 1771 1768 conn->binding = false; 1769 + ksmbd_user_session_get(sess); 1772 1770 } 1773 1771 work->sess = sess; 1774 1772 ··· 2232 2228 } 2233 2229 2234 2230 ksmbd_destroy_file_table(&sess->file_table); 2231 + down_write(&conn->session_lock); 2235 2232 sess->state = SMB2_SESSION_EXPIRED; 2233 + up_write(&conn->session_lock); 2236 2234 2237 2235 ksmbd_free_user(sess->user); 2238 2236 sess->user = NULL;