Merge tag '6.6-rc4-ksmbd-server-fixes' of git://git.samba.org/ksmbd

Pull smb server fixes from Steve French:
"Six SMB3 server fixes for various races found by RO0T Lab of Huawei:

- Fix oops when racing between oplock break ack and freeing file

- Simultaneous request fixes for parallel logoffs, and for parallel
lock requests

- Fixes for tree disconnect race, session expire race, and close/open
race"

* tag '6.6-rc4-ksmbd-server-fixes' of git://git.samba.org/ksmbd:
ksmbd: fix race condition between tree conn lookup and disconnect
ksmbd: fix race condition from parallel smb2 lock requests
ksmbd: fix race condition from parallel smb2 logoff requests
ksmbd: fix uaf in smb20_oplock_break_ack
ksmbd: fix race condition with fp
ksmbd: fix race condition between session lookup and expire

+2
fs/smb/server/connection.c
··· 84 84 spin_lock_init(&conn->llist_lock); 85 85 INIT_LIST_HEAD(&conn->lock_list); 86 86 87 + init_rwsem(&conn->session_lock); 88 + 87 89 down_write(&conn_list_lock); 88 90 list_add(&conn->conns_list, &conn_list); 89 91 up_write(&conn_list_lock);
+1
fs/smb/server/connection.h
··· 50 50 struct nls_table *local_nls; 51 51 struct unicode_map *um; 52 52 struct list_head conns_list; 53 + struct rw_semaphore session_lock; 53 54 /* smb session 1 per user */ 54 55 struct xarray sessions; 55 56 unsigned long last_active;
+39 -3
fs/smb/server/mgmt/tree_connect.c
··· 73 73 74 74 tree_conn->user = sess->user; 75 75 tree_conn->share_conf = sc; 76 + tree_conn->t_state = TREE_NEW; 76 77 status.tree_conn = tree_conn; 78 + atomic_set(&tree_conn->refcount, 1); 79 + init_waitqueue_head(&tree_conn->refcount_q); 77 80 78 81 ret = xa_err(xa_store(&sess->tree_conns, tree_conn->id, tree_conn, 79 82 GFP_KERNEL)); ··· 96 93 return status; 97 94 } 98 95 96 + void ksmbd_tree_connect_put(struct ksmbd_tree_connect *tcon) 97 + { 98 + /* 99 + * Checking waitqueue to releasing tree connect on 100 + * tree disconnect. waitqueue_active is safe because it 101 + * uses atomic operation for condition. 102 + */ 103 + if (!atomic_dec_return(&tcon->refcount) && 104 + waitqueue_active(&tcon->refcount_q)) 105 + wake_up(&tcon->refcount_q); 106 + } 107 + 99 108 int ksmbd_tree_conn_disconnect(struct ksmbd_session *sess, 100 109 struct ksmbd_tree_connect *tree_conn) 101 110 { 102 111 int ret; 103 112 113 + write_lock(&sess->tree_conns_lock); 114 + xa_erase(&sess->tree_conns, tree_conn->id); 115 + write_unlock(&sess->tree_conns_lock); 116 + 117 + if (!atomic_dec_and_test(&tree_conn->refcount)) 118 + wait_event(tree_conn->refcount_q, 119 + atomic_read(&tree_conn->refcount) == 0); 120 + 104 121 ret = ksmbd_ipc_tree_disconnect_request(sess->id, tree_conn->id); 105 122 ksmbd_release_tree_conn_id(sess, tree_conn->id); 106 - xa_erase(&sess->tree_conns, tree_conn->id); 107 123 ksmbd_share_config_put(tree_conn->share_conf); 108 124 kfree(tree_conn); 109 125 return ret; ··· 133 111 { 134 112 struct ksmbd_tree_connect *tcon; 135 113 114 + read_lock(&sess->tree_conns_lock); 136 115 tcon = xa_load(&sess->tree_conns, id); 137 116 if (tcon) { 138 - if (test_bit(TREE_CONN_EXPIRE, &tcon->status)) 117 + if (tcon->t_state != TREE_CONNECTED) 118 + tcon = NULL; 119 + else if (!atomic_inc_not_zero(&tcon->refcount)) 139 120 tcon = NULL; 140 121 } 122 + read_unlock(&sess->tree_conns_lock); 141 123 142 124 return tcon; 143 125 } ··· 155 129 if (!sess) 156 130 return -EINVAL; 157 131 158 - xa_for_each(&sess->tree_conns, id, tc) 132 + xa_for_each(&sess->tree_conns, id, tc) { 133 + write_lock(&sess->tree_conns_lock); 134 + if (tc->t_state == TREE_DISCONNECTED) { 135 + write_unlock(&sess->tree_conns_lock); 136 + ret = -ENOENT; 137 + continue; 138 + } 139 + tc->t_state = TREE_DISCONNECTED; 140 + write_unlock(&sess->tree_conns_lock); 141 + 159 142 ret |= ksmbd_tree_conn_disconnect(sess, tc); 143 + } 160 144 xa_destroy(&sess->tree_conns); 161 145 return ret; 162 146 }
+9 -2
fs/smb/server/mgmt/tree_connect.h
··· 14 14 struct ksmbd_user; 15 15 struct ksmbd_conn; 16 16 17 - #define TREE_CONN_EXPIRE 1 17 + enum { 18 + TREE_NEW = 0, 19 + TREE_CONNECTED, 20 + TREE_DISCONNECTED 21 + }; 18 22 19 23 struct ksmbd_tree_connect { 20 24 int id; ··· 31 27 32 28 int maximal_access; 33 29 bool posix_extensions; 34 - unsigned long status; 30 + atomic_t refcount; 31 + wait_queue_head_t refcount_q; 32 + unsigned int t_state; 35 33 }; 36 34 37 35 struct ksmbd_tree_conn_status { ··· 52 46 struct ksmbd_tree_conn_status 53 47 ksmbd_tree_conn_connect(struct ksmbd_conn *conn, struct ksmbd_session *sess, 54 48 const char *share_name); 49 + void ksmbd_tree_connect_put(struct ksmbd_tree_connect *tcon); 55 50 56 51 int ksmbd_tree_conn_disconnect(struct ksmbd_session *sess, 57 52 struct ksmbd_tree_connect *tree_conn);
+8 -3
fs/smb/server/mgmt/user_session.c
··· 174 174 unsigned long id; 175 175 struct ksmbd_session *sess; 176 176 177 - down_write(&sessions_table_lock); 177 + down_write(&conn->session_lock); 178 178 xa_for_each(&conn->sessions, id, sess) { 179 179 if (sess->state != SMB2_SESSION_VALID || 180 180 time_after(jiffies, ··· 185 185 continue; 186 186 } 187 187 } 188 - up_write(&sessions_table_lock); 188 + up_write(&conn->session_lock); 189 189 } 190 190 191 191 int ksmbd_session_register(struct ksmbd_conn *conn, ··· 227 227 } 228 228 } 229 229 } 230 + up_write(&sessions_table_lock); 230 231 232 + down_write(&conn->session_lock); 231 233 xa_for_each(&conn->sessions, id, sess) { 232 234 unsigned long chann_id; 233 235 struct channel *chann; ··· 246 244 ksmbd_session_destroy(sess); 247 245 } 248 246 } 249 - up_write(&sessions_table_lock); 247 + up_write(&conn->session_lock); 250 248 } 251 249 252 250 struct ksmbd_session *ksmbd_session_lookup(struct ksmbd_conn *conn, ··· 254 252 { 255 253 struct ksmbd_session *sess; 256 254 255 + down_read(&conn->session_lock); 257 256 sess = xa_load(&conn->sessions, id); 258 257 if (sess) 259 258 sess->last_active = jiffies; 259 + up_read(&conn->session_lock); 260 260 return sess; 261 261 } 262 262 ··· 355 351 xa_init(&sess->ksmbd_chann_list); 356 352 xa_init(&sess->rpc_handle_list); 357 353 sess->sequence_number = 1; 354 + rwlock_init(&sess->tree_conns_lock); 358 355 359 356 ret = __init_smb2_session(sess); 360 357 if (ret)
+1
fs/smb/server/mgmt/user_session.h
··· 60 60 61 61 struct ksmbd_file_table file_table; 62 62 unsigned long last_active; 63 + rwlock_t tree_conns_lock; 63 64 }; 64 65 65 66 static inline int test_session_flag(struct ksmbd_session *sess, int bit)
+2
fs/smb/server/server.c
··· 241 241 } while (is_chained == true); 242 242 243 243 send: 244 + if (work->tcon) 245 + ksmbd_tree_connect_put(work->tcon); 244 246 smb3_preauth_hash_rsp(work); 245 247 if (work->sess && work->sess->enc && work->encrypted && 246 248 conn->ops->encrypt_resp) {
+61 -35
fs/smb/server/smb2pdu.c
··· 1993 1993 if (conn->posix_ext_supported) 1994 1994 status.tree_conn->posix_extensions = true; 1995 1995 1996 + write_lock(&sess->tree_conns_lock); 1997 + status.tree_conn->t_state = TREE_CONNECTED; 1998 + write_unlock(&sess->tree_conns_lock); 1996 1999 rsp->StructureSize = cpu_to_le16(16); 1997 2000 out_err1: 1998 2001 rsp->Capabilities = 0; ··· 2125 2122 2126 2123 ksmbd_debug(SMB, "request\n"); 2127 2124 2125 + if (!tcon) { 2126 + ksmbd_debug(SMB, "Invalid tid %d\n", req->hdr.Id.SyncId.TreeId); 2127 + 2128 + rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED; 2129 + err = -ENOENT; 2130 + goto err_out; 2131 + } 2132 + 2133 + ksmbd_close_tree_conn_fds(work); 2134 + 2135 + write_lock(&sess->tree_conns_lock); 2136 + if (tcon->t_state == TREE_DISCONNECTED) { 2137 + write_unlock(&sess->tree_conns_lock); 2138 + rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED; 2139 + err = -ENOENT; 2140 + goto err_out; 2141 + } 2142 + 2143 + WARN_ON_ONCE(atomic_dec_and_test(&tcon->refcount)); 2144 + tcon->t_state = TREE_DISCONNECTED; 2145 + write_unlock(&sess->tree_conns_lock); 2146 + 2147 + err = ksmbd_tree_conn_disconnect(sess, tcon); 2148 + if (err) { 2149 + rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED; 2150 + goto err_out; 2151 + } 2152 + 2153 + work->tcon = NULL; 2154 + 2128 2155 rsp->StructureSize = cpu_to_le16(4); 2129 2156 err = ksmbd_iov_pin_rsp(work, rsp, 2130 2157 sizeof(struct smb2_tree_disconnect_rsp)); 2131 2158 if (err) { 2132 2159 rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES; 2133 - smb2_set_err_rsp(work); 2134 - return err; 2160 + goto err_out; 2135 2161 } 2136 2162 2137 - if (!tcon || test_and_set_bit(TREE_CONN_EXPIRE, &tcon->status)) { 2138 - ksmbd_debug(SMB, "Invalid tid %d\n", req->hdr.Id.SyncId.TreeId); 2139 - 2140 - rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED; 2141 - smb2_set_err_rsp(work); 2142 - return -ENOENT; 2143 - } 2144 - 2145 - ksmbd_close_tree_conn_fds(work); 2146 - ksmbd_tree_conn_disconnect(sess, tcon); 2147 - work->tcon = NULL; 2148 2163 return 0; 2164 + 2165 + err_out: 2166 + smb2_set_err_rsp(work); 2167 + return err; 2168 + 2149 2169 } 2150 2170 2151 2171 /** ··· 2190 2164 2191 2165 ksmbd_debug(SMB, "request\n"); 2192 2166 2193 - sess_id = le64_to_cpu(req->hdr.SessionId); 2194 - 2195 - rsp->StructureSize = cpu_to_le16(4); 2196 - err = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_logoff_rsp)); 2197 - if (err) { 2198 - rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES; 2167 + ksmbd_conn_lock(conn); 2168 + if (!ksmbd_conn_good(conn)) { 2169 + ksmbd_conn_unlock(conn); 2170 + rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED; 2199 2171 smb2_set_err_rsp(work); 2200 - return err; 2172 + return -ENOENT; 2201 2173 } 2202 - 2174 + sess_id = le64_to_cpu(req->hdr.SessionId); 2203 2175 ksmbd_all_conn_set_status(sess_id, KSMBD_SESS_NEED_RECONNECT); 2176 + ksmbd_conn_unlock(conn); 2177 + 2204 2178 ksmbd_close_session_fds(work); 2205 2179 ksmbd_conn_wait_idle(conn, sess_id); 2206 2180 ··· 2222 2196 ksmbd_free_user(sess->user); 2223 2197 sess->user = NULL; 2224 2198 ksmbd_all_conn_set_status(sess_id, KSMBD_SESS_NEED_NEGOTIATE); 2199 + 2200 + rsp->StructureSize = cpu_to_le16(4); 2201 + err = ksmbd_iov_pin_rsp(work, rsp, sizeof(struct smb2_logoff_rsp)); 2202 + if (err) { 2203 + rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES; 2204 + smb2_set_err_rsp(work); 2205 + return err; 2206 + } 2225 2207 return 0; 2226 2208 } 2227 2209 ··· 3404 3370 } 3405 3371 ksmbd_revert_fsids(work); 3406 3372 err_out1: 3407 - if (!rc) 3373 + if (!rc) { 3374 + ksmbd_update_fstate(&work->sess->file_table, fp, FP_INITED); 3408 3375 rc = ksmbd_iov_pin_rsp(work, (void *)rsp, iov_len); 3376 + } 3409 3377 if (rc) { 3410 3378 if (rc == -EINVAL) 3411 3379 rsp->hdr.Status = STATUS_INVALID_PARAMETER; ··· 7064 7028 7065 7029 ksmbd_debug(SMB, 7066 7030 "would have to wait for getting lock\n"); 7067 - spin_lock(&work->conn->llist_lock); 7068 - list_add_tail(&smb_lock->clist, 7069 - &work->conn->lock_list); 7070 - spin_unlock(&work->conn->llist_lock); 7071 7031 list_add(&smb_lock->llist, &rollback_list); 7072 7032 7073 7033 argv = kmalloc(sizeof(void *), GFP_KERNEL); ··· 7094 7062 7095 7063 if (work->state != KSMBD_WORK_ACTIVE) { 7096 7064 list_del(&smb_lock->llist); 7097 - spin_lock(&work->conn->llist_lock); 7098 - list_del(&smb_lock->clist); 7099 - spin_unlock(&work->conn->llist_lock); 7100 7065 locks_free_lock(flock); 7101 7066 7102 7067 if (work->state == KSMBD_WORK_CANCELLED) { ··· 7113 7084 } 7114 7085 7115 7086 list_del(&smb_lock->llist); 7116 - spin_lock(&work->conn->llist_lock); 7117 - list_del(&smb_lock->clist); 7118 - spin_unlock(&work->conn->llist_lock); 7119 7087 release_async_work(work); 7120 7088 goto retry; 7121 7089 } else if (!rc) { 7090 + list_add(&smb_lock->llist, &rollback_list); 7122 7091 spin_lock(&work->conn->llist_lock); 7123 7092 list_add_tail(&smb_lock->clist, 7124 7093 &work->conn->lock_list); 7125 7094 list_add_tail(&smb_lock->flist, 7126 7095 &fp->lock_list); 7127 7096 spin_unlock(&work->conn->llist_lock); 7128 - list_add(&smb_lock->llist, &rollback_list); 7129 7097 ksmbd_debug(SMB, "successful in taking lock\n"); 7130 7098 } else { 7131 7099 goto out; ··· 8062 8036 goto err_out; 8063 8037 } 8064 8038 8065 - opinfo_put(opinfo); 8066 - ksmbd_fd_put(work, fp); 8067 8039 opinfo->op_state = OPLOCK_STATE_NONE; 8068 8040 wake_up_interruptible_all(&opinfo->oplock_q); 8041 + opinfo_put(opinfo); 8042 + ksmbd_fd_put(work, fp); 8069 8043 8070 8044 rsp->StructureSize = cpu_to_le16(24); 8071 8045 rsp->OplockLevel = rsp_oplevel;
+20 -3
fs/smb/server/vfs_cache.c
··· 333 333 334 334 static struct ksmbd_file *ksmbd_fp_get(struct ksmbd_file *fp) 335 335 { 336 + if (fp->f_state != FP_INITED) 337 + return NULL; 338 + 336 339 if (!atomic_inc_not_zero(&fp->refcount)) 337 340 return NULL; 338 341 return fp; ··· 385 382 return 0; 386 383 387 384 ft = &work->sess->file_table; 388 - read_lock(&ft->lock); 385 + write_lock(&ft->lock); 389 386 fp = idr_find(ft->idr, id); 390 387 if (fp) { 391 388 set_close_state_blocked_works(fp); 392 389 393 - if (!atomic_dec_and_test(&fp->refcount)) 390 + if (fp->f_state != FP_INITED) 394 391 fp = NULL; 392 + else { 393 + fp->f_state = FP_CLOSED; 394 + if (!atomic_dec_and_test(&fp->refcount)) 395 + fp = NULL; 396 + } 395 397 } 396 - read_unlock(&ft->lock); 398 + write_unlock(&ft->lock); 397 399 398 400 if (!fp) 399 401 return -EINVAL; ··· 578 570 fp->tcon = work->tcon; 579 571 fp->volatile_id = KSMBD_NO_FID; 580 572 fp->persistent_id = KSMBD_NO_FID; 573 + fp->f_state = FP_NEW; 581 574 fp->f_ci = ksmbd_inode_get(fp); 582 575 583 576 if (!fp->f_ci) { ··· 598 589 err_out: 599 590 kmem_cache_free(filp_cache, fp); 600 591 return ERR_PTR(ret); 592 + } 593 + 594 + void ksmbd_update_fstate(struct ksmbd_file_table *ft, struct ksmbd_file *fp, 595 + unsigned int state) 596 + { 597 + write_lock(&ft->lock); 598 + fp->f_state = state; 599 + write_unlock(&ft->lock); 601 600 } 602 601 603 602 static int
+9
fs/smb/server/vfs_cache.h
··· 60 60 __le32 m_fattr; 61 61 }; 62 62 63 + enum { 64 + FP_NEW = 0, 65 + FP_INITED, 66 + FP_CLOSED 67 + }; 68 + 63 69 struct ksmbd_file { 64 70 struct file *filp; 65 71 u64 persistent_id; ··· 104 98 /* if ls is happening on directory, below is valid*/ 105 99 struct ksmbd_readdir_data readdir_data; 106 100 int dot_dotdot[2]; 101 + unsigned int f_state; 107 102 }; 108 103 109 104 static inline void set_ctx_actor(struct dir_context *ctx, ··· 149 142 int ksmbd_init_global_file_table(void); 150 143 void ksmbd_free_global_file_table(void); 151 144 void ksmbd_set_fd_limit(unsigned long limit); 145 + void ksmbd_update_fstate(struct ksmbd_file_table *ft, struct ksmbd_file *fp, 146 + unsigned int state); 152 147 153 148 /* 154 149 * INODE hash