Print this page
NEX-19040 SMB2 session ID re-use can confuse clients
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-18761 panic in smb_ofile_free with vdbench
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Rick McNeal <rick.mcneal@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-16604 Windows 10 SMB client exhausts smbauth sockets
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Yuri Pankov <yuri.pankov@nexenta.com>
NEX-16519 Panic while running IOmeter to a pool through an SMB share
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Matt Barden <matt.barden@nexenta.com>
NEX-9808 SMB3 persistent handles
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-15578 SMB2 durable handle redesign
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-9808 SMB3 persistent handles
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-15578 SMB2 durable handle redesign
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-7267 Codenomicon causes panic in smb_authsock_cancel
Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com>
Reviewed by: Rick McNeal <rick.mcneal@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Matt Barden <matt.barden@nexenta.com>
NEX-3553 SMB2/3 durable handles
Reviewed by: Gordon Ross <gwr@nexenta.com>
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
NEX-3776 SMB should handle PreviousSessionID
Reviewed by: Gordon Ross <gwr@nexenta.com>
NEX-5560 smb2 should use 64-bit server-global uids
Reviewed by: Gordon Ross <gwr@nexenta.com>
NEX-5537 Want reference counts for users, trees...
Reviewed by: Gordon Ross <gwr@nexenta.com>
NEX-2485 SMB authentication flood handled poorly
SMB-56 extended security NTLMSSP, inbound
SMB-50 User-mode SMB server
 Includes work by these authors:
 Thomas Keiser <thomas.keiser@nexenta.com>
 Albert Lee <trisk@nexenta.com>
SMB-65 SMB server in non-global zones (data structure changes)
Many things move to the smb_server_t object, and
many functions gain an sv arg (which server).
SMB-65 SMB server in non-global zones (kmem_caches)
common kmem_cache instances across zones
separate GZ-only init from NGZ init
SMB-63 taskq_create_proc ... TQ_DYNAMIC puts tasks in p0
re #11974 CIFS Share - Tree connect fails from Windows 7 Clients

*** 18,28 **** * * CDDL HEADER END */ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. ! * Copyright 2014 Nexenta Systems, Inc. All rights reserved. * Copyright (c) 2016 by Delphix. All rights reserved. */ /* * General Structures Layout --- 18,28 ---- * * CDDL HEADER END */ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. ! * Copyright 2018 Nexenta Systems, Inc. All rights reserved. * Copyright (c) 2016 by Delphix. All rights reserved. */ /* * General Structures Layout
*** 179,189 **** * the user from it, the lock must be entered in RW_WRITER mode. * * Rules of access to a user structure: * * 1) In order to avoid deadlocks, when both (mutex and lock of the session ! * list) have to be entered, the lock must be entered first. * * 2) All actions applied to a user require a reference count. * * 3) There are 2 ways of getting a reference count. One is when the user * logs in. The other when the user is looked up. --- 179,191 ---- * the user from it, the lock must be entered in RW_WRITER mode. * * Rules of access to a user structure: * * 1) In order to avoid deadlocks, when both (mutex and lock of the session ! * list) have to be entered, the lock must be entered first. Additionally, ! * one may NOT flush the deleteq of either the tree list or the ofile list ! * while the user mutex is held. * * 2) All actions applied to a user require a reference count. * * 3) There are 2 ways of getting a reference count. One is when the user * logs in. The other when the user is looked up.
*** 206,240 **** #include <smbsrv/smb_kproto.h> #include <smbsrv/smb_door.h> #define ADMINISTRATORS_SID "S-1-5-32-544" static int smb_user_enum_private(smb_user_t *, smb_svcenum_t *); static void smb_user_auth_logoff(smb_user_t *); - /* * Create a new user. */ smb_user_t * smb_user_new(smb_session_t *session) { smb_user_t *user; ASSERT(session); ASSERT(session->s_magic == SMB_SESSION_MAGIC); user = kmem_cache_alloc(smb_cache_user, KM_SLEEP); bzero(user, sizeof (smb_user_t)); user->u_refcnt = 1; user->u_session = session; user->u_server = session->s_server; user->u_logon_time = gethrestime_sec(); if (smb_idpool_alloc(&session->s_uid_pool, &user->u_uid)) goto errout; mutex_init(&user->u_mutex, NULL, MUTEX_DEFAULT, NULL); user->u_state = SMB_USER_STATE_LOGGING_ON; user->u_magic = SMB_USER_MAGIC; --- 208,257 ---- #include <smbsrv/smb_kproto.h> #include <smbsrv/smb_door.h> #define ADMINISTRATORS_SID "S-1-5-32-544" + /* Don't leak object addresses */ + #define SMB_USER_SSNID(u) \ + ((uintptr_t)&smb_cache_user ^ (uintptr_t)(u)) + + static void smb_user_delete(void *); static int smb_user_enum_private(smb_user_t *, smb_svcenum_t *); static void smb_user_auth_logoff(smb_user_t *); + static void smb_user_logoff_tq(void *); /* * Create a new user. + * + * For SMB2 and later, session IDs (u_ssnid) need to be unique among all + * current and "recent" sessions. The session ID is derived from the + * address of the smb_user object (obscured by XOR with a constant). + * This adds a 3-bit generation number in the low bits, incremented + * when we allocate an smb_user_t from its kmem cache, so it can't + * be confused with a (recent) previous incarnation of this object. */ smb_user_t * smb_user_new(smb_session_t *session) { smb_user_t *user; + uint_t gen; // generation (low 3 bits of ssnid) ASSERT(session); ASSERT(session->s_magic == SMB_SESSION_MAGIC); user = kmem_cache_alloc(smb_cache_user, KM_SLEEP); + gen = (user->u_ssnid + 1) & 7; bzero(user, sizeof (smb_user_t)); user->u_refcnt = 1; user->u_session = session; user->u_server = session->s_server; user->u_logon_time = gethrestime_sec(); if (smb_idpool_alloc(&session->s_uid_pool, &user->u_uid)) goto errout; + user->u_ssnid = SMB_USER_SSNID(user) + gen; mutex_init(&user->u_mutex, NULL, MUTEX_DEFAULT, NULL); user->u_state = SMB_USER_STATE_LOGGING_ON; user->u_magic = SMB_USER_MAGIC;
*** 264,273 **** --- 281,291 ---- char *account_name, uint32_t flags, uint32_t privileges, uint32_t audit_sid) { + ksocket_t authsock = NULL; ASSERT(user->u_magic == SMB_USER_MAGIC); ASSERT(cr); ASSERT(account_name); ASSERT(domain_name);
*** 277,287 **** if (user->u_state != SMB_USER_STATE_LOGGING_ON) { mutex_exit(&user->u_mutex); return (-1); } ! smb_authsock_close(user); user->u_state = SMB_USER_STATE_LOGGED_ON; user->u_flags = flags; user->u_name_len = strlen(account_name) + 1; user->u_domain_len = strlen(domain_name) + 1; --- 295,314 ---- if (user->u_state != SMB_USER_STATE_LOGGING_ON) { mutex_exit(&user->u_mutex); return (-1); } ! /* ! * In the transition from LOGGING_ON to LOGGED_ON, ! * we always have an auth. socket to close. ! */ ! authsock = user->u_authsock; ! user->u_authsock = NULL; ! if (user->u_auth_tmo != NULL) { ! (void) untimeout(user->u_auth_tmo); ! user->u_auth_tmo = NULL; ! } user->u_state = SMB_USER_STATE_LOGGED_ON; user->u_flags = flags; user->u_name_len = strlen(account_name) + 1; user->u_domain_len = strlen(domain_name) + 1;
*** 291,348 **** smb_user_setcred(user, cr, privileges); mutex_exit(&user->u_mutex); return (0); } /* * smb_user_logoff * ! * Change the user state and disconnect trees. * The user list must not be entered or modified here. */ void smb_user_logoff( smb_user_t *user) { ASSERT(user->u_magic == SMB_USER_MAGIC); mutex_enter(&user->u_mutex); ASSERT(user->u_refcnt); switch (user->u_state) { ! case SMB_USER_STATE_LOGGING_ON: { ! smb_authsock_close(user); ! user->u_state = SMB_USER_STATE_LOGGED_OFF; ! smb_server_dec_users(user->u_server); ! break; } ! case SMB_USER_STATE_LOGGED_ON: { /* * The user is moved into a state indicating that the log off * process has started. */ user->u_state = SMB_USER_STATE_LOGGING_OFF; mutex_exit(&user->u_mutex); smb_session_disconnect_owned_trees(user->u_session, user); smb_user_auth_logoff(user); - mutex_enter(&user->u_mutex); - user->u_state = SMB_USER_STATE_LOGGED_OFF; - smb_server_dec_users(user->u_server); break; ! } case SMB_USER_STATE_LOGGED_OFF: case SMB_USER_STATE_LOGGING_OFF: break; default: ASSERT(0); break; } - mutex_exit(&user->u_mutex); } /* * Take a reference on a user. Do not return a reference unless the user is in * the logged-in state. --- 318,390 ---- smb_user_setcred(user, cr, privileges); mutex_exit(&user->u_mutex); + /* This close can block, so not under the mutex. */ + if (authsock != NULL) + smb_authsock_close(user, authsock); + return (0); } /* * smb_user_logoff * ! * Change the user state to "logging off" and disconnect trees. * The user list must not be entered or modified here. + * + * We remain in state "logging off" until the last ref. is gone, + * then smb_user_release takes us to state "logged off". */ void smb_user_logoff( smb_user_t *user) { + ksocket_t authsock = NULL; + ASSERT(user->u_magic == SMB_USER_MAGIC); mutex_enter(&user->u_mutex); ASSERT(user->u_refcnt); switch (user->u_state) { ! case SMB_USER_STATE_LOGGING_ON: ! authsock = user->u_authsock; ! user->u_authsock = NULL; ! if (user->u_auth_tmo != NULL) { ! (void) untimeout(user->u_auth_tmo); ! user->u_auth_tmo = NULL; } + user->u_state = SMB_USER_STATE_LOGGING_OFF; + mutex_exit(&user->u_mutex); + /* This close can block, so not under the mutex. */ + if (authsock != NULL) { + smb_authsock_close(user, authsock); + } + break; ! case SMB_USER_STATE_LOGGED_ON: /* * The user is moved into a state indicating that the log off * process has started. */ user->u_state = SMB_USER_STATE_LOGGING_OFF; mutex_exit(&user->u_mutex); smb_session_disconnect_owned_trees(user->u_session, user); smb_user_auth_logoff(user); break; ! case SMB_USER_STATE_LOGGED_OFF: case SMB_USER_STATE_LOGGING_OFF: + mutex_exit(&user->u_mutex); break; default: ASSERT(0); + mutex_exit(&user->u_mutex); break; } } /* * Take a reference on a user. Do not return a reference unless the user is in * the logged-in state.
*** 385,419 **** */ void smb_user_release( smb_user_t *user) { ! ASSERT(user->u_magic == SMB_USER_MAGIC); mutex_enter(&user->u_mutex); ASSERT(user->u_refcnt); user->u_refcnt--; switch (user->u_state) { ! case SMB_USER_STATE_LOGGED_OFF: ! if (user->u_refcnt == 0) ! smb_session_post_user(user->u_session, user); break; case SMB_USER_STATE_LOGGING_ON: case SMB_USER_STATE_LOGGED_ON: - case SMB_USER_STATE_LOGGING_OFF: break; default: ASSERT(0); break; } mutex_exit(&user->u_mutex); } /* * Determine whether or not the user is an administrator. * Members of the administrators group have administrative rights. */ boolean_t smb_user_is_admin(smb_user_t *user) --- 427,541 ---- */ void smb_user_release( smb_user_t *user) { ! smb_session_t *ssn = user->u_session; + SMB_USER_VALID(user); + + /* flush the tree list delete queue */ + smb_llist_flush(&ssn->s_tree_list); + mutex_enter(&user->u_mutex); ASSERT(user->u_refcnt); user->u_refcnt--; switch (user->u_state) { ! case SMB_USER_STATE_LOGGING_OFF: ! if (user->u_refcnt == 0) { ! smb_session_t *ssn = user->u_session; ! user->u_state = SMB_USER_STATE_LOGGED_OFF; ! smb_llist_post(&ssn->s_user_list, user, ! smb_user_delete); ! } break; case SMB_USER_STATE_LOGGING_ON: case SMB_USER_STATE_LOGGED_ON: break; + case SMB_USER_STATE_LOGGED_OFF: default: ASSERT(0); break; } mutex_exit(&user->u_mutex); } /* + * Timeout handler for user logons that stay too long in + * state SMB_USER_STATE_LOGGING_ON. This is setup by a + * timeout call in smb_authsock_open, and called in a + * callout thread, so schedule a taskq job to do the + * real work of logging off this user. + */ + void + smb_user_auth_tmo(void *arg) + { + smb_user_t *user = arg; + smb_request_t *sr; + + SMB_USER_VALID(user); + + /* + * If we can't allocate a request, it means the + * session is being torn down, so nothing to do. + */ + sr = smb_request_alloc(user->u_session, 0); + if (sr == NULL) + return; + + /* + * Check user state, and take a hold if it's + * still logging on. If not, we're done. + */ + mutex_enter(&user->u_mutex); + if (user->u_state != SMB_USER_STATE_LOGGING_ON) { + mutex_exit(&user->u_mutex); + smb_request_free(sr); + return; + } + /* smb_user_hold_internal */ + user->u_refcnt++; + mutex_exit(&user->u_mutex); + + /* + * The user hold is given to the SR, and released in + * smb_user_logoff_tq / smb_request_free + */ + sr->uid_user = user; + sr->user_cr = user->u_cred; + sr->sr_state = SMB_REQ_STATE_SUBMITTED; + + (void) taskq_dispatch( + user->u_server->sv_worker_pool, + smb_user_logoff_tq, sr, TQ_SLEEP); + } + + /* + * Helper for smb_user_auth_tmo() + */ + static void + smb_user_logoff_tq(void *arg) + { + smb_request_t *sr = arg; + + SMB_REQ_VALID(sr); + + mutex_enter(&sr->sr_mutex); + sr->sr_worker = curthread; + sr->sr_state = SMB_REQ_STATE_ACTIVE; + mutex_exit(&sr->sr_mutex); + + smb_user_logoff(sr->uid_user); + + sr->sr_state = SMB_REQ_STATE_COMPLETED; + smb_request_free(sr); + } + + /* * Determine whether or not the user is an administrator. * Members of the administrators group have administrative rights. */ boolean_t smb_user_is_admin(smb_user_t *user)
*** 521,547 **** * Delete a user. The tree list should be empty. * * Remove the user from the session's user list before freeing resources * associated with the user. */ ! void smb_user_delete(void *arg) { smb_session_t *session; smb_user_t *user = (smb_user_t *)arg; SMB_USER_VALID(user); ASSERT(user->u_refcnt == 0); ASSERT(user->u_state == SMB_USER_STATE_LOGGED_OFF); ASSERT(user->u_authsock == NULL); session = user->u_session; smb_llist_enter(&session->s_user_list, RW_WRITER); smb_llist_remove(&session->s_user_list, user); smb_idpool_free(&session->s_uid_pool, user->u_uid); smb_llist_exit(&session->s_user_list); mutex_enter(&user->u_mutex); mutex_exit(&user->u_mutex); user->u_magic = (uint32_t)~SMB_USER_MAGIC; mutex_destroy(&user->u_mutex); --- 643,688 ---- * Delete a user. The tree list should be empty. * * Remove the user from the session's user list before freeing resources * associated with the user. */ ! static void smb_user_delete(void *arg) { smb_session_t *session; smb_user_t *user = (smb_user_t *)arg; + uint32_t ucount; SMB_USER_VALID(user); ASSERT(user->u_refcnt == 0); ASSERT(user->u_state == SMB_USER_STATE_LOGGED_OFF); ASSERT(user->u_authsock == NULL); + ASSERT(user->u_auth_tmo == NULL); session = user->u_session; + + smb_server_dec_users(session->s_server); smb_llist_enter(&session->s_user_list, RW_WRITER); smb_llist_remove(&session->s_user_list, user); smb_idpool_free(&session->s_uid_pool, user->u_uid); + ucount = smb_llist_get_count(&session->s_user_list); smb_llist_exit(&session->s_user_list); + if (ucount == 0) { + smb_rwx_rwenter(&session->s_lock, RW_WRITER); + session->s_state = SMB_SESSION_STATE_SHUTDOWN; + smb_rwx_cvbcast(&session->s_lock); + smb_rwx_rwexit(&session->s_lock); + } + + /* + * This user is no longer on s_user_list, however... + * + * This is called via smb_llist_post, which means it may run + * BEFORE smb_user_release drops u_mutex (if another thread + * flushes the delete queue before we do). Synchronize. + */ mutex_enter(&user->u_mutex); mutex_exit(&user->u_mutex); user->u_magic = (uint32_t)~SMB_USER_MAGIC; mutex_destroy(&user->u_mutex);
*** 671,681 **** info->ui_session_id = session->s_kid; info->ui_native_os = session->native_os; info->ui_ipaddr = session->ipaddr; info->ui_numopens = session->s_file_cnt; - info->ui_smb_uid = user->u_uid; info->ui_logon_time = user->u_logon_time; info->ui_flags = user->u_flags; info->ui_posix_uid = crgetuid(user->u_cred); info->ui_domain_len = user->u_domain_len; --- 812,821 ----
*** 705,717 **** smb_mem_free(info->ui_workstation); bzero(info, sizeof (smb_netuserinfo_t)); } static void smb_user_auth_logoff(smb_user_t *user) { ! uint32_t audit_sid = user->u_audit_sid; ! (void) smb_kdoor_upcall(user->u_server, SMB_DR_USER_AUTH_LOGOFF, &audit_sid, xdr_uint32_t, NULL, NULL); } --- 845,880 ---- smb_mem_free(info->ui_workstation); bzero(info, sizeof (smb_netuserinfo_t)); } + /* + * Tell smbd this user is going away so it can clean up their + * audit session, autohome dir, etc. + * + * Note that when we're shutting down, smbd will already have set + * smbd.s_shutting_down and therefore will ignore door calls. + * Skip this during shutdown to reduce upcall noise. + */ static void smb_user_auth_logoff(smb_user_t *user) { ! smb_server_t *sv = user->u_server; ! uint32_t audit_sid; ! if (sv->sv_state != SMB_SERVER_STATE_RUNNING) ! return; ! ! audit_sid = user->u_audit_sid; ! (void) smb_kdoor_upcall(sv, SMB_DR_USER_AUTH_LOGOFF, &audit_sid, xdr_uint32_t, NULL, NULL); + } + + boolean_t + smb_is_same_user(cred_t *cr1, cred_t *cr2) + { + ksid_t *ks1 = crgetsid(cr1, KSID_USER); + ksid_t *ks2 = crgetsid(cr2, KSID_USER); + + return (ks1->ks_rid == ks2->ks_rid && + strcmp(ks1->ks_domain->kd_name, ks2->ks_domain->kd_name) == 0); }