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,11 +18,11 @@
  *
  * CDDL HEADER END
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc. All rights reserved.
  * Copyright (c) 2016 by Delphix. All rights reserved.
  */
 
 /*
  * General Structures Layout

@@ -179,11 +179,13 @@
  *    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.
+ *       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,35 +208,50 @@
 #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,10 +281,11 @@
     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,11 +295,20 @@
         if (user->u_state != SMB_USER_STATE_LOGGING_ON) {
                 mutex_exit(&user->u_mutex);
                 return (-1);
         }
 
-        smb_authsock_close(user);
+        /*
+         * 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,58 +318,73 @@
 
         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 and disconnect trees.
+ * 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: {
-                smb_authsock_close(user);
-                user->u_state = SMB_USER_STATE_LOGGED_OFF;
-                smb_server_dec_users(user->u_server);
-                break;
+        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: {
+        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:
+                mutex_exit(&user->u_mutex);
                 break;
 
         default:
                 ASSERT(0);
+                mutex_exit(&user->u_mutex);
                 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.

@@ -385,35 +427,115 @@
  */
 void
 smb_user_release(
     smb_user_t          *user)
 {
-        ASSERT(user->u_magic == SMB_USER_MAGIC);
+        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_LOGGED_OFF:
-                if (user->u_refcnt == 0)
-                        smb_session_post_user(user->u_session, user);
+        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:
-        case SMB_USER_STATE_LOGGING_OFF:
                 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,27 +643,46 @@
  * 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
+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,11 +812,10 @@
 
         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;

@@ -705,13 +845,36 @@
                 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)
 {
-        uint32_t audit_sid = user->u_audit_sid;
+        smb_server_t *sv = user->u_server;
+        uint32_t audit_sid;
 
-        (void) smb_kdoor_upcall(user->u_server, SMB_DR_USER_AUTH_LOGOFF,
+        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);
 }