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

Configure Feed

Select the types of activity you want to include in your feed.

ksmbd: validate owner of durable handle on reconnect

Currently, ksmbd does not verify if the user attempting to reconnect
to a durable handle is the same user who originally opened the file.
This allows any authenticated user to hijack an orphaned durable handle
by predicting or brute-forcing the persistent ID.

According to MS-SMB2, the server MUST verify that the SecurityContext
of the reconnect request matches the SecurityContext associated with
the existing open.
Add a durable_owner structure to ksmbd_file to store the original opener's
UID, GID, and account name. and catpure the owner information when a file
handle becomes orphaned. and implementing ksmbd_vfs_compare_durable_owner()
to validate the identity of the requester during SMB2_CREATE (DHnC).

Fixes: c8efcc786146 ("ksmbd: add support for durable handles v1/v2")
Reported-by: Davide Ornaghi <d.ornaghi97@gmail.com>
Reported-by: Navaneeth K <knavaneeth786@gmail.com>
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
49110a8c 235e3232

+102 -16
+3 -5
fs/smb/server/mgmt/user_session.c
··· 382 382 return; 383 383 384 384 delete_proc_session(sess); 385 - 385 + ksmbd_tree_conn_session_logoff(sess); 386 + ksmbd_destroy_file_table(sess); 386 387 if (sess->user) 387 388 ksmbd_free_user(sess->user); 388 - 389 - ksmbd_tree_conn_session_logoff(sess); 390 - ksmbd_destroy_file_table(&sess->file_table); 391 389 ksmbd_launch_ksmbd_durable_scavenger(); 392 390 ksmbd_session_rpc_clear_list(sess); 393 391 free_channel_list(sess); ··· 616 618 goto out; 617 619 } 618 620 619 - ksmbd_destroy_file_table(&prev_sess->file_table); 621 + ksmbd_destroy_file_table(prev_sess); 620 622 prev_sess->state = SMB2_SESSION_EXPIRED; 621 623 ksmbd_all_conn_set_status(id, KSMBD_SESS_NEED_SETUP); 622 624 ksmbd_launch_ksmbd_durable_scavenger();
+7
fs/smb/server/oplock.c
··· 1841 1841 struct ksmbd_share_config *share, 1842 1842 struct ksmbd_file *fp, 1843 1843 struct lease_ctx_info *lctx, 1844 + struct ksmbd_user *user, 1844 1845 char *name) 1845 1846 { 1846 1847 struct oplock_info *opinfo = opinfo_get(fp); ··· 1849 1848 1850 1849 if (!opinfo) 1851 1850 return 0; 1851 + 1852 + if (ksmbd_vfs_compare_durable_owner(fp, user) == false) { 1853 + ksmbd_debug(SMB, "Durable handle reconnect failed: owner mismatch\n"); 1854 + ret = -EBADF; 1855 + goto out; 1856 + } 1852 1857 1853 1858 if (opinfo->is_lease == false) { 1854 1859 if (lctx) {
+1
fs/smb/server/oplock.h
··· 126 126 struct ksmbd_share_config *share, 127 127 struct ksmbd_file *fp, 128 128 struct lease_ctx_info *lctx, 129 + struct ksmbd_user *user, 129 130 char *name); 130 131 #endif /* __KSMBD_OPLOCK_H */
+2 -1
fs/smb/server/smb2pdu.c
··· 3013 3013 } 3014 3014 3015 3015 if (dh_info.reconnected == true) { 3016 - rc = smb2_check_durable_oplock(conn, share, dh_info.fp, lc, name); 3016 + rc = smb2_check_durable_oplock(conn, share, dh_info.fp, 3017 + lc, sess->user, name); 3017 3018 if (rc) { 3018 3019 ksmbd_put_durable_fd(dh_info.fp); 3019 3020 goto err_out2;
+78 -9
fs/smb/server/vfs_cache.c
··· 19 19 #include "misc.h" 20 20 #include "mgmt/tree_connect.h" 21 21 #include "mgmt/user_session.h" 22 + #include "mgmt/user_config.h" 22 23 #include "smb_common.h" 23 24 #include "server.h" 24 25 #include "smb2pdu.h" ··· 477 476 478 477 if (ksmbd_stream_fd(fp)) 479 478 kfree(fp->stream.name); 479 + kfree(fp->owner.name); 480 + 480 481 kmem_cache_free(filp_cache, fp); 481 482 } 482 483 ··· 790 787 } 791 788 792 789 static int 793 - __close_file_table_ids(struct ksmbd_file_table *ft, 790 + __close_file_table_ids(struct ksmbd_session *sess, 794 791 struct ksmbd_tree_connect *tcon, 795 792 bool (*skip)(struct ksmbd_tree_connect *tcon, 796 - struct ksmbd_file *fp)) 793 + struct ksmbd_file *fp, 794 + struct ksmbd_user *user)) 797 795 { 796 + struct ksmbd_file_table *ft = &sess->file_table; 798 797 struct ksmbd_file *fp; 799 798 unsigned int id = 0; 800 799 int num = 0; ··· 809 804 break; 810 805 } 811 806 812 - if (skip(tcon, fp) || 807 + if (skip(tcon, fp, sess->user) || 813 808 !atomic_dec_and_test(&fp->refcount)) { 814 809 id++; 815 810 write_unlock(&ft->lock); ··· 861 856 } 862 857 863 858 static bool tree_conn_fd_check(struct ksmbd_tree_connect *tcon, 864 - struct ksmbd_file *fp) 859 + struct ksmbd_file *fp, 860 + struct ksmbd_user *user) 865 861 { 866 862 return fp->tcon != tcon; 867 863 } ··· 997 991 kthread_stop(server_conf.dh_task); 998 992 } 999 993 994 + /* 995 + * ksmbd_vfs_copy_durable_owner - Copy owner info for durable reconnect 996 + * @fp: ksmbd file pointer to store owner info 997 + * @user: user pointer to copy from 998 + * 999 + * This function binds the current user's identity to the file handle 1000 + * to satisfy MS-SMB2 Step 8 (SecurityContext matching) during reconnect. 1001 + * 1002 + * Return: 0 on success, or negative error code on failure 1003 + */ 1004 + static int ksmbd_vfs_copy_durable_owner(struct ksmbd_file *fp, 1005 + struct ksmbd_user *user) 1006 + { 1007 + if (!user) 1008 + return -EINVAL; 1009 + 1010 + /* Duplicate the user name to ensure identity persistence */ 1011 + fp->owner.name = kstrdup(user->name, GFP_KERNEL); 1012 + if (!fp->owner.name) 1013 + return -ENOMEM; 1014 + 1015 + fp->owner.uid = user->uid; 1016 + fp->owner.gid = user->gid; 1017 + 1018 + return 0; 1019 + } 1020 + 1021 + /** 1022 + * ksmbd_vfs_compare_durable_owner - Verify if the requester is original owner 1023 + * @fp: existing ksmbd file pointer 1024 + * @user: user pointer of the reconnect requester 1025 + * 1026 + * Compares the UID, GID, and name of the current requester against the 1027 + * original owner stored in the file handle. 1028 + * 1029 + * Return: true if the user matches, false otherwise 1030 + */ 1031 + bool ksmbd_vfs_compare_durable_owner(struct ksmbd_file *fp, 1032 + struct ksmbd_user *user) 1033 + { 1034 + if (!user || !fp->owner.name) 1035 + return false; 1036 + 1037 + /* Check if the UID and GID match first (fast path) */ 1038 + if (fp->owner.uid != user->uid || fp->owner.gid != user->gid) 1039 + return false; 1040 + 1041 + /* Validate the account name to ensure the same SecurityContext */ 1042 + if (strcmp(fp->owner.name, user->name)) 1043 + return false; 1044 + 1045 + return true; 1046 + } 1047 + 1000 1048 static bool session_fd_check(struct ksmbd_tree_connect *tcon, 1001 - struct ksmbd_file *fp) 1049 + struct ksmbd_file *fp, struct ksmbd_user *user) 1002 1050 { 1003 1051 struct ksmbd_inode *ci; 1004 1052 struct oplock_info *op; ··· 1060 1000 struct ksmbd_lock *smb_lock, *tmp_lock; 1061 1001 1062 1002 if (!is_reconnectable(fp)) 1003 + return false; 1004 + 1005 + if (ksmbd_vfs_copy_durable_owner(fp, user)) 1063 1006 return false; 1064 1007 1065 1008 conn = fp->conn; ··· 1096 1033 1097 1034 void ksmbd_close_tree_conn_fds(struct ksmbd_work *work) 1098 1035 { 1099 - int num = __close_file_table_ids(&work->sess->file_table, 1036 + int num = __close_file_table_ids(work->sess, 1100 1037 work->tcon, 1101 1038 tree_conn_fd_check); 1102 1039 ··· 1105 1042 1106 1043 void ksmbd_close_session_fds(struct ksmbd_work *work) 1107 1044 { 1108 - int num = __close_file_table_ids(&work->sess->file_table, 1045 + int num = __close_file_table_ids(work->sess, 1109 1046 work->tcon, 1110 1047 session_fd_check); 1111 1048 ··· 1203 1140 } 1204 1141 up_write(&ci->m_lock); 1205 1142 1143 + fp->owner.uid = fp->owner.gid = 0; 1144 + kfree(fp->owner.name); 1145 + fp->owner.name = NULL; 1146 + 1206 1147 return 0; 1207 1148 } 1208 1149 ··· 1221 1154 return 0; 1222 1155 } 1223 1156 1224 - void ksmbd_destroy_file_table(struct ksmbd_file_table *ft) 1157 + void ksmbd_destroy_file_table(struct ksmbd_session *sess) 1225 1158 { 1159 + struct ksmbd_file_table *ft = &sess->file_table; 1160 + 1226 1161 if (!ft->idr) 1227 1162 return; 1228 1163 1229 - __close_file_table_ids(ft, NULL, session_fd_check); 1164 + __close_file_table_ids(sess, NULL, session_fd_check); 1230 1165 idr_destroy(ft->idr); 1231 1166 kfree(ft->idr); 1232 1167 ft->idr = NULL;
+11 -1
fs/smb/server/vfs_cache.h
··· 68 68 FP_CLOSED 69 69 }; 70 70 71 + /* Owner information for durable handle reconnect */ 72 + struct durable_owner { 73 + unsigned int uid; 74 + unsigned int gid; 75 + char *name; 76 + }; 77 + 71 78 struct ksmbd_file { 72 79 struct file *filp; 73 80 u64 persistent_id; ··· 121 114 bool is_resilient; 122 115 123 116 bool is_posix_ctxt; 117 + struct durable_owner owner; 124 118 }; 125 119 126 120 static inline void set_ctx_actor(struct dir_context *ctx, ··· 148 140 } 149 141 150 142 int ksmbd_init_file_table(struct ksmbd_file_table *ft); 151 - void ksmbd_destroy_file_table(struct ksmbd_file_table *ft); 143 + void ksmbd_destroy_file_table(struct ksmbd_session *sess); 152 144 int ksmbd_close_fd(struct ksmbd_work *work, u64 id); 153 145 struct ksmbd_file *ksmbd_lookup_fd_fast(struct ksmbd_work *work, u64 id); 154 146 struct ksmbd_file *ksmbd_lookup_foreign_fd(struct ksmbd_work *work, u64 id); ··· 174 166 void ksmbd_set_fd_limit(unsigned long limit); 175 167 void ksmbd_update_fstate(struct ksmbd_file_table *ft, struct ksmbd_file *fp, 176 168 unsigned int state); 169 + bool ksmbd_vfs_compare_durable_owner(struct ksmbd_file *fp, 170 + struct ksmbd_user *user); 177 171 178 172 /* 179 173 * INODE hash