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);
}