Print this page
NEX-13644 File access audit logging
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com>
Reviewed by: Saso Kiselkov <saso.kiselkov@nexenta.com>
Reviewed by: Rick McNeal <rick.mcneal@nexenta.com>
Reviewed by: Yuri Pankov <yuri.pankov@nexenta.com>
NEX-5665 SMB2 oplock leases
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com>
NEX-5665 SMB2 oplock leases
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com>
NEX-2831 panic in smb_make_link
NEX-2442 regression with smbtorture test raw.sfileinfo.rename
NEX-1920 SMB rename from Win2k8 fails
SMB-11 SMB2 message parse & dispatch
SMB-12 SMB2 Negotiate Protocol
SMB-13 SMB2 Session Setup
SMB-14 SMB2 Logoff
SMB-15 SMB2 Tree Connect
SMB-16 SMB2 Tree Disconnect
SMB-17 SMB2 Create
SMB-18 SMB2 Close
SMB-19 SMB2 Flush
SMB-20 SMB2 Read
SMB-21 SMB2 Write
SMB-22 SMB2 Lock/Unlock
SMB-23 SMB2 Ioctl
SMB-24 SMB2 Cancel
SMB-25 SMB2 Echo
SMB-26 SMB2 Query Dir
SMB-27 SMB2 Change Notify
SMB-28 SMB2 Query Info
SMB-29 SMB2 Set Info
SMB-30 SMB2 Oplocks
SMB-53 SMB2 Create Context options
(SMB2 code review cleanup 1, 2, 3)
        
*** 18,32 ****
   *
   * CDDL HEADER END
   */
  /*
   * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
!  * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
   */
  
  #include <sys/synch.h>
! #include <smbsrv/smb_kproto.h>
  #include <smbsrv/smb_fsops.h>
  #include <sys/nbmlock.h>
  
  /*
   * SMB_TRANS2_SET_FILE/PATH_INFO (RENAME_INFORMATION level) flag
--- 18,32 ----
   *
   * CDDL HEADER END
   */
  /*
   * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
!  * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
   */
  
  #include <sys/synch.h>
! #include <smbsrv/smb2_kproto.h>
  #include <smbsrv/smb_fsops.h>
  #include <sys/nbmlock.h>
  
  /*
   * SMB_TRANS2_SET_FILE/PATH_INFO (RENAME_INFORMATION level) flag
*** 34,43 ****
--- 34,44 ----
  #define SMB_RENAME_FLAG_OVERWRITE       0x001
  
  static int smb_rename_check_stream(smb_fqi_t *, smb_fqi_t *);
  static int smb_rename_check_attr(smb_request_t *, smb_node_t *, uint16_t);
  static int smb_rename_lookup_src(smb_request_t *);
+ static uint32_t smb_rename_check_src(smb_request_t *, smb_fqi_t *);
  static void smb_rename_release_src(smb_request_t *);
  static uint32_t smb_rename_errno2status(int);
  
  /*
   * smb_setinfo_rename
*** 97,107 ****
          smb_node_t *src_fnode, *src_dnode, *dst_dnode;
          smb_node_t *dst_fnode = 0;
          smb_node_t *tnode;
          char *new_name, *path;
          DWORD status;
!         int rc, count;
  
          tnode = sr->tid_tree->t_snode;
          path = dst_fqi->fq_path.pn_path;
  
          /* Check if attempting to rename a stream - not yet supported */
--- 98,113 ----
          smb_node_t *src_fnode, *src_dnode, *dst_dnode;
          smb_node_t *dst_fnode = 0;
          smb_node_t *tnode;
          char *new_name, *path;
          DWORD status;
!         int rc;
!         boolean_t have_src = B_FALSE;
!         boolean_t dst_exists = B_FALSE;
!         boolean_t do_audit;
!         char *srcpath = NULL;
!         char *dstpath = NULL;
  
          tnode = sr->tid_tree->t_snode;
          path = dst_fqi->fq_path.pn_path;
  
          /* Check if attempting to rename a stream - not yet supported */
*** 109,136 ****
          if (rc != 0)
                  return (smb_rename_errno2status(rc));
  
          /*
           * The source node may already have been provided,
!          * i.e. when called by SMB1/SMB2 smb_setinfo_rename.
!          * Not provided by smb_com_rename, smb_com_nt_rename.
           */
          if (src_fqi->fq_fnode) {
-                 smb_node_start_crit(src_fqi->fq_fnode, RW_READER);
-                 smb_node_ref(src_fqi->fq_fnode);
                  smb_node_ref(src_fqi->fq_dnode);
          } else {
                  /* lookup and validate src node */
                  rc = smb_rename_lookup_src(sr);
                  if (rc != 0)
                          return (smb_rename_errno2status(rc));
          }
- 
          src_fnode = src_fqi->fq_fnode;
          src_dnode = src_fqi->fq_dnode;
  
          /*
           * Find the destination dnode and last component.
           * May already be provided, i.e. when called via
           * SMB1 trans2 setinfo.
           */
          if (dst_fqi->fq_dnode) {
--- 115,157 ----
          if (rc != 0)
                  return (smb_rename_errno2status(rc));
  
          /*
           * The source node may already have been provided,
!          * i.e. when called by SMB1/SMB2 smb_setinfo_rename
!          * with an ofile.  When we have an ofile, open has
!          * already checked for sharing violations.  For
!          * path-based operations, do sharing check here.
           */
          if (src_fqi->fq_fnode) {
                  smb_node_ref(src_fqi->fq_dnode);
+                 smb_node_ref(src_fqi->fq_fnode);
+                 have_src = B_TRUE;
          } else {
                  /* lookup and validate src node */
                  rc = smb_rename_lookup_src(sr);
                  if (rc != 0)
                          return (smb_rename_errno2status(rc));
+                 /* Holding refs on dnode, fnode */
          }
          src_fnode = src_fqi->fq_fnode;
          src_dnode = src_fqi->fq_dnode;
  
+         /* Break oplocks, and check share modes. */
+         status = smb_rename_check_src(sr, src_fqi);
+         if (status != NT_STATUS_SUCCESS) {
+                 smb_node_release(src_fqi->fq_fnode);
+                 smb_node_release(src_fqi->fq_dnode);
+                 return (status);
+         }
          /*
+          * NB: src_fnode is now "in crit" (critical section)
+          * as if we did smb_node_start_crit(..., RW_READER);
+          * Call smb_rename_release_src(sr) on errors.
+          */
+ 
+         /*
           * Find the destination dnode and last component.
           * May already be provided, i.e. when called via
           * SMB1 trans2 setinfo.
           */
          if (dst_fqi->fq_dnode) {
*** 232,259 ****
                          smb_node_release(dst_fnode);
                          smb_node_release(dst_dnode);
                          return (NT_STATUS_OBJECT_NAME_COLLISION);
                  }
  
!                 (void) smb_oplock_break(sr, dst_fnode,
!                     SMB_OPLOCK_BREAK_TO_NONE | SMB_OPLOCK_BREAK_BATCH);
  
-                 /*
-                  * Wait (a little) for the oplock break to be
-                  * responded to by clients closing handles.
-                  * Hold node->n_lock as reader to keep new
-                  * ofiles from showing up after we check.
-                  */
                  smb_node_rdlock(dst_fnode);
-                 for (count = 0; count <= 12; count++) {
                          status = smb_node_delete_check(dst_fnode);
-                         if (status != NT_STATUS_SHARING_VIOLATION)
-                                 break;
-                         smb_node_unlock(dst_fnode);
-                         delay(MSEC_TO_TICK(100));
-                         smb_node_rdlock(dst_fnode);
-                 }
                  if (status != NT_STATUS_SUCCESS) {
                          smb_node_unlock(dst_fnode);
                          smb_rename_release_src(sr);
                          smb_node_release(dst_fnode);
                          smb_node_release(dst_dnode);
--- 253,278 ----
                          smb_node_release(dst_fnode);
                          smb_node_release(dst_dnode);
                          return (NT_STATUS_OBJECT_NAME_COLLISION);
                  }
  
!                 status = smb_oplock_break_DELETE(dst_fnode, NULL);
!                 if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
!                         if (sr->session->dialect >= SMB_VERS_2_BASE)
!                                 (void) smb2sr_go_async(sr);
!                         (void) smb_oplock_wait_break(dst_fnode, 0);
!                         status = 0;
!                 }
!                 if (status != 0) {
!                         smb_rename_release_src(sr);
!                         smb_node_release(dst_fnode);
!                         smb_node_release(dst_dnode);
!                         return (status);
!                 }
  
                  smb_node_rdlock(dst_fnode);
                  status = smb_node_delete_check(dst_fnode);
                  if (status != NT_STATUS_SUCCESS) {
                          smb_node_unlock(dst_fnode);
                          smb_rename_release_src(sr);
                          smb_node_release(dst_fnode);
                          smb_node_release(dst_dnode);
*** 283,298 ****
--- 302,347 ----
                          smb_node_release(dst_dnode);
                          return (NT_STATUS_ACCESS_DENIED);
                  }
  
                  new_name = dst_fnode->od_name;
+                 dst_exists = B_TRUE;
          }
  
+         do_audit = smb_audit_rename_init(sr);
+         /* save paths for later auditing */
+         if (do_audit) {
+                 if (!have_src) {
+                         srcpath = kmem_alloc(SMB_MAXPATHLEN, KM_SLEEP);
+                         smb_node_getpath_nofail(src_fnode, smb_audit_rootvp(sr),
+                             srcpath, SMB_MAXPATHLEN);
+                 }
+                 if (dst_exists) {
+                         dstpath = kmem_alloc(SMB_MAXPATHLEN, KM_SLEEP);
+                         smb_node_getpath_nofail(dst_fnode, smb_audit_rootvp(sr),
+                             dstpath, SMB_MAXPATHLEN);
+                 }
+         }
+ 
          rc = smb_fsop_rename(sr, sr->user_cr,
              src_dnode, src_fnode->od_name,
              dst_dnode, new_name);
  
+         if (do_audit) {
+                 smb_audit_rename_fini(sr,
+                     srcpath,
+                     dst_dnode,
+                     dstpath,
+                     rc == 0,
+                     smb_node_is_dir(src_fnode));
+ 
+                 if (srcpath != NULL)
+                         kmem_free(srcpath, SMB_MAXPATHLEN);
+                 if (dstpath != NULL)
+                         kmem_free(dstpath, SMB_MAXPATHLEN);
+         }
+ 
          if (rc == 0) {
                  /*
                   * Note that renames in the same directory are normally
                   * delivered in {old,new} pairs, and clients expect them
                   * in that order, if both events are delivered.
*** 432,458 ****
                  return (NT_STATUS_INVALID_PARAMETER);
          }
  
          /* The source node may already have been provided */
          if (src_fqi->fq_fnode) {
-                 smb_node_start_crit(src_fqi->fq_fnode, RW_READER);
-                 smb_node_ref(src_fqi->fq_fnode);
                  smb_node_ref(src_fqi->fq_dnode);
          } else {
                  /* lookup and validate src node */
                  rc = smb_rename_lookup_src(sr);
                  if (rc != 0)
                          return (smb_rename_errno2status(rc));
          }
  
          /* Not valid to create hardlink for directory */
          if (smb_node_is_dir(src_fqi->fq_fnode)) {
!                 smb_rename_release_src(sr);
                  return (NT_STATUS_FILE_IS_A_DIRECTORY);
          }
  
          /*
           * Find the destination dnode and last component.
           * May already be provided, i.e. when called via
           * SMB1 trans2 setinfo.
           */
          if (dst_fqi->fq_dnode) {
--- 481,515 ----
                  return (NT_STATUS_INVALID_PARAMETER);
          }
  
          /* The source node may already have been provided */
          if (src_fqi->fq_fnode) {
                  smb_node_ref(src_fqi->fq_dnode);
+                 smb_node_ref(src_fqi->fq_fnode);
          } else {
                  /* lookup and validate src node */
                  rc = smb_rename_lookup_src(sr);
                  if (rc != 0)
                          return (smb_rename_errno2status(rc));
+                 /* Holding refs on dnode, fnode */
          }
  
          /* Not valid to create hardlink for directory */
          if (smb_node_is_dir(src_fqi->fq_fnode)) {
!                 smb_node_release(src_fqi->fq_dnode);
!                 smb_node_release(src_fqi->fq_fnode);
                  return (NT_STATUS_FILE_IS_A_DIRECTORY);
          }
  
          /*
+          * Unlike in rename, we will not unlink the src,
+          * so skip the smb_rename_check_src() call, and
+          * just "start crit" instead.
+          */
+         smb_node_start_crit(src_fqi->fq_fnode, RW_READER);
+ 
+         /*
           * Find the destination dnode and last component.
           * May already be provided, i.e. when called via
           * SMB1 trans2 setinfo.
           */
          if (dst_fqi->fq_dnode) {
*** 508,535 ****
  }
  
  /*
   * smb_rename_lookup_src
   *
!  * Lookup the src node, checking for sharing violations and
!  * breaking any existing BATCH oplock.
!  * Populate sr->arg.dirop.fqi
   *
!  * Upon success, the dnode and fnode will have holds and the
!  * fnode will be in a critical section. These should be
!  * released using smb_rename_release_src().
   *
   * Returns errno values.
   */
  static int
  smb_rename_lookup_src(smb_request_t *sr)
  {
!         smb_node_t *src_node, *tnode;
!         DWORD status;
!         int rc;
!         int count;
          char *path;
  
          smb_fqi_t *src_fqi = &sr->arg.dirop.fqi;
  
          if (smb_is_stream_name(src_fqi->fq_path.pn_path))
                  return (EINVAL);
--- 565,587 ----
  }
  
  /*
   * smb_rename_lookup_src
   *
!  * Lookup the src node for a path-based link or rename.
   *
!  * On success, fills in sr->arg.dirop.fqi, and returns with
!  * holds on the source dnode and fnode.
   *
   * Returns errno values.
   */
  static int
  smb_rename_lookup_src(smb_request_t *sr)
  {
!         smb_node_t *tnode;
          char *path;
+         int rc;
  
          smb_fqi_t *src_fqi = &sr->arg.dirop.fqi;
  
          if (smb_is_stream_name(src_fqi->fq_path.pn_path))
                  return (EINVAL);
*** 539,594 ****
          path = src_fqi->fq_path.pn_path;
          rc = smb_pathname_reduce(sr, sr->user_cr, path, tnode, tnode,
              &src_fqi->fq_dnode, src_fqi->fq_last_comp);
          if (rc != 0)
                  return (rc);
  
          rc = smb_fsop_lookup(sr, sr->user_cr, 0, tnode,
              src_fqi->fq_dnode, src_fqi->fq_last_comp, &src_fqi->fq_fnode);
          if (rc != 0) {
                  smb_node_release(src_fqi->fq_dnode);
                  return (rc);
          }
!         src_node = src_fqi->fq_fnode;
  
!         rc = smb_rename_check_attr(sr, src_node, src_fqi->fq_sattr);
          if (rc != 0) {
                  smb_node_release(src_fqi->fq_fnode);
                  smb_node_release(src_fqi->fq_dnode);
                  return (rc);
          }
  
          /*
           * Break BATCH oplock before ofile checks. If a client
           * has a file open, this will force a flush or close,
           * which may affect the outcome of any share checking.
           */
-         (void) smb_oplock_break(sr, src_node,
-             SMB_OPLOCK_BREAK_TO_LEVEL_II | SMB_OPLOCK_BREAK_BATCH);
  
          /*
!          * Wait (a little) for the oplock break to be
!          * responded to by clients closing handles.
!          * Hold node->n_lock as reader to keep new
!          * ofiles from showing up after we check.
           */
          smb_node_rdlock(src_node);
-         for (count = 0; count <= 12; count++) {
                  status = smb_node_rename_check(src_node);
-                 if (status != NT_STATUS_SHARING_VIOLATION)
-                         break;
-                 smb_node_unlock(src_node);
-                 delay(MSEC_TO_TICK(100));
-                 smb_node_rdlock(src_node);
-         }
          if (status != NT_STATUS_SUCCESS) {
                  smb_node_unlock(src_node);
!                 smb_node_release(src_fqi->fq_fnode);
!                 smb_node_release(src_fqi->fq_dnode);
!                 return (EPIPE); /* = ERRbadshare */
          }
  
          /*
           * Note, the combination of these two:
           *      smb_node_rdlock(node);
           *      nbl_start_crit(node->vp, RW_READER);
           * is equivalent to this call:
--- 591,697 ----
          path = src_fqi->fq_path.pn_path;
          rc = smb_pathname_reduce(sr, sr->user_cr, path, tnode, tnode,
              &src_fqi->fq_dnode, src_fqi->fq_last_comp);
          if (rc != 0)
                  return (rc);
+         /* hold fq_dnode */
  
          rc = smb_fsop_lookup(sr, sr->user_cr, 0, tnode,
              src_fqi->fq_dnode, src_fqi->fq_last_comp, &src_fqi->fq_fnode);
          if (rc != 0) {
                  smb_node_release(src_fqi->fq_dnode);
                  return (rc);
          }
!         /* hold fq_dnode, fq_fnode */
  
!         rc = smb_rename_check_attr(sr, src_fqi->fq_fnode, src_fqi->fq_sattr);
          if (rc != 0) {
                  smb_node_release(src_fqi->fq_fnode);
                  smb_node_release(src_fqi->fq_dnode);
                  return (rc);
          }
  
+         return (0);
+ }
+ 
+ /*
+  * smb_rename_check_src
+  *
+  * Check for sharing violations on the file we'll unlink, and
+  * break oplocks for the rename operation.  Note that we've
+  * already done oplock breaks associated with opening a handle
+  * on the file to rename.
+  *
+  * On success, returns with fnode in a critical section,
+  * as if smb_node_start_crit were called with the node.
+  * Caller should release using smb_rename_release_src().
+  */
+ static uint32_t
+ smb_rename_check_src(smb_request_t *sr, smb_fqi_t *src_fqi)
+ {
+         smb_node_t *src_node = src_fqi->fq_fnode;
+         uint32_t status;
+ 
          /*
           * Break BATCH oplock before ofile checks. If a client
           * has a file open, this will force a flush or close,
           * which may affect the outcome of any share checking.
+          *
+          * This operation may have either a handle or path for
+          * the source node (that will be unlinked via rename).
           */
  
+         if (sr->fid_ofile != NULL) {
+                 status = smb_oplock_break_SETINFO(src_node, sr->fid_ofile,
+                     FileRenameInformation);
+                 if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+                         if (sr->session->dialect >= SMB_VERS_2_BASE)
+                                 (void) smb2sr_go_async(sr);
+                         (void) smb_oplock_wait_break(src_node, 0);
+                         status = 0;
+                 }
+ 
                  /*
!                  * Sharing violations were checked at open time.
!                  * Just "start crit" to be consistent with the
!                  * state returned for path-based rename.
                   */
+                 smb_node_start_crit(src_fqi->fq_fnode, RW_READER);
+                 return (NT_STATUS_SUCCESS);
+         }
+ 
+         /*
+          * This code path operates without a real open, so
+          * break oplocks now as if we opened for delete.
+          * Note: SMB2 does only ofile-based rename.
+          *
+          * Todo:  Use an "internal open" for path-based
+          * rename and delete, then delete this code.
+          */
+         ASSERT(sr->session->dialect < SMB_VERS_2_BASE);
+         status = smb_oplock_break_DELETE(src_node, NULL);
+         if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+                 (void) smb_oplock_wait_break(src_node, 0);
+         }
+ 
+         /*
+          * Path-based access to the src file (no ofile)
+          * so check for sharing violations here.
+          */
          smb_node_rdlock(src_node);
          status = smb_node_rename_check(src_node);
          if (status != NT_STATUS_SUCCESS) {
                  smb_node_unlock(src_node);
!                 return (status);
          }
  
+         status = smb_oplock_break_SETINFO(src_node, NULL,
+             FileRenameInformation);
+         if (status == NT_STATUS_OPLOCK_BREAK_IN_PROGRESS) {
+                 (void) smb_oplock_wait_break(src_node, 0);
+         }
+ 
          /*
           * Note, the combination of these two:
           *      smb_node_rdlock(node);
           *      nbl_start_crit(node->vp, RW_READER);
           * is equivalent to this call:
*** 603,621 ****
           * This checks nbl_share_conflict, nbl_lock_conflict
           */
          status = smb_nbl_conflict(src_node, 0, UINT64_MAX, NBL_RENAME);
          if (status != NT_STATUS_SUCCESS) {
                  smb_node_end_crit(src_node);
-                 smb_node_release(src_fqi->fq_fnode);
-                 smb_node_release(src_fqi->fq_dnode);
-                 if (status == NT_STATUS_SHARING_VIOLATION)
-                         return (EPIPE); /* = ERRbadshare */
-                 return (EACCES);
          }
  
!         /* NB: Caller expects holds on src_fqi fnode, dnode */
!         return (0);
  }
  
  /*
   * smb_rename_release_src
   */
--- 706,719 ----
           * This checks nbl_share_conflict, nbl_lock_conflict
           */
          status = smb_nbl_conflict(src_node, 0, UINT64_MAX, NBL_RENAME);
          if (status != NT_STATUS_SUCCESS) {
                  smb_node_end_crit(src_node);
          }
  
!         /* NB: Caller expects to be "in crit" on fnode. */
!         return (status);
  }
  
  /*
   * smb_rename_release_src
   */