1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright 2017 Nexenta Systems, Inc. All rights reserved. 14 */ 15 16 /* 17 * SMB2 Durable Handle support 18 */ 19 20 #include <sys/types.h> 21 #include <sys/cmn_err.h> 22 #include <sys/fcntl.h> 23 #include <sys/nbmlock.h> 24 #include <smbsrv/string.h> 25 #include <smbsrv/smb_kproto.h> 26 #include <smbsrv/smb_fsops.h> 27 #include <smbsrv/smbinfo.h> 28 #include <smbsrv/smb2_kproto.h> 29 30 /* Windows default values from [MS-SMB2] */ 31 /* 32 * (times in seconds) 33 * resilient: 34 * MaxTimeout = 300 (win7+) 35 * if timeout > MaxTimeout, ERROR 36 * if timeout != 0, timeout = req.timeout 37 * if timeout == 0, timeout = (infinity) (Win7/w2k8r2) 38 * if timeout == 0, timeout = 120 (Win8+) 39 * v2: 40 * if timeout != 0, timeout = MIN(timeout, 300) (spec) 41 * if timeout != 0, timeout = timeout (win8/2k12) 42 * if timeout == 0, timeout = Share.CATimeout. \ 43 * if Share.CATimeout == 0, timeout = 60 (win8/w2k12) 44 * if timeout == 0, timeout = 180 (win8.1/w2k12r2) 45 * open.timeout = 60 (win8/w2k12r2) (i.e. we ignore the request) 46 * v1: 47 * open.timeout = 16 minutes 48 */ 49 50 uint32_t smb2_dh_def_timeout = 60 * MILLISEC; /* mSec. */ 51 uint32_t smb2_dh_max_timeout = 300 * MILLISEC; /* mSec. */ 52 53 uint32_t smb2_res_def_timeout = 120 * MILLISEC; /* mSec. */ 54 uint32_t smb2_res_max_timeout = 300 * MILLISEC; /* mSec. */ 55 56 /* 57 * smb_dh_should_save 58 * 59 * During session tear-down, decide whether to keep a durable handle. 60 * 61 * There are two cases where we save durable handles: 62 * 1. An SMB2 LOGOFF request was received 63 * 2. An unexpected disconnect from the client 64 * Note: Specifying a PrevSessionID in session setup 65 * is considered a disconnect (we just haven't learned about it yet) 66 * In every other case, we close durable handles. 67 * 68 * [MS-SMB2] 3.3.5.6 SMB2_LOGOFF 69 * [MS-SMB2] 3.3.7.1 Handling Loss of a Connection 70 * 71 * If any of the following are true, preserve for reconnect: 72 * 73 * - Open.IsResilient is TRUE. 74 * 75 * - Open.OplockLevel == SMB2_OPLOCK_LEVEL_BATCH and 76 * Open.OplockState == Held, and Open.IsDurable is TRUE. 77 * 78 * - Open.OplockLevel == SMB2_OPLOCK_LEVEL_LEASE, 79 * Lease.LeaseState SMB2_LEASE_HANDLE_CACHING, 80 * Open.OplockState == Held, and Open.IsDurable is TRUE. 81 * 82 * - Open.IsPersistent is TRUE. 83 */ 84 boolean_t 85 smb_dh_should_save(smb_ofile_t *of) 86 { 87 ASSERT(MUTEX_HELD(&of->f_mutex)); 88 ASSERT(of->dh_vers != SMB2_NOT_DURABLE); 89 90 if (of->f_user->preserve_opens == SMB2_DH_PRESERVE_NONE) 91 return (B_FALSE); 92 93 if (of->f_user->preserve_opens == SMB2_DH_PRESERVE_ALL) 94 return (B_TRUE); 95 96 switch (of->dh_vers) { 97 case SMB2_RESILIENT: 98 return (B_TRUE); 99 100 case SMB2_DURABLE_V2: 101 if (of->dh_persist) 102 return (B_TRUE); 103 /* FALLTHROUGH */ 104 case SMB2_DURABLE_V1: 105 /* IS durable (v1 or v2) */ 106 if ((of->f_oplock.og_state & (OPLOCK_LEVEL_BATCH | 107 OPLOCK_LEVEL_CACHE_HANDLE)) != 0) 108 return (B_TRUE); 109 /* FALLTHROUGH */ 110 case SMB2_NOT_DURABLE: 111 default: 112 break; 113 } 114 115 return (B_FALSE); 116 } 117 118 /* 119 * Requirements for ofile found during reconnect (MS-SMB2 3.3.5.9.7): 120 * - security descriptor must match provided descriptor 121 * 122 * If file is leased: 123 * - lease must be requested 124 * - client guid must match session guid 125 * - file name must match given name 126 * - lease key must match provided lease key 127 * If file is not leased: 128 * - Lease must not be requested 129 * 130 * dh_v2 only: 131 * - SMB2_DHANDLE_FLAG_PERSISTENT must be set if dh_persist is true 132 * - SMB2_DHANDLE_FLAG_PERSISTENT must not be set if dh_persist is false 133 * - desired access, share access, and create_options must be ignored 134 * - createguid must match 135 */ 136 static uint32_t 137 smb2_dh_reconnect_checks(smb_request_t *sr, smb_ofile_t *of) 138 { 139 smb_arg_open_t *op = &sr->sr_open; 140 char *fname; 141 142 if (of->f_lease != NULL) { 143 if (bcmp(sr->session->clnt_uuid, 144 of->f_lease->ls_clnt, 16) != 0) 145 return (NT_STATUS_OBJECT_NAME_NOT_FOUND); 146 147 if (op->op_oplock_level != SMB2_OPLOCK_LEVEL_LEASE) 148 return (NT_STATUS_OBJECT_NAME_NOT_FOUND); 149 if (bcmp(op->lease_key, of->f_lease->ls_key, 150 SMB_LEASE_KEY_SZ) != 0) 151 return (NT_STATUS_OBJECT_NAME_NOT_FOUND); 152 153 /* 154 * We're supposed to check the name is the same. 155 * Not really necessary to do this, so just do 156 * minimal effort (check last component) 157 */ 158 fname = strrchr(op->fqi.fq_path.pn_path, '\\'); 159 if (fname != NULL) 160 fname++; 161 else 162 fname = op->fqi.fq_path.pn_path; 163 if (smb_strcasecmp(fname, of->f_node->od_name, 0) != 0) { 164 #ifdef DEBUG 165 cmn_err(CE_NOTE, "reconnect name <%s> of name <%s>", 166 fname, of->f_node->od_name); 167 #endif 168 return (NT_STATUS_INVALID_PARAMETER); 169 } 170 } else { 171 if (op->op_oplock_level == SMB2_OPLOCK_LEVEL_LEASE) 172 return (NT_STATUS_OBJECT_NAME_NOT_FOUND); 173 } 174 175 if (op->dh_vers == SMB2_DURABLE_V2) { 176 boolean_t op_persist = 177 ((op->dh_v2_flags & SMB2_DHANDLE_FLAG_PERSISTENT) != 0); 178 if (of->dh_persist != op_persist) 179 return (NT_STATUS_OBJECT_NAME_NOT_FOUND); 180 if (memcmp(op->create_guid, of->dh_create_guid, UUID_LEN)) 181 return (NT_STATUS_OBJECT_NAME_NOT_FOUND); 182 } 183 184 if (!smb_is_same_user(sr->user_cr, of->f_cr)) 185 return (NT_STATUS_ACCESS_DENIED); 186 187 return (NT_STATUS_SUCCESS); 188 } 189 190 /* 191 * [MS-SMB2] 3.3.5.9.7 and 3.3.5.9.12 (durable reconnect v1/v2) 192 * 193 * Looks up an ofile on the server's sv_dh_list by the persistid. 194 * If found, it validates the request. 195 * (see smb2_dh_reconnect_checks() for details) 196 * If the checks are passed, add it onto the new tree's list. 197 * 198 * Note that the oplock break code path can get to an ofile via the node 199 * ofile list. It starts with a ref taken in smb_ofile_hold_olbrk, which 200 * waits if the ofile is found in state RECONNECT. That wait happens with 201 * the node ofile list lock held as reader, and the oplock mutex held. 202 * Implications of that are: While we're in state RECONNECT, we shoud NOT 203 * block (at least, not for long) and must not try to enter any of the 204 * node ofile list lock or oplock mutex. Thankfully, we don't need to 205 * enter those while reclaiming an orphaned ofile. 206 */ 207 uint32_t 208 smb2_dh_reconnect(smb_request_t *sr) 209 { 210 smb_arg_open_t *op = &sr->sr_open; 211 smb_tree_t *tree = sr->tid_tree; 212 smb_ofile_t *of; 213 cred_t *old_cr; 214 uint32_t status = NT_STATUS_OBJECT_NAME_NOT_FOUND; 215 uint16_t fid = 0; 216 217 if (smb_idpool_alloc(&tree->t_fid_pool, &fid)) 218 return (NT_STATUS_TOO_MANY_OPENED_FILES); 219 220 /* Find orphaned handle. */ 221 of = smb_ofile_lookup_by_persistid(sr, op->dh_fileid.persistent); 222 if (of == NULL) 223 goto errout; 224 225 mutex_enter(&of->f_mutex); 226 if (of->f_state != SMB_OFILE_STATE_ORPHANED) { 227 mutex_exit(&of->f_mutex); 228 goto errout; 229 } 230 231 status = smb2_dh_reconnect_checks(sr, of); 232 if (status != NT_STATUS_SUCCESS) { 233 mutex_exit(&of->f_mutex); 234 goto errout; 235 } 236 237 /* 238 * Note: cv_broadcast(&of->f_cv) when we're 239 * done messing around in this state. 240 * See: smb_ofile_hold_olbrk() 241 */ 242 of->f_state = SMB_OFILE_STATE_RECONNECT; 243 mutex_exit(&of->f_mutex); 244 245 /* 246 * At this point, we should be the only thread with a ref on the 247 * ofile, and the RECONNECT state should prevent new refs from 248 * being granted, or other durable threads from observing or 249 * reclaiming it. Put this ofile in the new tree, similar to 250 * the last part of smb_ofile_open. 251 */ 252 253 old_cr = of->f_cr; 254 of->f_cr = sr->user_cr; 255 crhold(of->f_cr); 256 crfree(old_cr); 257 258 of->f_session = sr->session; /* hold is via user and tree */ 259 smb_user_hold_internal(sr->uid_user); 260 of->f_user = sr->uid_user; 261 smb_tree_hold_internal(tree); 262 of->f_tree = tree; 263 of->f_fid = fid; 264 265 smb_llist_enter(&tree->t_ofile_list, RW_WRITER); 266 smb_llist_insert_tail(&tree->t_ofile_list, of); 267 smb_llist_exit(&tree->t_ofile_list); 268 atomic_inc_32(&tree->t_open_files); 269 atomic_inc_32(&sr->session->s_file_cnt); 270 271 /* 272 * The ofile is now in the caller's session & tree. 273 * 274 * In case smb_ofile_hold or smb_oplock_send_brk() are 275 * waiting for state RECONNECT to complete, wakeup. 276 */ 277 mutex_enter(&of->f_mutex); 278 of->dh_expire_time = 0; 279 of->f_state = SMB_OFILE_STATE_OPEN; 280 cv_broadcast(&of->f_cv); 281 mutex_exit(&of->f_mutex); 282 283 /* 284 * The ofile is now visible in the new session. 285 * From here, this is similar to the last part of 286 * smb_common_open(). 287 */ 288 op->fqi.fq_fattr.sa_mask = SMB_AT_ALL; 289 (void) smb_node_getattr(sr, of->f_node, zone_kcred(), of, 290 &op->fqi.fq_fattr); 291 292 /* 293 * Set up the fileid and dosattr in open_param for response 294 */ 295 op->fileid = op->fqi.fq_fattr.sa_vattr.va_nodeid; 296 op->dattr = op->fqi.fq_fattr.sa_dosattr; 297 298 /* 299 * Set up the file type in open_param for the response 300 * The ref. from ofile lookup is "given" to fid_ofile. 301 */ 302 op->ftype = SMB_FTYPE_DISK; 303 sr->smb_fid = of->f_fid; 304 sr->fid_ofile = of; 305 306 if (smb_node_is_file(of->f_node)) { 307 op->dsize = op->fqi.fq_fattr.sa_vattr.va_size; 308 } else { 309 /* directory or symlink */ 310 op->dsize = 0; 311 } 312 313 op->create_options = 0; /* no more modifications wanted */ 314 op->action_taken = SMB_OACT_OPENED; 315 return (NT_STATUS_SUCCESS); 316 317 errout: 318 if (of != NULL) 319 smb_ofile_release(of); 320 if (fid != 0) 321 smb_idpool_free(&tree->t_fid_pool, fid); 322 323 return (status); 324 } 325 326 /* 327 * Durable handle expiration 328 * ofile state is _EXPIRED 329 */ 330 static void 331 smb2_dh_expire(void *arg) 332 { 333 smb_ofile_t *of = (smb_ofile_t *)arg; 334 335 smb_ofile_close(of, 0); 336 smb_ofile_release(of); 337 } 338 339 void 340 smb2_durable_timers(smb_server_t *sv) 341 { 342 smb_hash_t *hash; 343 smb_llist_t *bucket; 344 smb_ofile_t *of; 345 hrtime_t now; 346 int i; 347 348 hash = sv->sv_persistid_ht; 349 now = gethrtime(); 350 351 for (i = 0; i < hash->num_buckets; i++) { 352 bucket = &hash->buckets[i].b_list; 353 smb_llist_enter(bucket, RW_READER); 354 for (of = smb_llist_head(bucket); 355 of != NULL; 356 of = smb_llist_next(bucket, of)) { 357 SMB_OFILE_VALID(of); 358 359 /* 360 * Check outside the mutex first to avoid some 361 * mutex_enter work in this loop. If the state 362 * changes under foot, the worst that happens 363 * is we either enter the mutex when we might 364 * not have needed to, or we miss some DH in 365 * this pass and get it on the next. 366 */ 367 if (of->f_state != SMB_OFILE_STATE_ORPHANED) 368 continue; 369 370 mutex_enter(&of->f_mutex); 371 /* STATE_ORPHANED implies dh_expire_time != 0 */ 372 if (of->f_state == SMB_OFILE_STATE_ORPHANED && 373 of->dh_expire_time <= now) { 374 of->f_state = SMB_OFILE_STATE_EXPIRED; 375 /* inline smb_ofile_hold_internal() */ 376 of->f_refcnt++; 377 smb_llist_post(bucket, of, smb2_dh_expire); 378 } 379 mutex_exit(&of->f_mutex); 380 } 381 smb_llist_exit(bucket); 382 } 383 } 384 385 /* 386 * Clean out durable handles during shutdown. 387 * Like, smb2_durable_timers but expire all, 388 * and make sure the hash buckets are empty. 389 */ 390 void 391 smb2_dh_shutdown(smb_server_t *sv) 392 { 393 smb_hash_t *hash; 394 smb_llist_t *bucket; 395 smb_ofile_t *of; 396 int i; 397 398 hash = sv->sv_persistid_ht; 399 400 for (i = 0; i < hash->num_buckets; i++) { 401 bucket = &hash->buckets[i].b_list; 402 smb_llist_enter(bucket, RW_READER); 403 of = smb_llist_head(bucket); 404 while (of != NULL) { 405 SMB_OFILE_VALID(of); 406 mutex_enter(&of->f_mutex); 407 408 switch (of->f_state) { 409 case SMB_OFILE_STATE_ORPHANED: 410 of->f_state = SMB_OFILE_STATE_EXPIRED; 411 /* inline smb_ofile_hold_internal() */ 412 of->f_refcnt++; 413 smb_llist_post(bucket, of, smb2_dh_expire); 414 break; 415 default: 416 break; 417 } 418 mutex_exit(&of->f_mutex); 419 of = smb_llist_next(bucket, of); 420 } 421 smb_llist_exit(bucket); 422 } 423 424 #ifdef DEBUG 425 for (i = 0; i < hash->num_buckets; i++) { 426 bucket = &hash->buckets[i].b_list; 427 smb_llist_enter(bucket, RW_READER); 428 of = smb_llist_head(bucket); 429 while (of != NULL) { 430 SMB_OFILE_VALID(of); 431 cmn_err(CE_NOTE, "dh_shutdown leaked of=%p", 432 (void *)of); 433 of = smb_llist_next(bucket, of); 434 } 435 smb_llist_exit(bucket); 436 } 437 #endif // DEBUG 438 } 439 440 uint32_t 441 smb2_fsctl_set_resilient(smb_request_t *sr, smb_fsctl_t *fsctl) 442 { 443 uint32_t timeout; 444 smb_ofile_t *of = sr->fid_ofile; 445 446 /* 447 * Note: The spec does not explicitly prohibit resilient directories 448 * the same way it prohibits durable directories. We prohibit them 449 * anyway as a simplifying assumption, as there doesn't seem to be 450 * much use for it. (HYPER-V only seems to use it on files anyway) 451 */ 452 if (fsctl->InputCount < 8 || !smb_node_is_file(of->f_node)) 453 return (NT_STATUS_INVALID_PARAMETER); 454 455 (void) smb_mbc_decodef(fsctl->in_mbc, "l4.", 456 &timeout); /* milliseconds */ 457 458 if (smb2_enable_dh == 0) 459 return (NT_STATUS_NOT_SUPPORTED); 460 461 /* 462 * The spec wants us to return INVALID_PARAMETER if the timeout 463 * is too large, but we have no way of informing the client 464 * what an appropriate timeout is, so just set the timeout to 465 * our max and return SUCCESS. 466 */ 467 if (timeout == 0) 468 timeout = smb2_res_def_timeout; 469 if (timeout > smb2_res_max_timeout) 470 timeout = smb2_res_max_timeout; 471 472 mutex_enter(&of->f_mutex); 473 of->dh_vers = SMB2_RESILIENT; 474 of->dh_timeout_offset = MSEC2NSEC(timeout); 475 mutex_exit(&of->f_mutex); 476 477 return (NT_STATUS_SUCCESS); 478 } 479